## Funcționalități Principale ### Bulk Upload & Processing - Drag & drop pentru upload bonuri multiple oriunde pe pagină - Batch processing cu job queue și worker pool - Real-time updates via SSE (Server-Sent Events) cu fallback polling - Duplicate detection via SHA-256 file hash - Auto-retry pentru job-uri failed - Cancel individual jobs sau batch complet ### Mobile UX - Android Native Style - Top bar fixă cu hamburger, titlu centrat, acțiuni (search/filter) - Bottom navigation cu 4 tab-uri (Bonuri, Upload, Rapoarte, Setări) - FAB (Floating Action Button) cu hide/show on scroll - Filter chips orizontal scrollabile - Selecție multiplă prin long-press (500ms) - Select All + Bulk Delete cu confirmare - Layout Android pentru Create/Edit/View bon (Gmail compose style) ### Bug Fixes - Refresh individual via SSE în loc de refresh total pagină - Bonurile cu eroare OCR rămân vizibile pentru editare manuală - Afișare nume fișier original pentru toate bonurile - Upload stabil pe mobil (fix race condition File API) - Păstrare ordine bonuri la refresh (nu se reordonează) ### Backend - SSE endpoint pentru status updates real-time - Bulk delete endpoint cu partial success - Auto-cleanup bonuri failed după 7 zile - Batch model cu tracking complet ### Testing - E2E tests cu Playwright - Unit tests pentru bulk upload, auto-create, cleanup ## Commits Squashed: 43 user stories (US-001 → US-043) ## Branch: ralph/bulk-receipt-upload ## Timp dezvoltare: ~3 zile (Ralph autonomous) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
204 lines
5.7 KiB
Bash
Executable File
204 lines
5.7 KiB
Bash
Executable File
#!/bin/bash
|
|
# Ralph - Autonomous Loop for PRD Implementation
|
|
# Usage: ./ralph.sh [max_iterations]
|
|
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
PRD_FILE="$SCRIPT_DIR/prd.json"
|
|
PROGRESS_FILE="$SCRIPT_DIR/progress.txt"
|
|
PROMPT_FILE="$SCRIPT_DIR/prompt.md"
|
|
LOG_DIR="$SCRIPT_DIR/logs"
|
|
|
|
MAX_ITERATIONS=${1:-50}
|
|
ITERATION=0
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
log() {
|
|
echo -e "${BLUE}[Ralph]${NC} $1"
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$PROGRESS_FILE"
|
|
}
|
|
|
|
error() {
|
|
echo -e "${RED}[Ralph ERROR]${NC} $1"
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$PROGRESS_FILE"
|
|
}
|
|
|
|
success() {
|
|
echo -e "${GREEN}[Ralph]${NC} $1"
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$PROGRESS_FILE"
|
|
}
|
|
|
|
# Check prerequisites
|
|
if [ ! -f "$PRD_FILE" ]; then
|
|
error "prd.json not found at $PRD_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v claude &> /dev/null; then
|
|
error "claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v jq &> /dev/null; then
|
|
error "jq not found. Install with: apt install jq"
|
|
exit 1
|
|
fi
|
|
|
|
# Create log directory
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
# Get project info
|
|
PROJECT_NAME=$(jq -r '.projectName' "$PRD_FILE")
|
|
BRANCH_NAME=$(jq -r '.branchName' "$PRD_FILE")
|
|
|
|
log "Starting Ralph for project: $PROJECT_NAME"
|
|
log "Max iterations: $MAX_ITERATIONS"
|
|
|
|
# Create branch if not exists
|
|
cd "$PROJECT_ROOT"
|
|
CURRENT_BRANCH=$(git branch --show-current)
|
|
if [ "$CURRENT_BRANCH" != "$BRANCH_NAME" ]; then
|
|
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
|
|
log "Switching to existing branch: $BRANCH_NAME"
|
|
git checkout "$BRANCH_NAME"
|
|
else
|
|
log "Creating new branch: $BRANCH_NAME"
|
|
git checkout -b "$BRANCH_NAME"
|
|
fi
|
|
fi
|
|
|
|
# Function to get next pending story
|
|
get_next_story() {
|
|
jq -r '.userStories | map(select(.passes == false)) | sort_by(.priority) | .[0] | .id // empty' "$PRD_FILE"
|
|
}
|
|
|
|
# Function to get story details
|
|
get_story_details() {
|
|
local story_id=$1
|
|
jq -r --arg id "$story_id" '.userStories[] | select(.id == $id)' "$PRD_FILE"
|
|
}
|
|
|
|
# Function to mark story as passed
|
|
mark_story_passed() {
|
|
local story_id=$1
|
|
local notes=$2
|
|
local tmp_file=$(mktemp)
|
|
jq --arg id "$story_id" --arg notes "$notes" \
|
|
'(.userStories[] | select(.id == $id)) |= (.passes = true | .notes = $notes)' \
|
|
"$PRD_FILE" > "$tmp_file" && mv "$tmp_file" "$PRD_FILE"
|
|
}
|
|
|
|
# Function to count stories
|
|
count_stories() {
|
|
local total=$(jq '.userStories | length' "$PRD_FILE")
|
|
local passed=$(jq '[.userStories[] | select(.passes == true)] | length' "$PRD_FILE")
|
|
echo "$passed/$total"
|
|
}
|
|
|
|
# Main loop
|
|
while [ $ITERATION -lt $MAX_ITERATIONS ]; do
|
|
ITERATION=$((ITERATION + 1))
|
|
|
|
log "=== Iteration $ITERATION/$MAX_ITERATIONS ==="
|
|
|
|
# Get next story
|
|
NEXT_STORY=$(get_next_story)
|
|
|
|
if [ -z "$NEXT_STORY" ]; then
|
|
success "All stories completed! 🎉"
|
|
break
|
|
fi
|
|
|
|
log "Working on story: $NEXT_STORY"
|
|
|
|
# Get story details for prompt
|
|
STORY_JSON=$(get_story_details "$NEXT_STORY")
|
|
STORY_TITLE=$(echo "$STORY_JSON" | jq -r '.title')
|
|
|
|
# Create iteration prompt
|
|
ITERATION_PROMPT="You are implementing user story $NEXT_STORY: $STORY_TITLE
|
|
|
|
Read the full PRD at scripts/ralph/prd.json for context and CSS rules.
|
|
|
|
Story details:
|
|
$STORY_JSON
|
|
|
|
IMPORTANT CSS RULES (from PRD):
|
|
- NEVER use hardcoded values - always use design tokens
|
|
- Check docs/DESIGN_TOKENS.md and docs/CSS_PATTERNS.md before writing CSS
|
|
- Test in BOTH light and dark mode
|
|
- NEVER use :deep() in components
|
|
|
|
Your task:
|
|
1. Implement this story following all acceptance criteria
|
|
2. Run tests/typecheck to verify
|
|
3. If ALL criteria pass, respond with: STORY_PASSED
|
|
4. If blocked or need clarification, respond with: STORY_BLOCKED: <reason>
|
|
|
|
Do NOT move to other stories. Focus only on $NEXT_STORY."
|
|
|
|
# Run Claude
|
|
LOG_FILE="$LOG_DIR/iteration_${ITERATION}_${NEXT_STORY}.log"
|
|
log "Running Claude... (log: $LOG_FILE)"
|
|
|
|
# Run claude with the prompt (--output-format json avoids streaming mode issues)
|
|
CLAUDE_OUTPUT=$(cd "$PROJECT_ROOT" && claude -p "$ITERATION_PROMPT" --output-format json 2>&1 | tee "$LOG_FILE")
|
|
|
|
# Check result
|
|
if echo "$CLAUDE_OUTPUT" | grep -q "STORY_PASSED"; then
|
|
success "Story $NEXT_STORY passed!"
|
|
mark_story_passed "$NEXT_STORY" "Completed in iteration $ITERATION"
|
|
|
|
# Commit changes
|
|
cd "$PROJECT_ROOT"
|
|
if [ -n "$(git status --porcelain)" ]; then
|
|
git add -A
|
|
git commit -m "feat($PROJECT_NAME): Complete $NEXT_STORY - $STORY_TITLE
|
|
|
|
Implemented by Ralph autonomous loop.
|
|
Iteration: $ITERATION
|
|
|
|
Co-Authored-By: Claude <noreply@anthropic.com>"
|
|
log "Changes committed"
|
|
fi
|
|
|
|
elif echo "$CLAUDE_OUTPUT" | grep -q "STORY_BLOCKED"; then
|
|
BLOCK_REASON=$(echo "$CLAUDE_OUTPUT" | grep "STORY_BLOCKED" | sed 's/STORY_BLOCKED://')
|
|
error "Story $NEXT_STORY blocked: $BLOCK_REASON"
|
|
log "Stopping loop due to blocked story"
|
|
break
|
|
else
|
|
log "Story $NEXT_STORY not yet complete, continuing..."
|
|
fi
|
|
|
|
# Progress update
|
|
PROGRESS=$(count_stories)
|
|
log "Progress: $PROGRESS stories completed"
|
|
|
|
# Small delay between iterations
|
|
sleep 2
|
|
done
|
|
|
|
# Final summary
|
|
echo ""
|
|
log "=== Ralph Session Complete ==="
|
|
PROGRESS=$(count_stories)
|
|
log "Final progress: $PROGRESS stories completed"
|
|
log "Branch: $BRANCH_NAME"
|
|
log "Logs: $LOG_DIR"
|
|
|
|
# Show remaining stories
|
|
REMAINING=$(jq -r '[.userStories[] | select(.passes == false)] | length' "$PRD_FILE")
|
|
if [ "$REMAINING" -gt 0 ]; then
|
|
echo -e "\n${YELLOW}Remaining stories:${NC}"
|
|
jq -r '.userStories[] | select(.passes == false) | " - \(.id): \(.title)"' "$PRD_FILE"
|
|
fi
|