vibe-and-thrive 1.6.8 → 1.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-and-thrive",
3
- "version": "1.6.8",
3
+ "version": "1.7.0",
4
4
  "description": "Ship quality code faster with AI - RALPH autonomous loop, Claude Code hooks, and pre-commit checks",
5
5
  "author": "Allie Jones <allie@allthrive.ai>",
6
6
  "license": "MIT",
package/ralph/loop.sh CHANGED
@@ -251,55 +251,59 @@ startup_checklist() {
251
251
  fi
252
252
  }
253
253
 
254
- # Build the prompt with story context injected
255
- build_prompt() {
256
- local story="$1"
257
- local failure_context="${2:-}"
258
-
259
- # Read base PROMPT.md
260
- cat "$PROMPT_FILE"
254
+ # Helper: Inject story details into prompt
255
+ _inject_story_context() {
256
+ local story_json="$1"
261
257
 
262
258
  echo ""
263
259
  echo "---"
264
260
  echo ""
265
261
  echo "## Current Story"
266
262
  echo ""
267
-
268
- # Inject story details
269
- local story_json
270
- story_json=$(jq --arg id "$story" '.stories[] | select(.id==$id)' "$RALPH_DIR/prd.json")
271
263
  echo '```json'
272
264
  echo "$story_json"
273
265
  echo '```'
266
+ }
267
+
268
+ # Helper: Inject file guidance into prompt
269
+ _inject_file_guidance() {
270
+ local story_json="$1"
274
271
 
275
- # Extract file guidance if present
276
272
  local has_files
277
273
  has_files=$(echo "$story_json" | jq -r '.files // empty' 2>/dev/null)
278
- if [[ -n "$has_files" ]]; then
279
- echo ""
280
- echo "### File Guidance for This Story"
281
- echo ""
282
- echo "**Create these files:**"
283
- echo "$story_json" | jq -r '.files.create[]? // empty' | sed 's/^/- /'
284
- echo ""
285
- echo "**Modify these files:**"
286
- echo "$story_json" | jq -r '.files.modify[]? // empty' | sed 's/^/- /'
287
- echo ""
288
- echo "**Reuse/import from:**"
289
- echo "$story_json" | jq -r '.files.reuse[]? // empty' | sed 's/^/- /'
290
- fi
274
+ [[ -z "$has_files" ]] && return
275
+
276
+ echo ""
277
+ echo "### File Guidance for This Story"
278
+ echo ""
279
+ echo "**Create these files:**"
280
+ echo "$story_json" | jq -r '.files.create[]? // empty' | sed 's/^/- /'
281
+ echo ""
282
+ echo "**Modify these files:**"
283
+ echo "$story_json" | jq -r '.files.modify[]? // empty' | sed 's/^/- /'
284
+ echo ""
285
+ echo "**Reuse/import from:**"
286
+ echo "$story_json" | jq -r '.files.reuse[]? // empty' | sed 's/^/- /'
287
+ }
288
+
289
+ # Helper: Inject scalability guidance for story
290
+ _inject_story_scale() {
291
+ local story_json="$1"
291
292
 
292
- # Extract scalability guidance if present (for backend stories)
293
293
  local has_scale
294
294
  has_scale=$(echo "$story_json" | jq -r '.scale // empty' 2>/dev/null)
295
- if [[ -n "$has_scale" ]]; then
296
- echo ""
297
- echo "### Scalability Requirements for This Story"
298
- echo ""
299
- echo "$story_json" | jq -r '.scale | to_entries[] | "- **\(.key):** \(.value)"' 2>/dev/null
300
- fi
295
+ [[ -z "$has_scale" ]] && return
296
+
297
+ echo ""
298
+ echo "### Scalability Requirements for This Story"
299
+ echo ""
300
+ echo "$story_json" | jq -r '.scale | to_entries[] | "- **\(.key):** \(.value)"' 2>/dev/null
301
+ }
302
+
303
+ # Helper: Inject styleguide reference for frontend stories
304
+ _inject_styleguide() {
305
+ local story_json="$1"
301
306
 
302
- # For frontend stories, instruct Claude to read the styleguide first
303
307
  local story_type
304
308
  story_type=$(echo "$story_json" | jq -r '.type // "frontend"' 2>/dev/null)
305
309
  local styleguide_path
@@ -312,74 +316,87 @@ build_prompt() {
312
316
  echo "**FIRST:** Read the project styleguide at \`$styleguide_path\` before implementing."
313
317
  echo "Use existing components, colors, and patterns from the styleguide."
314
318
  fi
319
+ }
315
320
 
321
+ # Helper: Inject feature-level context
322
+ _inject_feature_context() {
316
323
  echo ""
317
324
  echo "## Feature Context"
318
325
  echo ""
319
326
  echo '```json'
320
327
  jq '{feature: .feature, metadata: .metadata}' "$RALPH_DIR/prd.json"
321
328
  echo '```'
329
+ }
322
330
 
323
- # Include scalability if defined
331
+ # Helper: Inject scalability requirements
332
+ _inject_scalability() {
324
333
  local has_scalability
325
334
  has_scalability=$(jq -r '.scalability // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
326
- if [[ -n "$has_scalability" ]]; then
327
- echo ""
328
- echo "## Scalability Requirements"
329
- echo ""
330
- echo "**IMPORTANT:** Follow these scalability rules."
331
- echo ""
332
- echo '```json'
333
- jq '.scalability' "$RALPH_DIR/prd.json"
334
- echo '```'
335
- echo ""
336
- echo "### Key Rules:"
337
- echo "- Always paginate list endpoints (never return unbounded arrays)"
338
- echo "- Avoid N+1 queries - eager load relationships"
339
- echo "- Add database indexes for frequently queried fields"
340
- echo "- Implement caching strategy as specified"
341
- echo "- Add rate limiting to public endpoints"
342
- fi
335
+ [[ -z "$has_scalability" ]] && return
336
+
337
+ echo ""
338
+ echo "## Scalability Requirements"
339
+ echo ""
340
+ echo "**IMPORTANT:** Follow these scalability rules."
341
+ echo ""
342
+ echo '```json'
343
+ jq '.scalability' "$RALPH_DIR/prd.json"
344
+ echo '```'
345
+ echo ""
346
+ echo "### Key Rules:"
347
+ echo "- Always paginate list endpoints (never return unbounded arrays)"
348
+ echo "- Avoid N+1 queries - eager load relationships"
349
+ echo "- Add database indexes for frequently queried fields"
350
+ echo "- Implement caching strategy as specified"
351
+ echo "- Add rate limiting to public endpoints"
352
+ }
343
353
 
344
- # Include architecture if defined
354
+ # Helper: Inject architecture guidelines
355
+ _inject_architecture() {
345
356
  local has_architecture
346
357
  has_architecture=$(jq -r '.architecture // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
347
- if [[ -n "$has_architecture" ]]; then
348
- echo ""
349
- echo "## Architecture Guidelines"
350
- echo ""
351
- echo "**IMPORTANT:** Follow these architecture rules strictly."
352
- echo ""
353
- echo '```json'
354
- jq '.architecture' "$RALPH_DIR/prd.json"
355
- echo '```'
356
- echo ""
357
- echo "### Key Rules:"
358
- echo "- Put files in the specified directories"
359
- echo "- Reuse existing components listed in 'patterns.reuse'"
360
- echo "- Do NOT create anything in 'doNotCreate'"
361
- echo "- Keep files under $(jq -r '.architecture.principles.maxFileLines // 300' "$RALPH_DIR/prd.json") lines"
362
- echo "- Scripts go in scripts/, docs go in docs/"
363
- fi
358
+ [[ -z "$has_architecture" ]] && return
364
359
 
365
- # Include failure context from previous iteration if available
366
- if [[ -n "$failure_context" ]]; then
367
- echo ""
368
- echo "## Previous Iteration Failed"
369
- echo ""
370
- echo "**IMPORTANT:** The previous attempt at this story failed verification. Review the errors below and fix them."
371
- echo ""
372
- echo '```'
373
- echo "$failure_context"
374
- echo '```'
375
- echo ""
376
- echo "### What to do:"
377
- echo "1. Read the error messages carefully"
378
- echo "2. Identify the root cause"
379
- echo "3. Fix the issue (do not just retry the same approach)"
380
- echo "4. Run verification again"
381
- fi
360
+ echo ""
361
+ echo "## Architecture Guidelines"
362
+ echo ""
363
+ echo "**IMPORTANT:** Follow these architecture rules strictly."
364
+ echo ""
365
+ echo '```json'
366
+ jq '.architecture' "$RALPH_DIR/prd.json"
367
+ echo '```'
368
+ echo ""
369
+ echo "### Key Rules:"
370
+ echo "- Put files in the specified directories"
371
+ echo "- Reuse existing components listed in 'patterns.reuse'"
372
+ echo "- Do NOT create anything in 'doNotCreate'"
373
+ echo "- Keep files under $(jq -r '.architecture.principles.maxFileLines // 300' "$RALPH_DIR/prd.json") lines"
374
+ echo "- Scripts go in scripts/, docs go in docs/"
375
+ }
376
+
377
+ # Helper: Inject failure context from previous iteration
378
+ _inject_failure_context() {
379
+ local failure_context="$1"
380
+ [[ -z "$failure_context" ]] && return
381
+
382
+ echo ""
383
+ echo "## Previous Iteration Failed"
384
+ echo ""
385
+ echo "**IMPORTANT:** The previous attempt at this story failed verification. Review the errors below and fix them."
386
+ echo ""
387
+ echo '```'
388
+ echo "$failure_context"
389
+ echo '```'
390
+ echo ""
391
+ echo "### What to do:"
392
+ echo "1. Read the error messages carefully"
393
+ echo "2. Identify the root cause"
394
+ echo "3. Fix the issue (do not just retry the same approach)"
395
+ echo "4. Run verification again"
396
+ }
382
397
 
398
+ # Helper: Inject signs (learned patterns)
399
+ _inject_signs() {
383
400
  echo ""
384
401
  echo "## Signs (Learned Patterns)"
385
402
  echo ""
@@ -390,16 +407,43 @@ build_prompt() {
390
407
  else
391
408
  echo "(none yet)"
392
409
  fi
410
+ }
393
411
 
394
- # Include user's DNA (personal preferences) if it exists
395
- if [[ -f "$HOME/.claude/DNA.md" ]]; then
396
- echo ""
397
- echo "## Developer DNA"
398
- echo ""
399
- echo "The developer has these working preferences:"
400
- echo ""
401
- cat "$HOME/.claude/DNA.md"
402
- fi
412
+ # Helper: Inject developer DNA
413
+ _inject_developer_dna() {
414
+ [[ ! -f "$HOME/.claude/DNA.md" ]] && return
415
+
416
+ echo ""
417
+ echo "## Developer DNA"
418
+ echo ""
419
+ echo "The developer has these working preferences:"
420
+ echo ""
421
+ cat "$HOME/.claude/DNA.md"
422
+ }
423
+
424
+ # Build the prompt with story context injected
425
+ build_prompt() {
426
+ local story="$1"
427
+ local failure_context="${2:-}"
428
+
429
+ # Read base PROMPT.md
430
+ cat "$PROMPT_FILE"
431
+
432
+ # Get story JSON once
433
+ local story_json
434
+ story_json=$(jq --arg id "$story" '.stories[] | select(.id==$id)' "$RALPH_DIR/prd.json")
435
+
436
+ # Inject all sections
437
+ _inject_story_context "$story_json"
438
+ _inject_file_guidance "$story_json"
439
+ _inject_story_scale "$story_json"
440
+ _inject_styleguide "$story_json"
441
+ _inject_feature_context
442
+ _inject_scalability
443
+ _inject_architecture
444
+ _inject_failure_context "$failure_context"
445
+ _inject_signs
446
+ _inject_developer_dna
403
447
  }
404
448
 
405
449
  # Mark feature as complete (keep PRD for appending new stories)
@@ -418,7 +462,14 @@ archive_feature() {
418
462
  if ! git commit -m "feat: complete $feature_name" 2>/dev/null; then
419
463
  # Retry after pre-commit auto-fixes
420
464
  git add -A
421
- git commit -m "feat: complete $feature_name" 2>/dev/null || true
465
+ if ! git commit -m "feat: complete $feature_name" 2>/dev/null; then
466
+ # Check if it's "nothing to commit" vs real error
467
+ if git diff --cached --quiet 2>/dev/null; then
468
+ echo " (no changes to commit)"
469
+ else
470
+ print_warning "Final commit failed - check git status"
471
+ fi
472
+ fi
422
473
  fi
423
474
  fi
424
475
 
package/ralph/utils.sh CHANGED
@@ -6,9 +6,17 @@ readonly MAX_LOG_LINES=30
6
6
  readonly MAX_PROGRESS_LINES=10
7
7
  readonly MAX_GIT_STATUS_LINES=10
8
8
  readonly MAX_OUTPUT_PREVIEW_LINES=20
9
+ readonly MAX_ERROR_PREVIEW_LINES=40
10
+ readonly MAX_LINT_ERROR_LINES=20
9
11
  readonly ITERATION_DELAY_SECONDS=2
10
12
  readonly DEFAULT_TIMEOUT_SECONDS=600
11
13
  readonly DEFAULT_MAX_ITERATIONS=20
14
+ readonly CODE_REVIEW_TIMEOUT_SECONDS=120
15
+ readonly MAX_PROGRESS_FILE_LINES=1000
16
+
17
+ # Common project directories (avoid duplication across files)
18
+ readonly FRONTEND_DIRS=("apps/web" "frontend" "client" "web")
19
+ readonly BACKEND_DIRS=("apps/api" "api" "backend" "server")
12
20
 
13
21
  # Track temp files for safe cleanup
14
22
  RALPH_TEMP_FILES=()
@@ -20,6 +28,24 @@ YELLOW='\033[1;33m'
20
28
  BLUE='\033[0;34m'
21
29
  NC='\033[0m' # No Color
22
30
 
31
+ # Get existing frontend directories in this project
32
+ get_frontend_dirs() {
33
+ local dirs=()
34
+ for d in "${FRONTEND_DIRS[@]}"; do
35
+ [[ -d "$d" ]] && dirs+=("$d")
36
+ done
37
+ printf '%s\n' "${dirs[@]}"
38
+ }
39
+
40
+ # Get existing backend directories in this project
41
+ get_backend_dirs() {
42
+ local dirs=()
43
+ for d in "${BACKEND_DIRS[@]}"; do
44
+ [[ -d "$d" ]] && dirs+=("$d")
45
+ done
46
+ printf '%s\n' "${dirs[@]}"
47
+ }
48
+
23
49
  # Progress bar for story display
24
50
  progress_bar() {
25
51
  local current=$1 total=$2 width=${3:-6}
@@ -95,14 +121,26 @@ check_dependencies() {
95
121
  fi
96
122
  }
97
123
 
98
- # Log progress to progress.txt
124
+ # Log progress to progress.txt (with rotation to prevent unbounded growth)
99
125
  log_progress() {
100
126
  local story="$1"
101
127
  local status="$2"
102
128
  local msg="${3:-}"
103
129
  local timestamp
130
+ local progress_file="$RALPH_DIR/progress.txt"
131
+
104
132
  timestamp=$(date -Iseconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S)
105
- echo "[$timestamp] $status $story $msg" >> "$RALPH_DIR/progress.txt"
133
+ echo "[$timestamp] $status $story $msg" >> "$progress_file"
134
+
135
+ # Rotate if file exceeds max lines (keep last half)
136
+ if [[ -f "$progress_file" ]]; then
137
+ local line_count
138
+ line_count=$(wc -l < "$progress_file" 2>/dev/null || echo "0")
139
+ if [[ "$line_count" -gt "$MAX_PROGRESS_FILE_LINES" ]]; then
140
+ local keep_lines=$((MAX_PROGRESS_FILE_LINES / 2))
141
+ tail -"$keep_lines" "$progress_file" > "$progress_file.tmp" && mv "$progress_file.tmp" "$progress_file"
142
+ fi
143
+ fi
106
144
  }
107
145
 
108
146
  # Get a value from config.json with a default
package/ralph/verify.sh CHANGED
@@ -225,8 +225,8 @@ EOF
225
225
  echo " Reviewing changes..."
226
226
 
227
227
  local result
228
- # 2 minute timeout for code review
229
- result=$(echo "$prompt" | run_with_timeout 120 claude -p --dangerously-skip-permissions 2>/dev/null) || {
228
+ # Timeout for code review (defined in utils.sh)
229
+ result=$(echo "$prompt" | run_with_timeout "$CODE_REVIEW_TIMEOUT_SECONDS" claude -p --dangerously-skip-permissions 2>/dev/null) || {
230
230
  print_warning " Code review skipped (Claude unavailable or timed out)"
231
231
  return 0
232
232
  }
@@ -345,13 +345,13 @@ run_auto_fix() {
345
345
  fi
346
346
  fi
347
347
 
348
- # Check frontend directory too (monorepo support)
349
- local fe_dir=""
350
- [[ -d "frontend" ]] && fe_dir="frontend"
351
- [[ -d "client" ]] && fe_dir="client"
352
- [[ -d "web" ]] && fe_dir="web"
348
+ # Check frontend directories (monorepo support)
349
+ local fe_dirs
350
+ fe_dirs=$(get_frontend_dirs)
353
351
 
354
- if [[ -n "$fe_dir" && -f "$fe_dir/package.json" ]]; then
352
+ while IFS= read -r fe_dir; do
353
+ [[ -z "$fe_dir" ]] && continue
354
+ [[ ! -f "$fe_dir/package.json" ]] && continue
355
355
  # ESLint fix
356
356
  if grep -q '"eslint"' "$fe_dir/package.json" 2>/dev/null; then
357
357
  (cd "$fe_dir" && npx eslint --fix . 2>/dev/null) || true
@@ -366,7 +366,7 @@ run_auto_fix() {
366
366
  if grep -q '"prettier"' "$fe_dir/package.json" 2>/dev/null; then
367
367
  (cd "$fe_dir" && npx prettier --write . 2>/dev/null) || true
368
368
  fi
369
- fi
369
+ done <<< "$fe_dirs"
370
370
 
371
371
  # Prettier fix (root - for non-monorepo)
372
372
  if [[ -f "package.json" ]] && grep -q '"prettier"' package.json 2>/dev/null; then
@@ -387,14 +387,18 @@ verify_lint() {
387
387
  print_error "failed"
388
388
  echo ""
389
389
  echo " Unfixable lint errors:"
390
- ruff check . 2>/dev/null | head -20 | sed 's/^/ /'
390
+ ruff check . 2>/dev/null | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
391
391
  failed=1
392
392
  fi
393
393
  fi
394
394
 
395
395
  # Check for monorepo backend directories
396
- for api_dir in "apps/api" "backend" "api"; do
397
- if [[ -d "$api_dir" ]] && [[ -f "$api_dir/pyproject.toml" || -f "$api_dir/ruff.toml" ]]; then
396
+ local api_dirs
397
+ api_dirs=$(get_backend_dirs)
398
+
399
+ while IFS= read -r api_dir; do
400
+ [[ -z "$api_dir" ]] && continue
401
+ if [[ -f "$api_dir/pyproject.toml" || -f "$api_dir/ruff.toml" ]]; then
398
402
  echo -n " Ruff lint check ($api_dir)... "
399
403
  if (cd "$api_dir" && ruff check . --quiet 2>/dev/null); then
400
404
  print_success "passed"
@@ -402,11 +406,11 @@ verify_lint() {
402
406
  print_error "failed"
403
407
  echo ""
404
408
  echo " Unfixable lint errors in $api_dir:"
405
- (cd "$api_dir" && ruff check . 2>/dev/null) | head -20 | sed 's/^/ /'
409
+ (cd "$api_dir" && ruff check . 2>/dev/null) | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
406
410
  failed=1
407
411
  fi
408
412
  fi
409
- done
413
+ done <<< "$api_dirs"
410
414
 
411
415
  return $failed
412
416
  }
@@ -418,15 +422,16 @@ run_fastapi_response_check() {
418
422
  # Skip if check script doesn't exist
419
423
  [[ ! -f "$check_script" ]] && return 0
420
424
 
421
- # Find FastAPI directories
425
+ # Find FastAPI directories (check root + backend dirs)
422
426
  local fastapi_dirs=()
423
- for dir in "." "apps/api" "api" "backend" "server"; do
424
- if [[ -d "$dir" ]]; then
425
- # Check for FastAPI imports
426
- if grep -rq "from fastapi" "$dir"/*.py 2>/dev/null || \
427
- grep -rq "from fastapi" "$dir"/**/*.py 2>/dev/null; then
428
- fastapi_dirs+=("$dir")
429
- fi
427
+ local all_dirs=("." $(get_backend_dirs))
428
+
429
+ for dir in "${all_dirs[@]}"; do
430
+ [[ ! -d "$dir" ]] && continue
431
+ # Check for FastAPI imports
432
+ if grep -rq "from fastapi" "$dir"/*.py 2>/dev/null || \
433
+ grep -rq "from fastapi" "$dir"/**/*.py 2>/dev/null; then
434
+ fastapi_dirs+=("$dir")
430
435
  fi
431
436
  done
432
437
 
@@ -445,7 +450,7 @@ run_fastapi_response_check() {
445
450
  else
446
451
  print_error "failed"
447
452
  echo ""
448
- echo "$output" | head -40 | sed 's/^/ /'
453
+ echo "$output" | head -"$MAX_ERROR_PREVIEW_LINES" | sed 's/^/ /'
449
454
  # Save for failure context so Claude can fix
450
455
  echo "$output" > "$log_file"
451
456
  failed=1
@@ -493,7 +498,7 @@ run_configured_checks() {
493
498
  print_error "failed"
494
499
  echo ""
495
500
  echo " Pre-commit hook errors (after auto-fix):"
496
- grep -A 20 "Failed\|error\|Error" "$precommit_log" | head -40 | sed 's/^/ /'
501
+ grep -A 20 "Failed\|error\|Error" "$precommit_log" | head -"$MAX_ERROR_PREVIEW_LINES" | sed 's/^/ /'
497
502
  return 1
498
503
  fi
499
504
  else
@@ -501,7 +506,7 @@ run_configured_checks() {
501
506
  print_error "failed"
502
507
  echo ""
503
508
  echo " Pre-commit hook errors:"
504
- grep -A 20 "Failed\|error\|Error" "$precommit_log" | head -40 | sed 's/^/ /'
509
+ grep -A 20 "Failed\|error\|Error" "$precommit_log" | head -"$MAX_ERROR_PREVIEW_LINES" | sed 's/^/ /'
505
510
  return 1
506
511
  fi
507
512
  fi