agentic-loop 3.22.1 → 3.26.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.
@@ -119,7 +119,7 @@ validate_prd() {
119
119
  print_error "prd.json is not valid JSON."
120
120
  echo ""
121
121
  echo "Fix it manually or regenerate with:"
122
- echo " /idea 'your feature'"
122
+ echo " /prd 'your feature'"
123
123
  echo ""
124
124
  return 1
125
125
  fi
@@ -131,7 +131,7 @@ validate_prd() {
131
131
  print_error "prd.json is missing .feature.name"
132
132
  echo ""
133
133
  echo "Add a feature name to your PRD or regenerate with:"
134
- echo " /idea 'your feature'"
134
+ echo " /prd 'your feature'"
135
135
  echo ""
136
136
  return 1
137
137
  fi
@@ -140,7 +140,7 @@ validate_prd() {
140
140
  if ! jq -e '.stories' "$prd_file" >/dev/null 2>&1; then
141
141
  print_error "prd.json is missing 'stories' array."
142
142
  echo ""
143
- echo "Regenerate with: /idea 'your feature'"
143
+ echo "Regenerate with: /prd 'your feature'"
144
144
  echo ""
145
145
  return 1
146
146
  fi
@@ -151,7 +151,7 @@ validate_prd() {
151
151
  if [[ "$story_count" == "0" ]]; then
152
152
  print_error "prd.json has no stories."
153
153
  echo ""
154
- echo "Regenerate with: /idea 'your feature'"
154
+ echo "Regenerate with: /prd 'your feature'"
155
155
  echo ""
156
156
  return 1
157
157
  fi
@@ -163,7 +163,7 @@ validate_prd() {
163
163
  print_error "Some stories are missing required fields (id, title):"
164
164
  echo "$invalid_stories" | head -5
165
165
  echo ""
166
- echo "Fix the PRD or regenerate with: /idea 'your feature'"
166
+ echo "Fix the PRD or regenerate with: /prd 'your feature'"
167
167
  echo ""
168
168
  return 1
169
169
  fi
@@ -405,6 +405,20 @@ _check_story_issues() {
405
405
  fi
406
406
  fi
407
407
 
408
+ # Import-check anti-pattern: python -c "from X import Y" or hasattr()
409
+ if [[ -n "$test_steps" ]] && echo "$test_steps" | grep -qE '(python[3]? -c .*(from |import |hasattr))'; then
410
+ echo "testSteps use import-checks (python -c 'from/import/hasattr') — replace with real behavioral tests"
411
+ fi
412
+
413
+ # Frontend stories must include Playwright MCP visual verification guidance in notes
414
+ if [[ "$story_type" == "frontend" ]]; then
415
+ local story_notes
416
+ story_notes=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .notes // ""' "$prd_file")
417
+ if ! echo "$story_notes" | grep -qiE "(playwright.*mcp|mcp.*playwright|visual.*verif|screenshot|navigate.*screenshot)"; then
418
+ echo "frontend notes should include Playwright MCP visual verification guidance"
419
+ fi
420
+ fi
421
+
408
422
  # All testSteps are server-dependent
409
423
  if [[ -n "$test_steps" ]]; then
410
424
  local has_offline=false has_server=false
@@ -443,6 +457,8 @@ _validate_and_fix_stories() {
443
457
  local cnt_naming_convention=0 cnt_bare_pytest=0 cnt_bare_python=0
444
458
  local cnt_server_only=0
445
459
  local cnt_custom=0
460
+ local cnt_import_check=0
461
+ local cnt_playwright_notes=0
446
462
 
447
463
  echo " Checking test coverage..."
448
464
 
@@ -508,6 +524,8 @@ _validate_and_fix_stories() {
508
524
  "missing pagination criteria") cnt_list_pagination=$((cnt_list_pagination + 1)) ;;
509
525
  "API consumer needs camelCase transformation note") cnt_naming_convention=$((cnt_naming_convention + 1)) ;;
510
526
  "all testSteps need a live server"*) cnt_server_only=$((cnt_server_only + 1)) ;;
527
+ "testSteps use import-checks"*) cnt_import_check=$((cnt_import_check + 1)) ;;
528
+ "frontend notes should include Playwright"*) cnt_playwright_notes=$((cnt_playwright_notes + 1)) ;;
511
529
  esac
512
530
  done <<< "$shared_issues"
513
531
 
@@ -539,6 +557,17 @@ _validate_and_fix_stories() {
539
557
  fi
540
558
  done <<< "$story_ids"
541
559
 
560
+ # Global check: if any frontend stories exist, at least one story should have E2E tests
561
+ local has_frontend_stories has_e2e_story
562
+ has_frontend_stories=$(jq -r '[.stories[] | select(.type == "frontend")] | length' "$prd_file" 2>/dev/null)
563
+ has_e2e_story=$(jq -r '[.stories[] | select(.testing.types[]? == "e2e")] | length' "$prd_file" 2>/dev/null)
564
+ if [[ "$has_frontend_stories" -gt 0 && "$has_e2e_story" == "0" ]]; then
565
+ echo ""
566
+ print_warning "No E2E story found — frontend features should have at least one Playwright E2E story"
567
+ echo " Add a final story with testing.types: [\"e2e\"] and Playwright testSteps"
568
+ echo ""
569
+ fi
570
+
542
571
  # If issues found, show summary and attempt fix
543
572
  if [[ "$needs_fix" == "true" ]]; then
544
573
  echo " Optimizing test coverage for $story_count stories..."
@@ -558,6 +587,8 @@ _validate_and_fix_stories() {
558
587
  [[ $cnt_bare_pytest -gt 0 ]] && echo " ${cnt_bare_pytest}x use '${py_runner:-python3} pytest' not bare 'pytest'"
559
588
  [[ $cnt_bare_python -gt 0 ]] && echo " ${cnt_bare_python}x use 'python3' not bare 'python' (macOS compatibility)"
560
589
  [[ $cnt_server_only -gt 0 ]] && echo " ${cnt_server_only}x all testSteps need live server (add offline fallback)"
590
+ [[ $cnt_import_check -gt 0 ]] && echo " ${cnt_import_check}x testSteps use import-checks (replace with real tests)"
591
+ [[ $cnt_playwright_notes -gt 0 ]] && echo " ${cnt_playwright_notes}x frontend: add Playwright MCP visual verification to notes"
561
592
  [[ $cnt_custom -gt 0 ]] && echo " ${cnt_custom} stories with custom check issues"
562
593
 
563
594
  # Skip auto-fix in dry-run mode
package/ralph/prd.sh CHANGED
@@ -118,7 +118,7 @@ ralph_prd() {
118
118
  printf '%s\n' ' "acceptanceCriteria": ["AC 1", "AC 2"],' >> "$prompt_file"
119
119
  printf '%s\n' ' "errorHandling": ["what happens on failure"],' >> "$prompt_file"
120
120
  printf '%s\n' ' "testSteps": ["executable shell commands only"],' >> "$prompt_file"
121
- printf '%s\n' ' "contextFiles": ["docs/ideas/feature.md"],' >> "$prompt_file"
121
+ printf '%s\n' ' "contextFiles": ["docs/ideas/feature.md", "docs/plans/feature-plan.md"],' >> "$prompt_file"
122
122
  printf '%s\n' ' "notes": ""' >> "$prompt_file"
123
123
  printf '%s\n' ' }' >> "$prompt_file"
124
124
  printf '%s\n' ' ]' >> "$prompt_file"
@@ -130,7 +130,7 @@ demo_skills() {
130
130
 
131
131
  echo -e " ${BOLD}In Claude Code, type these commands:${NC}\n"
132
132
 
133
- echo -e " ${CYAN}/idea${NC} Brainstorm → PRD → Ready for Ralph"
133
+ echo -e " ${CYAN}/prd${NC} Brainstorm → PRD → Ready for Ralph"
134
134
  sleep 0.1
135
135
  echo -e " ${CYAN}/vibe-help${NC} Quick reference cheatsheet"
136
136
  sleep 0.1
@@ -83,7 +83,7 @@ lesson_slash_commands() {
83
83
  echo ""
84
84
  echo -e " ${BOLD}Custom commands${NC} (from ${DIM}.claude/skills/${NC}):"
85
85
  echo ""
86
- echo -e " ${CYAN}/idea${NC} Brainstorm → PRD → Ready for Ralph"
86
+ echo -e " ${CYAN}/prd${NC} Brainstorm → PRD → Ready for Ralph"
87
87
  echo -e " ${CYAN}/vibe-check${NC} Code quality audit"
88
88
  echo -e " ${CYAN}/review${NC} Code review with security checks"
89
89
  echo ""
@@ -135,7 +135,7 @@ lesson_integration() {
135
135
 
136
136
  echo -e " ${BOLD}agentic-loop${NC} adds superpowers to Claude Code:"
137
137
  echo ""
138
- echo -e " ${BOLD}1. The Workflow (/idea → Ralph → Ship)${NC}"
138
+ echo -e " ${BOLD}1. The Workflow (/prd → Ralph → Ship)${NC}"
139
139
  echo -e " ${DIM}Brainstorm ideas, generate PRDs, execute autonomously${NC}"
140
140
  echo ""
141
141
  echo -e " ${BOLD}2. Code Quality (/vibe-check)${NC}"
@@ -145,7 +145,7 @@ lesson_integration() {
145
145
  echo -e " ${DIM}Automatically blocks problematic commits${NC}"
146
146
  echo ""
147
147
  echo -e " ${BOLD}4. Slash Commands (7 included)${NC}"
148
- echo -e " ${DIM}/idea, /vibe-help, /tour, /vibe-check, /review, /explain, /styleguide${NC}"
148
+ echo -e " ${DIM}/prd, /vibe-help, /tour, /vibe-check, /review, /explain, /styleguide${NC}"
149
149
  echo ""
150
150
  echo -e " ${BOLD}5. MCP Configuration${NC}"
151
151
  echo -e " ${DIM}Chrome DevTools for visual verification${NC}"
package/ralph/setup.sh CHANGED
@@ -169,6 +169,7 @@ ralph_setup() {
169
169
  setup_custom_checks
170
170
  setup_gitignore
171
171
  setup_claude_hooks "$pkg_root"
172
+ setup_claude_hud
172
173
  setup_slash_commands "$pkg_root"
173
174
  setup_claude_md
174
175
  setup_mcp
@@ -185,7 +186,7 @@ ralph_setup() {
185
186
  echo " --- Terminal 1: Claude Code ---"
186
187
  echo " claude --dangerously-skip-permissions"
187
188
  echo " /tour # Guided walkthrough"
188
- echo " /idea 'your feature' # Generate a PRD"
189
+ echo " /prd 'your feature' # Generate a PRD"
189
190
  echo ""
190
191
  echo " --- Terminal 2: Ralph Loop ---"
191
192
  echo " npx agentic-loop run # Execute PRDs autonomously"
@@ -197,7 +198,7 @@ setup_ralph_dir() {
197
198
  local pkg_root="$1"
198
199
 
199
200
  echo "Creating .ralph/ directory..."
200
- mkdir -p ".ralph/archive" ".ralph/screenshots"
201
+ mkdir -p ".ralph/archive" ".ralph/screenshots" ".ralph/hooks"
201
202
 
202
203
  # Copy config template based on detected project type
203
204
  if [[ ! -f ".ralph/config.json" ]]; then
@@ -401,26 +402,34 @@ setup_claude_hooks() {
401
402
  inject_context=$(_resolve_hook "inject-context.sh")
402
403
  save_learnings=$(_resolve_hook "save-learnings.sh")
403
404
 
405
+ # Wrap hook path with existence check so missing files don't produce errors.
406
+ # If the hook script is deleted after setup (git clean, etc.), this prevents
407
+ # "No such file or directory" errors on every Claude session end.
408
+ _safe_cmd() {
409
+ local path="$1"
410
+ printf 'if [ -f "%s" ]; then "%s"; else echo '"'"'{"continue": true}'"'"'; fi' "$path" "$path"
411
+ }
412
+
404
413
  # Build hooks arrays using jq for proper JSON
405
414
  local post_edit_hooks post_all_hooks session_start_hooks stop_hooks
406
415
 
407
416
  # PostToolUse: warn-* hooks on Edit|Write
408
417
  post_edit_hooks="[]"
409
418
  for hook_path in "$warn_debug" "$warn_secrets" "$warn_urls" "$warn_empty_catch"; do
410
- [[ -n "$hook_path" ]] && post_edit_hooks=$(echo "$post_edit_hooks" | jq --arg cmd "$hook_path" '. + [{"type": "command", "command": $cmd, "timeout": 5}]')
419
+ [[ -n "$hook_path" ]] && post_edit_hooks=$(echo "$post_edit_hooks" | jq --arg cmd "$(_safe_cmd "$hook_path")" '. + [{"type": "command", "command": $cmd, "timeout": 5}]')
411
420
  done
412
421
 
413
422
  # PostToolUse: log-tools on all
414
423
  post_all_hooks="[]"
415
- [[ -n "$log_tools" ]] && post_all_hooks=$(jq -n --arg cmd "$log_tools" '[{"type": "command", "command": $cmd, "timeout": 3}]')
424
+ [[ -n "$log_tools" ]] && post_all_hooks=$(jq -n --arg cmd "$(_safe_cmd "$log_tools")" '[{"type": "command", "command": $cmd, "timeout": 3}]')
416
425
 
417
426
  # SessionStart: inject-context
418
427
  session_start_hooks="[]"
419
- [[ -n "$inject_context" ]] && session_start_hooks=$(jq -n --arg cmd "$inject_context" '[{"type": "command", "command": $cmd, "timeout": 5}]')
428
+ [[ -n "$inject_context" ]] && session_start_hooks=$(jq -n --arg cmd "$(_safe_cmd "$inject_context")" '[{"type": "command", "command": $cmd, "timeout": 5}]')
420
429
 
421
430
  # Stop: save-learnings
422
431
  stop_hooks="[]"
423
- [[ -n "$save_learnings" ]] && stop_hooks=$(jq -n --arg cmd "$save_learnings" '[{"type": "command", "command": $cmd, "timeout": 10}]')
432
+ [[ -n "$save_learnings" ]] && stop_hooks=$(jq -n --arg cmd "$(_safe_cmd "$save_learnings")" '[{"type": "command", "command": $cmd, "timeout": 10}]')
424
433
 
425
434
  # Build the complete hooks config
426
435
  local hooks_config
@@ -446,6 +455,106 @@ setup_claude_hooks() {
446
455
  echo " Configured .claude/settings.json"
447
456
  }
448
457
 
458
+ # Install claude-hud statusline plugin for Claude Code
459
+ setup_claude_hud() {
460
+ local hud_dir="$HOME/.claude/plugins/claude-hud"
461
+ local marketplaces_file="$HOME/.claude/plugins/known_marketplaces.json"
462
+
463
+ # Check dependencies
464
+ if ! command -v git &>/dev/null; then
465
+ print_warning "git not found — skipping claude-hud install"
466
+ return 0
467
+ fi
468
+ if ! command -v node &>/dev/null; then
469
+ print_warning "node not found — skipping claude-hud install"
470
+ return 0
471
+ fi
472
+ if ! command -v jq &>/dev/null; then
473
+ print_warning "jq not found — skipping claude-hud install"
474
+ return 0
475
+ fi
476
+
477
+ echo "Setting up claude-hud statusline..."
478
+
479
+ # Clone if not already present
480
+ if [[ ! -d "$hud_dir" ]]; then
481
+ mkdir -p "$HOME/.claude/plugins"
482
+ if ! git clone --depth 1 https://github.com/jarrodwatts/claude-hud.git "$hud_dir" 2>/dev/null; then
483
+ print_warning "Failed to clone claude-hud — skipping"
484
+ return 0
485
+ fi
486
+ echo " Cloned claude-hud plugin"
487
+ else
488
+ echo " claude-hud already installed"
489
+ fi
490
+
491
+ # Build if dist/index.js is missing
492
+ if [[ ! -f "$hud_dir/dist/index.js" ]]; then
493
+ echo " Building claude-hud..."
494
+ if ! (cd "$hud_dir" && npm ci --production=false --ignore-scripts 2>/dev/null && npm run build 2>/dev/null); then
495
+ print_warning "Failed to build claude-hud — statusline may not work"
496
+ echo " Try manually: cd $hud_dir && npm ci && npm run build"
497
+ return 0
498
+ fi
499
+ echo " Built successfully"
500
+ fi
501
+
502
+ # Register in known_marketplaces.json
503
+ if [[ -f "$marketplaces_file" ]]; then
504
+ if ! jq -e '."claude-hud"' "$marketplaces_file" > /dev/null 2>&1; then
505
+ local tmp
506
+ tmp=$(mktemp)
507
+ jq '."claude-hud" = {
508
+ "source": {
509
+ "source": "github",
510
+ "repo": "jarrodwatts/claude-hud"
511
+ },
512
+ "installLocation": ($ENV.HOME + "/.claude/plugins/claude-hud"),
513
+ "lastUpdated": (now | todate)
514
+ }' "$marketplaces_file" > "$tmp" && mv "$tmp" "$marketplaces_file"
515
+ echo " Registered in known_marketplaces.json"
516
+ fi
517
+ else
518
+ mkdir -p "$(dirname "$marketplaces_file")"
519
+ jq -n '{
520
+ "claude-hud": {
521
+ "source": {
522
+ "source": "github",
523
+ "repo": "jarrodwatts/claude-hud"
524
+ },
525
+ "installLocation": ($ENV.HOME + "/.claude/plugins/claude-hud"),
526
+ "lastUpdated": (now | todate)
527
+ }
528
+ }' > "$marketplaces_file"
529
+ echo " Created known_marketplaces.json with claude-hud"
530
+ fi
531
+
532
+ # Write agentic-loop-tuned config
533
+ cat > "$hud_dir/config.json" << 'EOF'
534
+ {
535
+ "lineLayout": "expanded",
536
+ "pathLevels": 2,
537
+ "gitStatus": {
538
+ "enabled": true,
539
+ "showDirty": true,
540
+ "showAheadBehind": true
541
+ },
542
+ "display": {
543
+ "showContextBar": true,
544
+ "contextValue": "percent",
545
+ "showModel": true,
546
+ "showDuration": true,
547
+ "showSpeed": false,
548
+ "showUsage": true,
549
+ "showTools": true,
550
+ "showAgents": true,
551
+ "showTodos": true
552
+ }
553
+ }
554
+ EOF
555
+ echo " Configured claude-hud for agentic-loop (expanded layout, all panels)"
556
+ }
557
+
449
558
  # Copy slash commands (skills format for Claude Code)
450
559
  setup_slash_commands() {
451
560
  local pkg_root="$1"
@@ -569,6 +678,8 @@ ${python_runner:+- Python: Use \`$python_runner\` (not bare \`python\`)}
569
678
  framework_template="$pkg_root/templates/examples/CLAUDE-${framework_type}.md"
570
679
  fi
571
680
 
681
+ local style_marker="<!-- vibe-and-thrive-detected -->"
682
+
572
683
  if [[ -f "CLAUDE.md" ]]; then
573
684
  # Append framework template if it exists and not already included
574
685
  if [[ -n "$framework_template" && -f "$framework_template" ]]; then
@@ -581,6 +692,16 @@ ${python_runner:+- Python: Use \`$python_runner\` (not bare \`python\`)}
581
692
  echo " Appended ${framework_type} conventions to CLAUDE.md"
582
693
  fi
583
694
  fi
695
+ # Add writing style defaults if not already present
696
+ if ! grep -q "$style_marker" "CLAUDE.md" 2>/dev/null; then
697
+ cat >> CLAUDE.md << EOF
698
+
699
+ $style_marker
700
+ ## Writing Style
701
+ - Active voice only. Never use passive voice.
702
+ - Never use em dashes. Use commas, periods, or parentheses instead.
703
+ EOF
704
+ fi
584
705
  echo "$detected_section" >> CLAUDE.md
585
706
  echo " Updated CLAUDE.md"
586
707
  else
@@ -590,6 +711,11 @@ ${python_runner:+- Python: Use \`$python_runner\` (not bare \`python\`)}
590
711
 
591
712
  ## Your Rules
592
713
  <!-- Add your project-specific rules, patterns, and conventions here -->
714
+
715
+ <!-- vibe-and-thrive-detected -->
716
+ ## Writing Style
717
+ - Active voice only. Never use passive voice.
718
+ - Never use em dashes. Use commas, periods, or parentheses instead.
593
719
  EOF
594
720
 
595
721
  # Include framework template if available
@@ -715,20 +841,28 @@ setup_precommit_hooks() {
715
841
  # Create .pre-commit-config.yaml if it doesn't exist
716
842
  if [[ ! -f ".pre-commit-config.yaml" ]]; then
717
843
  cat > .pre-commit-config.yaml << 'EOF'
844
+ # Pre-commit hooks powered by agentic-loop vibe-check
845
+ # Runs on staged files before each commit
718
846
  repos:
719
- - repo: https://github.com/allierays/agentic-loop
720
- rev: v1.0.0
847
+ - repo: local
848
+ hooks:
849
+ - id: vibe-check
850
+ name: Vibe Check
851
+ entry: npx vibe-check --fail-on error
852
+ language: node
853
+ types_or: [javascript, ts, python, json]
854
+ pass_filenames: true
855
+
856
+ # Standard hooks
857
+ - repo: https://github.com/pre-commit/pre-commit-hooks
858
+ rev: v4.5.0
721
859
  hooks:
722
- - id: backup-db
723
- name: Backup database before commit
724
- - id: check-secrets
725
- name: Check for hardcoded secrets
726
- - id: check-hardcoded-urls
727
- name: Check for hardcoded URLs
728
- - id: check-debug
729
- name: Check for debug statements
730
- - id: check-signs-secrets
731
- name: Check signs.json for credentials
860
+ - id: trailing-whitespace
861
+ exclude: '\.txt$'
862
+ - id: end-of-file-fixer
863
+ exclude: '\.txt$'
864
+ - id: check-yaml
865
+ - id: check-added-large-files
732
866
  EOF
733
867
  echo " Created .pre-commit-config.yaml"
734
868
  else
package/ralph/utils.sh CHANGED
@@ -34,6 +34,7 @@ readonly BROWSER_PAGE_TIMEOUT_MS=30000
34
34
  readonly CURL_TIMEOUT_SECONDS=10
35
35
  readonly SIGN_EXTRACTION_TIMEOUT_SECONDS=30
36
36
  readonly PREFLIGHT_CACHE_TTL_SECONDS=600
37
+ readonly UPDATE_CHECK_TTL_SECONDS=86400 # Check for updates once per day
37
38
 
38
39
  # Common project directories (avoid duplication across files)
39
40
  readonly FRONTEND_DIRS=("apps/web" "frontend" "client" "web")
@@ -49,6 +50,51 @@ YELLOW='\033[1;33m'
49
50
  BLUE='\033[0;34m'
50
51
  NC='\033[0m' # No Color
51
52
 
53
+ # Terminal background theming for Ralph
54
+ # Saves original background color and applies a teal tint to visually
55
+ # distinguish the Ralph terminal from Claude Code.
56
+ # Only works in macOS Terminal.app — no-op on Linux, iTerm2, VS Code, etc.
57
+ # Note: AppleScript targets "front window" (focused window) not the specific
58
+ # window running the script. In practice this works because the user just
59
+ # ran the command, but rapid window switching can cause a mismatch.
60
+ _ORIGINAL_TERMINAL_BG=""
61
+
62
+ set_terminal_bg() {
63
+ local hex="${1:-#1a2e2e}" # Default: subtle dark teal
64
+
65
+ # Only works in Terminal.app
66
+ [[ "$TERM_PROGRAM" != "Apple_Terminal" ]] && return 0
67
+
68
+ # Save current background color for restore
69
+ _ORIGINAL_TERMINAL_BG=$(osascript -e 'tell application "Terminal" to get background color of front window' 2>/dev/null) || return 0
70
+
71
+ # Parse hex to 16-bit RGB values (Terminal.app uses 0-65535 range)
72
+ local r=$((16#${hex:1:2} * 257))
73
+ local g=$((16#${hex:3:2} * 257))
74
+ local b=$((16#${hex:5:2} * 257))
75
+
76
+ osascript -e "tell application \"Terminal\" to set background color of front window to {$r, $g, $b}" 2>/dev/null || true
77
+ }
78
+
79
+ restore_terminal_bg() {
80
+ [[ -z "$_ORIGINAL_TERMINAL_BG" ]] && return 0
81
+ [[ "$TERM_PROGRAM" != "Apple_Terminal" ]] && return 0
82
+
83
+ osascript -e "tell application \"Terminal\" to set background color of front window to {$_ORIGINAL_TERMINAL_BG}" 2>/dev/null || true
84
+ _ORIGINAL_TERMINAL_BG=""
85
+ }
86
+
87
+ # Set terminal tab title (works in Terminal.app, iTerm2, and most xterm-compatible terminals)
88
+ set_tab_title() {
89
+ local title="$1"
90
+ printf '\033]0;%s\007' "$title"
91
+ }
92
+
93
+ # Restore tab title to default (empty = terminal decides)
94
+ restore_tab_title() {
95
+ printf '\033]0;\007'
96
+ }
97
+
52
98
  # Get existing frontend directories in this project
53
99
  get_frontend_dirs() {
54
100
  local dirs=()
@@ -410,6 +456,8 @@ create_temp_file() {
410
456
 
411
457
  # Clean up only tracked temp files on exit
412
458
  cleanup() {
459
+ restore_tab_title
460
+ restore_terminal_bg
413
461
  if [[ ${#RALPH_TEMP_FILES[@]} -gt 0 ]]; then
414
462
  for f in "${RALPH_TEMP_FILES[@]}"; do
415
463
  rm -f "$f" 2>/dev/null
@@ -249,9 +249,9 @@ make typecheck # Check TypeScript types
249
249
 
250
250
  ## Workflow
251
251
 
252
- 1. **Idea** - Run `/idea "feature description"` to brainstorm
253
- 2. **Approve** - Review the idea file, then approve
254
- 3. **PRD** - Review generated stories in `.ralph/prd.json`
252
+ 1. **PRD** - Run `/prd "feature description"` to generate stories
253
+ 2. **Review** - Review generated stories in `.ralph/prd.json`
254
+ 3. **Validate** - PRD validation checks story coherence
255
255
  4. **Run** - Execute `ralph run` for autonomous coding
256
256
  5. **Audit** - Run `/vibe-check` before shipping
257
257
  6. **Commit** - Pre-commit hooks catch remaining issues
@@ -48,6 +48,13 @@
48
48
  "category": "testing",
49
49
  "learnedFrom": null,
50
50
  "createdAt": "2026-01-25T00:00:00-08:00"
51
+ },
52
+ {
53
+ "id": "sign-008",
54
+ "pattern": "NEVER use import-checks as test steps (python -c 'from X import Y', hasattr, test -f). These only verify a symbol exists, not that it works. A function that raises on every call still passes an import check. Always use real behavioral tests: curl for API endpoints, pytest with actual function calls, or Playwright for UI.",
55
+ "category": "testing",
56
+ "learnedFrom": "Okta SSO PRD review — all 4 backend stories had import-check testSteps that would pass even with completely broken code",
57
+ "createdAt": "2026-02-17T00:00:00-08:00"
51
58
  }
52
59
  ]
53
60
  }