Ralph iteration 1: work in progress
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# Implementation Plan — React Conversion
|
||||
|
||||
## Project Overview
|
||||
|
||||
Convert the completed `concept.html` (ECG Heartbeat CV Website) into a modern React application with TypeScript, Vite, and Tailwind CSS. The project will be a portfolio-grade React implementation that preserves all animations, interactions, and design details from the HTML concept while following React best practices.
|
||||
|
||||
**Key Features to Port:**
|
||||
- Boot sequence with terminal typing animation
|
||||
- ECG flatline and heartbeat SVG animations
|
||||
- Branching lines that trace UI elements into existence
|
||||
- Color transition from green ECG to teal/coral design system
|
||||
- Floating pill navigation with active section tracking
|
||||
- SVG circular skill gauges with scroll-triggered animations
|
||||
- Experience timeline with ECG decoration
|
||||
- Scroll-reveal animations using IntersectionObserver
|
||||
- Fully responsive design (desktop/tablet/mobile)
|
||||
|
||||
**Tech Stack:**
|
||||
- React 18+ with TypeScript
|
||||
- Vite for build tooling
|
||||
- Tailwind CSS for styling
|
||||
- Framer Motion for complex animations (boot sequence, ECG transitions)
|
||||
- React Intersection Observer for scroll-triggered animations
|
||||
- Lucide React for icons (replacing unicode symbols)
|
||||
|
||||
**Project Structure:**
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── BootSequence.tsx # Terminal typing animation
|
||||
│ ├── ECGAnimation.tsx # Flatline, heartbeats, branching
|
||||
│ ├── FloatingNav.tsx # Pill navigation with active tracking
|
||||
│ ├── Hero.tsx # About section with vitals
|
||||
│ ├── Skills.tsx # Skill gauges with SVG circles
|
||||
│ ├── Experience.tsx # Timeline layout
|
||||
│ ├── Education.tsx # Education cards
|
||||
│ ├── Projects.tsx # Project cards with gradient borders
|
||||
│ ├── Contact.tsx # Contact grid
|
||||
│ └── Footer.tsx # Footer with ECG decoration
|
||||
├── hooks/
|
||||
│ ├── useScrollReveal.ts # IntersectionObserver for scroll animations
|
||||
│ └── useActiveSection.ts # Track active nav section
|
||||
├── lib/
|
||||
│ └── utils.ts # Utility functions (skill gauge math)
|
||||
├── types/
|
||||
│ └── index.ts # TypeScript interfaces
|
||||
├── App.tsx # Main app with boot/ECG/CV phases
|
||||
├── main.tsx # Entry point
|
||||
└── index.css # Tailwind + custom CSS variables
|
||||
```
|
||||
|
||||
**Reference Materials:**
|
||||
- `References/concept.html` — Complete working HTML implementation with all animations
|
||||
- `References/CV_v4.md` — Source CV content to populate sections
|
||||
- `References/ECGVideo/` — Remotion video project with ECG animation patterns
|
||||
|
||||
## Quality Checks
|
||||
|
||||
- `npm run dev` — Development server starts without errors
|
||||
- `npm run build` — Production build completes without errors
|
||||
- `npm run lint` — No ESLint errors
|
||||
- `npm run typecheck` — No TypeScript errors
|
||||
- Open `http://localhost:5173` and verify:
|
||||
- Boot sequence plays exactly as in concept.html (terminal typing, 4 second duration)
|
||||
- ECG flatline draws left-to-right
|
||||
- Three heartbeats animate with increasing amplitude
|
||||
- Branching lines trace outward on third beat
|
||||
- Background transitions from black to white
|
||||
- Final CV design renders with all sections
|
||||
- Floating pill nav tracks active section on scroll
|
||||
- Skill gauges animate when scrolled into view
|
||||
- All hover effects work (card elevation, gradient borders)
|
||||
- Responsive layouts work at 768px and 480px
|
||||
- No console errors
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] **Task 1: Initialize React project with Vite + TypeScript + Tailwind**
|
||||
|
||||
Run `npm create vite@latest . -- --template react-ts` to scaffold the project. Install dependencies: `npm install framer-motion lucide-react`. Initialize Tailwind: `npm install -D tailwindcss postcss autoprefixer && npx tailwindcss init -p`. Configure `tailwind.config.js` with custom colors (teal #00897B, coral #FF6B6B, etc.). Set up `src/index.css` with Tailwind directives and CSS custom properties matching concept.html.
|
||||
|
||||
- [ ] **Task 2: Set up project structure and types**
|
||||
|
||||
Create the folder structure (`components/`, `hooks/`, `lib/`, `types/`). Define TypeScript interfaces in `types/index.ts` for: `Skill` (name, level, category, color), `Experience` (role, org, date, bullets), `Education` (degree, institution, period, detail), `Project` (title, description, link?). Create `lib/utils.ts` with helper function `calculateSkillOffset(level: number, radius: number): number` that returns `2 * Math.PI * radius * (1 - level / 100)`.
|
||||
|
||||
- [ ] **Task 3: Build BootSequence component**
|
||||
|
||||
Create `components/BootSequence.tsx`. Implement terminal typing animation using Framer Motion or CSS transitions. Display boot lines with correct colors (cyan labels, green values, dim text). Use exact boot text from concept.html: "CLINICAL TERMINAL v3.2.1", "Initialising pharmacist profile...", SYSTEM/USER/ROLE/LOCATION, module loading, [OK] lines, READY. Duration: ~4 seconds. Emit `onComplete` callback when finished. Styling: black background, Fira Code font.
|
||||
|
||||
- [ ] **Task 4: Build ECGAnimation component**
|
||||
|
||||
Create `components/ECGAnimation.tsx`. Port the ECG logic from concept.html:
|
||||
- SVG flatline drawing left-to-right (1000ms)
|
||||
- Three PQRST heartbeats with increasing amplitude (40px → 60px → 100px)
|
||||
- Color interpolation: #00ff41 → #00C9A7 → #00897B
|
||||
- Branching lines from third R peak tracing UI outlines (pill nav, hero, cards)
|
||||
- Background transition from black to white
|
||||
- Emit `onComplete` callback when animation finishes
|
||||
Use Framer Motion for path drawing animations (pathLength).
|
||||
|
||||
- [ ] **Task 5: Build FloatingNav component**
|
||||
|
||||
Create `components/FloatingNav.tsx`. Floating pill navigation bar fixed at top center. Links: About, Skills, Experience, Education, Projects, Contact. Active link tracking via `useActiveSection` hook (IntersectionObserver). Smooth scroll to sections on click. Responsive: horizontal scroll on mobile. Styling: white bg, rounded-full, shadow-md, teal active state with dot indicator.
|
||||
|
||||
- [ ] **Task 6: Build Hero section component**
|
||||
|
||||
Create `components/Hero.tsx`. Port hero section from concept.html: centered layout, name (clamp 36-52px), job title (muted), location pill (teal border), summary paragraph (max-width 560px). Four vital sign metric cards in a row: "10+ Years Experience", "Python/SQL/BI Analytics Stack", "Pop. Health Focus Area", "NHS N&W System". Cards have teal border-top, hover elevation. Responsive: 2x2 grid on tablet, stacked on mobile.
|
||||
|
||||
- [ ] **Task 7: Build Skills section with SVG gauges**
|
||||
|
||||
Create `components/Skills.tsx`. Three skill categories: Technical (8 skills, teal), Clinical (6 skills, coral), Strategic (4 skills, teal). Each skill has circular SVG progress gauge using calculated stroke-dashoffset. Scroll-triggered animation: gauges fill when section enters viewport, staggered by 100ms. Port all 18 skills with correct percentages from concept.html.
|
||||
|
||||
- [ ] **Task 8: Build Experience section with timeline**
|
||||
|
||||
Create `components/Experience.tsx`. Vertical timeline with 5 roles: Interim Head (May-Nov 2025), Deputy Head (Jul 2024-Present), High-Cost Drugs (May 2022-Jul 2024), Pharmacy Manager (Nov 2017-May 2022), Duty Pharmacy Manager (Aug 2016-Nov 2017). Decorative ECG waveform SVG beside heading. Timeline dot filled for current roles. Cards with hover effect (scale, shadow, left border). Responsive: hide timeline line on mobile, stack cards.
|
||||
|
||||
- [ ] **Task 9: Build Education, Projects, Contact sections**
|
||||
|
||||
Create `components/Education.tsx`, `components/Projects.tsx`, `components/Contact.tsx`.
|
||||
|
||||
**Education:** 2-column grid. MPharm (Hons) UEA 2011-2015 (2:1). Mary Seacole Leadership Programme 2018. Gradient top border (teal→coral). A-Levels line below.
|
||||
|
||||
**Projects:** 2x2 grid. PharMetrics (with link), Patient Pathway Analysis, Blueteq Generator, NMS Video. Gradient border hover effect.
|
||||
|
||||
**Contact:** 4-column grid. Phone, Email, LinkedIn, Location. Use Lucide icons (Phone, Mail, Linkedin, MapPin). Responsive: 2x2 on mobile.
|
||||
|
||||
- [ ] **Task 10: Build Footer component and main App.tsx**
|
||||
|
||||
Create `components/Footer.tsx`. Decorative ECG waveform SVG, attribution text. Update `App.tsx` to orchestrate the three phases: 1) BootSequence (4s), 2) ECGAnimation (4s), 3) CV Content (with all sections). Use React state to track current phase. Ensure smooth transitions between phases.
|
||||
|
||||
- [ ] **Task 11: Implement scroll animations and responsive design**
|
||||
|
||||
Create `hooks/useScrollReveal.ts`. IntersectionObserver-based hook for scroll-triggered section reveals. Add scroll-reveal animations to all sections (opacity 0→1, translateY 24px→0). Ensure animations only trigger once. Add responsive breakpoints: tablet (768px), mobile (480px). Test all layouts.
|
||||
|
||||
- [ ] **Task 12: Final integration, testing, and polish**
|
||||
|
||||
Run all quality checks. Verify TypeScript compiles without errors. Verify no console errors. Test boot sequence timing matches concept.html (~4s). Test ECG animation timing and easing. Verify all CV content accuracy against CV_v4.md. Test all interactive elements (nav, hover effects, scroll animations). Verify responsive layouts at all breakpoints. Final build test.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Ralph Wiggum Loop - Iteration Prompt
|
||||
|
||||
You are operating inside an automated loop. Each iteration you receive fresh context - you have NO memory of previous iterations. Your only persistence is the filesystem.
|
||||
|
||||
You are converting the completed `concept.html` (ECG Heartbeat CV Website) into a modern React application with TypeScript, Vite, and Tailwind CSS. The goal is a portfolio-grade React implementation that preserves all animations, interactions, and design details from the HTML concept.
|
||||
|
||||
## Your Task This Iteration
|
||||
|
||||
1. **Use the /frontend-design skill** (REQUIRED for visual components): Before writing ANY code for components that involve visual design, styling, animations, or UI elements, you MUST invoke the `/frontend-design` skill. This includes: BootSequence, ECGAnimation, FloatingNav, Hero, Skills, Experience, Education, Projects, Contact, Footer, and any component with CSS/styling. This skill gives you access to specialized frontend design capabilities for higher quality, polished output.
|
||||
|
||||
2. **Read the plan**: Open `IMPLEMENTATION_PLAN.md` and find the highest-priority unchecked item (`- [ ]`). Items are listed in priority order - pick the first unchecked one.
|
||||
|
||||
3. **Read accumulated learnings**: Open `progress.txt` and read the "Codebase Patterns" section. This contains learnings from previous iterations.
|
||||
|
||||
4. **Read guardrails**: Open `guardrails.md` and read ALL guardrails. These are hard rules you MUST follow. Violating a guardrail is a quality check failure.
|
||||
|
||||
5. **Implement the item**: Complete the single task you selected. Keep changes focused - one task per iteration. Write production-quality React/TypeScript code that is artistic, creative, and visually polished. This is a design showcase - the output should make someone say "wow, that's slick."
|
||||
|
||||
6. **Run quality checks**: Execute the quality check commands listed in `IMPLEMENTATION_PLAN.md` under "Quality Checks". Fix any issues before proceeding.
|
||||
|
||||
7. **Commit your changes**: Stage and commit all changes with a descriptive message referencing the task you completed.
|
||||
|
||||
8. **Mark the item complete**: In `IMPLEMENTATION_PLAN.md`, change the item from `- [ ]` to `- [x]`.
|
||||
|
||||
9. **Update progress.txt**: Append to the "Iteration Log" section with:
|
||||
- Which task you completed
|
||||
- Any learnings or codebase patterns discovered (add to "Codebase Patterns" section)
|
||||
- Any issues encountered
|
||||
- Design decisions made (if visual component)
|
||||
|
||||
10. **Commit the progress update**: Stage and commit the updated `IMPLEMENTATION_PLAN.md` and `progress.txt`.
|
||||
|
||||
11. **Check for completion**: If ALL items in the task checklist are now checked (`- [x]`), output the following completion signal on its own line:
|
||||
|
||||
```
|
||||
<promise>COMPLETE</promise>
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
|
||||
- **ALWAYS invoke /frontend-design skill before writing visual component code** — this is mandatory for BootSequence, ECGAnimation, FloatingNav, Hero, Skills, Experience, Education, Projects, Contact, Footer, and any styled component
|
||||
- **Only work on ONE task per iteration**
|
||||
- **Always read progress.txt AND guardrails.md before starting** — previous iterations may have left important context
|
||||
- **If a task is blocked or unclear**, document why in progress.txt and move to the next unchecked item
|
||||
- **Keep commits atomic and well-described**
|
||||
- **If quality checks fail, fix the issues before committing**
|
||||
- **The visual quality bar is HIGH** — this is a design portfolio piece
|
||||
- **Preserve all animations exactly** — timing, easing, and visual effects must match concept.html
|
||||
- **Use TypeScript strictly** — no `any` types, proper interfaces for all data structures
|
||||
- **Follow the established project structure** — components in `src/components/`, hooks in `src/hooks/`, etc.
|
||||
|
||||
## Reference Files
|
||||
|
||||
- `References/concept.html` — The complete working HTML implementation (your source of truth for animations, styling, timing)
|
||||
- `References/CV_v4.md` — CV content to populate sections
|
||||
- `References/ECGVideo/` — Remotion video project with ECG animation patterns
|
||||
@@ -0,0 +1,90 @@
|
||||
# Guardrails — React Conversion
|
||||
|
||||
## Standard Guardrails
|
||||
|
||||
### Frontend-design skill requirement
|
||||
- **When**: Writing ANY component with visual styling, animations, or UI elements
|
||||
- **Rule**: You MUST invoke the `/frontend-design` skill before writing code. This applies to: BootSequence, ECGAnimation, FloatingNav, Hero, Skills, Experience, Education, Projects, Contact, Footer, and any styled component.
|
||||
- **Why**: The frontend-design skill provides specialized capabilities for creating polished, professional-grade visual output. Skipping it results in lower quality design.
|
||||
|
||||
### Boot sequence consistency
|
||||
- **When**: Implementing BootSequence component
|
||||
- **Rule**: Boot text must match concept.html exactly: "CLINICAL TERMINAL v3.2.1", "Initialising pharmacist profile...", SYSTEM/USER/ROLE/LOCATION labels with values, loading modules line, three [OK] lines, "---", and final ready line. Use Fira Code font, green #00ff41 for [OK] and values, cyan #00e5ff for labels, dim green #3a6b45 for other text.
|
||||
- **Why**: Boot sequence is the shared identity across all concepts. Must be identical.
|
||||
|
||||
### ECG animation fidelity
|
||||
- **When**: Implementing ECGAnimation component
|
||||
- **Rule**: Timing and visual effects must match concept.html exactly: flatline 1000ms, three heartbeats with amplitudes 40px→60px→100px, color shift #00ff41→#00C9A7→#00897B, branching lines from third R peak, background transition black→white.
|
||||
- **Why**: The ECG animation is the signature visual effect. Any deviation breaks the experience.
|
||||
|
||||
### CV content accuracy
|
||||
- **When**: Adding CV content to components
|
||||
- **Rule**: Use the expanded CV_v4.md content. Key roles in order: Interim Head (May-Nov 2025), Deputy Head (Jul 2024-Present), High-Cost Drugs & Interface Pharmacist (May 2022-Jul 2024), Pharmacy Manager Tesco (Nov 2017-May 2022), Duty Pharmacy Manager Tesco (Aug 2016-Nov 2017). Include Mary Seacole Programme in education. Include key achievements with specific numbers (£14.6M, 14,000 patients, £2.6M, 70%, 200hrs, £1M).
|
||||
- **Why**: The CV data must be accurate and complete. Missing roles or wrong dates would be a critical error.
|
||||
|
||||
### TypeScript strictness
|
||||
- **When**: Writing any TypeScript code
|
||||
- **Rule**: No `any` types. Define interfaces for all data structures. Use proper React.FC types or function component signatures with typed props. Enable strict mode in tsconfig.json.
|
||||
- **Why**: Type safety is a core benefit of the React conversion. `any` defeats the purpose.
|
||||
|
||||
### Google Fonts loading
|
||||
- **When**: Setting up index.html
|
||||
- **Rule**: Use preconnect links to fonts.googleapis.com AND fonts.gstatic.com (with crossorigin), then the font CSS link. Load ALL fonts: Fira Code, Plus Jakarta Sans, Inter Tight. Test that fonts actually render.
|
||||
- **Why**: Fonts are critical to the design identity. Missing fonts break the visual concept.
|
||||
|
||||
### Transition timing
|
||||
- **When**: Building the boot-to-design transition
|
||||
- **Rule**: Boot phase should take ~4 seconds. ECG animation should take ~5-6 seconds. Total time from page load to fully revealed design: no more than 10 seconds.
|
||||
- **Why**: Too long and users will leave. Too short and the effect is lost.
|
||||
|
||||
### No console errors
|
||||
- **When**: Writing JavaScript/TypeScript
|
||||
- **Rule**: No errors in the browser console. Handle edge cases: elements that might not exist, animation cleanup on unmount, proper dependency arrays in hooks.
|
||||
- **Why**: Console errors suggest broken functionality and are a quality check failure.
|
||||
|
||||
### Responsive breakpoints
|
||||
- **When**: Adding responsive CSS/Tailwind classes
|
||||
- **Rule**: Must work at 3 breakpoints: desktop (>768px), tablet (<=768px), mobile (<=480px). Navigation must be usable at all sizes. Content must not overflow horizontally. Touch targets must be reasonable size.
|
||||
- **Why**: CVs are often viewed on mobile devices.
|
||||
|
||||
### Scroll animation observer
|
||||
- **When**: Implementing scroll-triggered animations
|
||||
- **Rule**: Use IntersectionObserver via custom hook (useScrollReveal), NOT scroll event listeners. Set appropriate threshold (0.1-0.15). Animations should only play once (don't re-trigger on scroll up).
|
||||
- **Why**: IntersectionObserver is more performant and reliable than scroll listeners.
|
||||
|
||||
### Tailwind CSS usage
|
||||
- **When**: Writing component styles
|
||||
- **Rule**: Use Tailwind utility classes for all styling. Only use inline styles or CSS modules for dynamic values that can't be expressed with Tailwind (e.g., stroke-dashoffset calculations). Extend Tailwind config for custom colors.
|
||||
- **Why**: Consistent styling approach, smaller bundle size, better maintainability.
|
||||
|
||||
## Project-Specific Guardrails
|
||||
|
||||
### Framer Motion for complex animations
|
||||
- **When**: Animating the boot sequence, ECG paths, branching lines
|
||||
- **Rule**: Use Framer Motion's `motion` components and props (initial, animate, transition). Use `pathLength` for SVG drawing animations. Use `AnimatePresence` for exit animations. Define transition objects with exact timing from concept.html.
|
||||
- **Why**: Framer Motion provides declarative, performant animations that are easier to maintain than imperative JS.
|
||||
|
||||
### Skill circle calculation
|
||||
- **When**: Building SVG circular progress gauges in Skills component
|
||||
- **Rule**: The circumference formula is `2 * Math.PI * radius`. `strokeDasharray = circumference`. `strokeDashoffset = circumference * (1 - level / 100)`. The circle MUST have `transform: rotate(-90deg)` to start progress from 12 o'clock position.
|
||||
- **Why**: Wrong math or missing rotation produces circles that fill from the wrong position or have incorrect percentages.
|
||||
|
||||
### Component file structure
|
||||
- **When**: Creating new components
|
||||
- **Rule**: One component per file in `src/components/`. Named exports for components. Props interface defined at top of file. Follow naming: PascalCase for components (BootSequence.tsx), camelCase for hooks (useScrollReveal.ts).
|
||||
- **Why**: Consistent organization makes the codebase maintainable.
|
||||
|
||||
### Lucide React icons
|
||||
- **When**: Adding icons to Contact or other sections
|
||||
- **Rule**: Use Lucide React icons instead of unicode symbols. Import specific icons: `import { Phone, Mail, Linkedin, MapPin } from 'lucide-react'`. Size icons consistently (default 24px or specified size prop).
|
||||
- **Why**: Lucide provides consistent, scalable SVG icons that match the design system.
|
||||
|
||||
### Custom hooks for reusable logic
|
||||
- **When**: Implementing scroll reveal, active section tracking
|
||||
- **Rule**: Extract reusable logic into custom hooks in `src/hooks/`. Hooks should be composable and return values/functions needed by components. Name with `use` prefix.
|
||||
- **Why**: Keeps components clean, enables reuse, follows React best practices.
|
||||
|
||||
### Vite configuration
|
||||
- **When**: Setting up the project build
|
||||
- **Rule**: Use Vite's default React template. Configure path aliases in `vite.config.ts` for clean imports (e.g., `@/components/Hero`). Ensure `build.outDir` is set correctly.
|
||||
- **Why**: Vite provides fast dev server and optimized production builds.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Progress Log — React Conversion Phase
|
||||
|
||||
## Codebase Patterns
|
||||
- **Source of truth**: `References/concept.html` contains the complete working HTML implementation. All animations, timing, colors, and styling must be preserved exactly when porting to React.
|
||||
- **Tech stack**: React 18+, TypeScript, Vite, Tailwind CSS, Framer Motion, Lucide React
|
||||
- **Project structure**: Components in `src/components/`, hooks in `src/hooks/`, types in `src/types/`, utilities in `src/lib/`
|
||||
- **Animation approach**: Framer Motion for complex sequences (boot, ECG), CSS transitions for simple hover effects, IntersectionObserver (via hook) for scroll-triggered animations
|
||||
- **SVG animations**: Use Framer Motion's `pathLength` prop for drawing effects, or CSS `stroke-dasharray`/`stroke-dashoffset` for skill gauges
|
||||
- **Skill gauge math**: `circumference = 2 * Math.PI * radius`, `strokeDashoffset = circumference * (1 - level / 100)`, rotate -90deg to start from top
|
||||
- **Boot sequence timing**: 14 lines × 220ms = ~3080ms, plus 400ms pause, 800ms fade = ~4.28s total
|
||||
- **ECG timing**: Flatline 1000ms + 3 beats × 600ms + holds 300ms + branching 1500ms + fade 500ms = ~5.5s
|
||||
- **Color palette**:
|
||||
- ECG phase: #000 (black), #00ff41 (green), #00e5ff (cyan), #3a6b45 (dim green)
|
||||
- Final design: #00897B (teal), #FF6B6B (coral), #0F172A (heading), #334155 (text), #94A3B8 (muted)
|
||||
- **Fonts**: Fira Code (boot), Plus Jakarta Sans (primary), Inter Tight (secondary)
|
||||
- **Responsive breakpoints**: 768px (tablet), 480px (mobile)
|
||||
|
||||
## Iteration Log
|
||||
|
||||
### Phase Transition — React Conversion Setup
|
||||
- Previous phase completed: Single HTML file `concept.html` fully built with all 9 tasks
|
||||
- New phase started: Convert HTML concept to React + TypeScript + Vite project
|
||||
- IMPLEMENTATION_PLAN.md updated with 12 React-specific tasks
|
||||
- RALPH_PROMPT.md updated with explicit /frontend-design skill requirement for all visual components
|
||||
- This progress.txt reset for new phase
|
||||
|
||||
<!-- Iterations will be logged here as tasks are completed -->
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Ralph Wiggum Loop - Visualization Improvements variant.
|
||||
|
||||
.DESCRIPTION
|
||||
Outer loop for iterative chart improvement (bug fixes, polish, new analytics).
|
||||
Each iteration spawns a fresh `claude --print` invocation.
|
||||
Memory persists via filesystem only: git commits, progress.txt, IMPLEMENTATION_PLAN.md, guardrails.md.
|
||||
|
||||
Runs until completion (<promise>COMPLETE</promise>) or circuit breaker trips.
|
||||
No arbitrary iteration limit — the loop continues until done.
|
||||
|
||||
Circuit breakers prevent runaway costs:
|
||||
- No git changes for N consecutive iterations (stalled)
|
||||
- Same error repeated N consecutive iterations (stuck)
|
||||
|
||||
.PARAMETER Model
|
||||
Claude model to use. Default: "opus".
|
||||
|
||||
.PARAMETER BranchName
|
||||
Optional git branch name. If provided, creates/checks out the branch before starting.
|
||||
|
||||
.PARAMETER MaxNoProgress
|
||||
Number of consecutive iterations with no git changes before circuit breaker trips. Default: 3.
|
||||
|
||||
.PARAMETER MaxSameError
|
||||
Number of consecutive iterations with the same error before circuit breaker trips. Default: 3.
|
||||
|
||||
.EXAMPLE
|
||||
.\ralph.ps1 -Model "opus" -BranchName "feature/dash-migration"
|
||||
|
||||
.EXAMPLE
|
||||
.\ralph.ps1 -Model "sonnet" -MaxNoProgress 2
|
||||
#>
|
||||
|
||||
param(
|
||||
[string]$Model = "opus",
|
||||
[string]$BranchName,
|
||||
[int]$MaxNoProgress = 3,
|
||||
[int]$MaxSameError = 3
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$promptFile = Join-Path $scriptDir "RALPH_PROMPT.md"
|
||||
$planFile = Join-Path $scriptDir "IMPLEMENTATION_PLAN.md"
|
||||
$guardrailsFile = Join-Path $scriptDir "guardrails.md"
|
||||
$progressFile = Join-Path $scriptDir "progress.txt"
|
||||
$logDir = Join-Path $scriptDir "logs"
|
||||
|
||||
# --- Validation ---
|
||||
|
||||
if (-not (Test-Path $promptFile)) {
|
||||
Write-Error "RALPH_PROMPT.md not found at $promptFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $planFile)) {
|
||||
Write-Error "IMPLEMENTATION_PLAN.md not found at $planFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $guardrailsFile)) {
|
||||
Write-Warning "guardrails.md not found at $guardrailsFile - loop may miss known failure patterns"
|
||||
}
|
||||
|
||||
# Ensure progress.txt exists
|
||||
if (-not (Test-Path $progressFile)) {
|
||||
@"
|
||||
# Progress Log
|
||||
|
||||
## Design Context
|
||||
<!-- Design decisions and context go here -->
|
||||
|
||||
## Reflex Patterns
|
||||
<!-- Reusable Reflex patterns discovered during development -->
|
||||
|
||||
## Iteration Log
|
||||
<!-- Each iteration appends a structured entry below. See RALPH_PROMPT.md for format. -->
|
||||
"@ | Set-Content -Path $progressFile -Encoding UTF8
|
||||
Write-Host "Created progress.txt"
|
||||
}
|
||||
|
||||
# Ensure logs directory exists
|
||||
if (-not (Test-Path $logDir)) {
|
||||
New-Item -ItemType Directory -Path $logDir | Out-Null
|
||||
Write-Host "Created logs directory"
|
||||
}
|
||||
|
||||
# --- Git Setup ---
|
||||
|
||||
$gitInitialised = $false
|
||||
try {
|
||||
$result = git rev-parse --is-inside-work-tree 2>&1
|
||||
if ($LASTEXITCODE -eq 0 -and $result -eq "true") {
|
||||
$gitInitialised = $true
|
||||
}
|
||||
} catch {
|
||||
# Not a git repo — expected on first run
|
||||
}
|
||||
|
||||
if (-not $gitInitialised) {
|
||||
Write-Host "Initialising git repository..."
|
||||
git init
|
||||
git add -A
|
||||
git commit -m "Initial commit before Ralph loop"
|
||||
}
|
||||
|
||||
if ($BranchName) {
|
||||
$currentBranch = git branch --show-current
|
||||
if ($currentBranch -ne $BranchName) {
|
||||
$branchExists = git branch --list $BranchName
|
||||
if ($branchExists) {
|
||||
Write-Host "Switching to existing branch: $BranchName"
|
||||
git checkout $BranchName
|
||||
} else {
|
||||
Write-Host "Creating branch: $BranchName"
|
||||
git checkout -b $BranchName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Circuit Breaker State ---
|
||||
|
||||
$noProgressCount = 0
|
||||
$lastErrorSignature = ""
|
||||
$sameErrorCount = 0
|
||||
|
||||
# Capture the HEAD commit hash before the loop starts
|
||||
$preLoopHead = git rev-parse HEAD 2>$null
|
||||
|
||||
# --- Main Loop ---
|
||||
|
||||
$promptContent = Get-Content -Path $promptFile -Raw
|
||||
|
||||
# Count existing iterations from progress.txt to track total across runs
|
||||
$existingIterations = 0
|
||||
if (Test-Path $progressFile) {
|
||||
$existingIterations = (Select-String -Path $progressFile -Pattern "## Iteration" -AllMatches | Measure-Object).Count
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "===== Ralph Wiggum Loop (Visualization Improvements) =====" -ForegroundColor Cyan
|
||||
Write-Host "Model: $Model | Runs until COMPLETE" -ForegroundColor Cyan
|
||||
Write-Host "Circuit breakers: no-progress=$MaxNoProgress, same-error=$MaxSameError" -ForegroundColor Cyan
|
||||
if ($BranchName) { Write-Host "Branch: $BranchName" -ForegroundColor Cyan }
|
||||
if ($existingIterations -gt 0) { Write-Host "Previous iterations: $existingIterations" -ForegroundColor Cyan }
|
||||
Write-Host "===========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$i = 0
|
||||
while ($true) {
|
||||
$i++
|
||||
$totalIteration = $existingIterations + $i
|
||||
Write-Host ""
|
||||
Write-Host "--- Iteration $i (Total: $totalIteration) ---" -ForegroundColor Yellow
|
||||
|
||||
# Record HEAD before this iteration
|
||||
$headBefore = git rev-parse HEAD 2>$null
|
||||
|
||||
# Show start time and status
|
||||
$iterStart = Get-Date
|
||||
Write-Host " Started: $($iterStart.ToString('HH:mm:ss'))" -ForegroundColor DarkGray
|
||||
Write-Host " Spawning Claude ($Model)..." -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
# Spawn fresh Claude instance with stream-json for tool call visibility
|
||||
$logFile = Join-Path $logDir "iteration_$totalIteration.log"
|
||||
$rawLogFile = Join-Path $logDir "iteration_$totalIteration.raw.jsonl"
|
||||
$maxRetries = 10
|
||||
$retryCount = 0
|
||||
$outputString = ""
|
||||
$apiOverloaded = $false
|
||||
|
||||
do {
|
||||
$apiOverloaded = $false
|
||||
$textBuilder = [System.Text.StringBuilder]::new()
|
||||
$toolCount = 0
|
||||
|
||||
# Clear raw log file for this attempt
|
||||
if (Test-Path $rawLogFile) { Remove-Item $rawLogFile -Force }
|
||||
|
||||
if ($retryCount -gt 0) {
|
||||
$backoffSeconds = [Math]::Pow(2, $retryCount - 1)
|
||||
Write-Host " [Retry $retryCount/$maxRetries] API overloaded, waiting $backoffSeconds seconds..." -ForegroundColor DarkYellow
|
||||
Start-Sleep -Seconds $backoffSeconds
|
||||
Write-Host " Retrying Claude invocation..." -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
$promptContent | claude --print --verbose --dangerously-skip-permissions --model $Model --output-format stream-json 2>&1 | ForEach-Object {
|
||||
$line = $_.ToString().Trim()
|
||||
if (-not $line) { return }
|
||||
|
||||
# Save raw event for debugging (with error handling for stream closure)
|
||||
try {
|
||||
Add-Content -Path $rawLogFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||
} catch {
|
||||
# Stream closed or file locked - ignore and continue
|
||||
}
|
||||
|
||||
try {
|
||||
$evt = $line | ConvertFrom-Json -ErrorAction Stop
|
||||
|
||||
# --- Tool use start (show tool name) ---
|
||||
if ($evt.type -eq 'content_block_start' -and $evt.content_block.type -eq 'tool_use') {
|
||||
$toolCount++
|
||||
$toolName = $evt.content_block.name
|
||||
Write-Host " [$toolName]" -ForegroundColor DarkCyan
|
||||
}
|
||||
# --- Assistant text content (streaming deltas) ---
|
||||
elseif ($evt.type -eq 'content_block_delta' -and $evt.delta.type -eq 'text_delta' -and $evt.delta.text) {
|
||||
Write-Host -NoNewline $evt.delta.text
|
||||
[void]$textBuilder.Append($evt.delta.text)
|
||||
}
|
||||
# --- Result event (error display + text capture for circuit breakers) ---
|
||||
elseif ($evt.type -eq 'result') {
|
||||
if ($evt.subtype -eq 'error_result' -and $evt.error) {
|
||||
Write-Host " [ERROR] $($evt.error)" -ForegroundColor Red
|
||||
[void]$textBuilder.AppendLine("ERROR: $($evt.error)")
|
||||
}
|
||||
elseif ($evt.result) {
|
||||
# Capture for circuit breaker detection; don't print
|
||||
# (text already displayed via streaming deltas above)
|
||||
[void]$textBuilder.AppendLine($evt.result)
|
||||
}
|
||||
}
|
||||
# --- Message-level content (final message summary) ---
|
||||
elseif ($evt.message -and $evt.message.content) {
|
||||
foreach ($block in $evt.message.content) {
|
||||
if ($block.type -eq 'text' -and $block.text) {
|
||||
Write-Host $block.text
|
||||
[void]$textBuilder.AppendLine($block.text)
|
||||
}
|
||||
elseif ($block.type -eq 'tool_use') {
|
||||
$toolCount++
|
||||
Write-Host " [$($block.name)]" -ForegroundColor DarkCyan
|
||||
}
|
||||
# Silently ignore tool_result and other block types
|
||||
}
|
||||
}
|
||||
# All other JSON events (input_json_delta, content_block_stop,
|
||||
# message_start, message_stop, ping, etc.) are silently ignored
|
||||
|
||||
} catch {
|
||||
# Not valid JSON — only print if it looks like meaningful stderr
|
||||
# (filter out JSON fragments from multi-line events)
|
||||
if ($line -and $line -notmatch '^\s*[\{\[\}\]"]') {
|
||||
Write-Host $line -ForegroundColor DarkYellow
|
||||
[void]$textBuilder.AppendLine($line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$outputString = $textBuilder.ToString()
|
||||
|
||||
# Check for 529 overloaded error
|
||||
if ($outputString -match "529.*overloaded|overloaded_error") {
|
||||
$apiOverloaded = $true
|
||||
$retryCount++
|
||||
if ($retryCount -ge $maxRetries) {
|
||||
Write-Host " [ERROR] API overloaded after $maxRetries retries, giving up." -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
# Check for usage limit with cooldown (e.g. "Usage limit reached. Reset at 3 pm")
|
||||
elseif ($outputString -match "(?i)usage limit reached.*reset at (\d{1,2})(?::(\d{2}))?\s*(am|pm)") {
|
||||
$resetHour = [int]$Matches[1]
|
||||
$resetMinute = if ($Matches[2]) { [int]$Matches[2] } else { 0 }
|
||||
$resetAmPm = $Matches[3]
|
||||
|
||||
if ($resetAmPm -ieq "pm" -and $resetHour -ne 12) { $resetHour += 12 }
|
||||
elseif ($resetAmPm -ieq "am" -and $resetHour -eq 12) { $resetHour = 0 }
|
||||
|
||||
$now = Get-Date
|
||||
$resetTime = Get-Date -Hour $resetHour -Minute $resetMinute -Second 0
|
||||
if ($resetTime -le $now) { $resetTime = $resetTime.AddDays(1) }
|
||||
$resetTime = $resetTime.AddMinutes(2)
|
||||
|
||||
$waitSeconds = [Math]::Ceiling(($resetTime - $now).TotalSeconds)
|
||||
$waitMinutes = [Math]::Ceiling($waitSeconds / 60)
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " [USAGE LIMIT] Reset at $($Matches[1]) $resetAmPm. Cooling down ~$waitMinutes minutes (until $($resetTime.ToString('HH:mm')))..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds $waitSeconds
|
||||
Write-Host " [USAGE LIMIT] Cooldown complete. Retrying iteration..." -ForegroundColor Green
|
||||
|
||||
$apiOverloaded = $true
|
||||
# Don't increment retryCount — deterministic wait, not a flaky error
|
||||
}
|
||||
} while ($apiOverloaded -and $retryCount -lt $maxRetries)
|
||||
|
||||
$outputString | Set-Content -Path $logFile -Encoding UTF8
|
||||
|
||||
# Show elapsed time and tool count
|
||||
$elapsed = (Get-Date) - $iterStart
|
||||
Write-Host ""
|
||||
Write-Host " Finished: $(Get-Date -Format 'HH:mm:ss') (elapsed: $($elapsed.ToString('mm\:ss')), tools: $toolCount)" -ForegroundColor DarkGray
|
||||
|
||||
# --- Circuit Breaker: No Progress ---
|
||||
$headAfter = git rev-parse HEAD 2>$null
|
||||
if ($headAfter -eq $headBefore) {
|
||||
$noProgressCount++
|
||||
Write-Host " [Circuit Breaker] No git commits this iteration ($noProgressCount/$MaxNoProgress)" -ForegroundColor DarkYellow
|
||||
if ($noProgressCount -ge $MaxNoProgress) {
|
||||
Write-Host ""
|
||||
Write-Host "===== CIRCUIT BREAKER: NO PROGRESS =====" -ForegroundColor Red
|
||||
Write-Host "No git commits for $MaxNoProgress consecutive iterations. The loop is stalled." -ForegroundColor Red
|
||||
Write-Host "Check progress.txt and logs/ for details on what went wrong." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
$noProgressCount = 0
|
||||
}
|
||||
|
||||
# --- Circuit Breaker: Repeated Error ---
|
||||
$errorLines = $outputString | Select-String -Pattern "(?i)(error|exception|failed|fatal)[:.].*" -AllMatches
|
||||
if ($errorLines) {
|
||||
$filteredErrors = $errorLines.Matches | Where-Object { $_.Value -notmatch "529|overloaded" } | Select-Object -First 3
|
||||
$currentErrorSignature = ($filteredErrors | ForEach-Object { $_.Value }) -join "|"
|
||||
if ($currentErrorSignature -and $currentErrorSignature -eq $lastErrorSignature) {
|
||||
$sameErrorCount++
|
||||
Write-Host " [Circuit Breaker] Same error pattern repeated ($sameErrorCount/$MaxSameError)" -ForegroundColor DarkYellow
|
||||
if ($sameErrorCount -ge $MaxSameError) {
|
||||
Write-Host ""
|
||||
Write-Host "===== CIRCUIT BREAKER: REPEATED ERROR =====" -ForegroundColor Red
|
||||
Write-Host "Same error pattern for $MaxSameError consecutive iterations:" -ForegroundColor Red
|
||||
Write-Host " $currentErrorSignature" -ForegroundColor Red
|
||||
Write-Host "Check progress.txt and logs/ for details." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
} elseif ($currentErrorSignature) {
|
||||
$sameErrorCount = 0
|
||||
}
|
||||
$lastErrorSignature = $currentErrorSignature
|
||||
} else {
|
||||
$sameErrorCount = 0
|
||||
$lastErrorSignature = ""
|
||||
}
|
||||
|
||||
# --- Push to Remote ---
|
||||
$hasRemote = git remote 2>$null
|
||||
if ($hasRemote) {
|
||||
$currentBranch = git branch --show-current
|
||||
git push origin $currentBranch 2>$null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " Pushed to remote." -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " Push failed or no remote configured - continuing." -ForegroundColor DarkYellow
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check for Completion ---
|
||||
if ($outputString -match "<promise>COMPLETE</promise>") {
|
||||
Write-Host ""
|
||||
Write-Host "===== COMPLETE =====" -ForegroundColor Green
|
||||
Write-Host "Visualization improvements finished after $i iteration(s) this run ($totalIteration total)." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Brief pause between iterations
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
Reference in New Issue
Block a user