Initial commit: Inspection reporting app

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jimmy 2026-03-22 01:03:51 +01:00
commit 0a4f870f67
22 changed files with 654 additions and 0 deletions

1
.ralph/current-events Normal file
View file

@ -0,0 +1 @@
.ralph/events-20260321-235351.jsonl

1
.ralph/current-loop-id Normal file
View file

@ -0,0 +1 @@
primary-20260321-235351

View file

@ -0,0 +1,4 @@
2026-03-21T23:53:51.588056Z INFO ralph: Creating scratchpad directory: /home/jimmy/inspection_app_n/prototoolagain/.ralph/agent
2026-03-21T23:53:51.588132Z INFO ralph: Spawning subprocess for TUI mode child_args=["-c", "ralph.yml", "-H", "builtin:code-assist", "run", "--rpc"]
2026-03-21T23:53:51.588162Z INFO ralph: TUI subprocess stderr redirected to log file log_file=/home/jimmy/inspection_app_n/prototoolagain/.ralph/diagnostics/logs/ralph-2026-03-22T00-53-51-588-32854.log
2026-03-21T23:53:51.588319Z INFO ralph: TUI running in subprocess RPC mode

View file

@ -0,0 +1,9 @@
2026-03-21T23:53:51.596629Z  WARN ralph::loop_runner: Interactive mode requested but stdout is not a TTY, falling back to autonomous
2026-03-21T23:53:51.596942Z  INFO ralph_core::event_loop: Memory injection check: enabled=true, inject=Auto, workspace_root="/home/jimmy/inspection_app_n/prototoolagain"
2026-03-21T23:53:51.596956Z  INFO ralph_core::event_loop: Looking for memories at: "/home/jimmy/inspection_app_n/prototoolagain/.ralph/agent/memories.md" (exists: false)
2026-03-21T23:53:51.596960Z  INFO ralph_core::event_loop: Successfully loaded 0 memories from store
2026-03-21T23:53:51.596961Z  INFO ralph_core::event_loop: Memory store is empty - no memories to inject
2026-03-21T23:59:04.846327Z  INFO ralph_core::event_loop: Memory injection check: enabled=true, inject=Auto, workspace_root="/home/jimmy/inspection_app_n/prototoolagain"
2026-03-21T23:59:04.846344Z  INFO ralph_core::event_loop: Looking for memories at: "/home/jimmy/inspection_app_n/prototoolagain/.ralph/agent/memories.md" (exists: false)
2026-03-21T23:59:04.846349Z  INFO ralph_core::event_loop: Successfully loaded 0 memories from store
2026-03-21T23:59:04.846352Z  INFO ralph_core::event_loop: Memory store is empty - no memories to inject

View file

@ -0,0 +1 @@
{"ts":"2026-03-21T23:53:51.596805172+00:00","iteration":0,"hat":"loop","topic":"build.start","triggered":"planner","payload":"You are building a production-ready Inspection Reporting and Management web application from scratch. The GitHub remote URL is: https://github.com/pingud98/prototoolagain.git\n\n---\n\n## TECH STACK\n\n- Language: Python 3.11+\n- Web Framework: Flask (with Flask-Login, Flask-WTF, Flask-SQLAlchemy)\n- Database: SQLite via SQLAlchemy ORM\n- PDF Generation: WeasyPrint (A4-formatted output)\n- TLS/HTTPS: Self-signed certificate via trustme or mkcert for local hosting\n- Frontend: Jinja2 templates + Tailwind CS... [truncated, 7661 chars total]"}

1
.ralph/history.jsonl Normal file

File diff suppressed because one or more lines are too long

View file

5
.ralph/loop.lock Normal file
View file

@ -0,0 +1,5 @@
{
"pid": 32871,
"started": "2026-03-21T23:53:51.590496087Z",
"prompt": "You are building a production-ready Inspection Reporting and Management web application from scra..."
}

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 James Devine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

237
PROMPT.md Normal file
View file

@ -0,0 +1,237 @@
You are building a production-ready Inspection Reporting and Management web application from scratch. The GitHub remote URL is: https://github.com/pingud98/prototoolagain.git
---
## TECH STACK
- Language: Python 3.11+
- Web Framework: Flask (with Flask-Login, Flask-WTF, Flask-SQLAlchemy)
- Database: SQLite via SQLAlchemy ORM
- PDF Generation: WeasyPrint (A4-formatted output)
- TLS/HTTPS: Self-signed certificate via trustme or mkcert for local hosting
- Frontend: Jinja2 templates + Tailwind CSS (via CDN) + vanilla JS
- Auth: Bcrypt password hashing, session-based login
- File Storage: Local filesystem under /uploads/, referenced in DB
---
## PROJECT STRUCTURE
inspection-app/
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── routes/
│ │ ├── auth.py
│ │ ├── inspections.py
│ │ ├── admin.py
│ │ └── export.py
│ ├── templates/
│ │ ├── base.html
│ │ ├── login.html
│ │ ├── dashboard.html
│ │ ├── inspection_form.html
│ │ ├── inspection_view.html
│ │ └── admin/
│ │ ├── users.html
│ │ └── user_form.html
│ ├── static/
│ │ ├── css/
│ │ └── js/
│ └── utils/
│ ├── pdf_generator.py
│ └── security.py
├── uploads/
├── certs/
├── setup.py
├── config.py
├── run.py
├── requirements.txt
└── .gitignore
---
## DATABASE MODELS
### User
- id, username, full_name, email, password_hash, is_admin, is_active, created_at
### Inspection
- id, installation_name, location, inspection_date, version (int, starts at 1),
reference_number (int), observations, conclusion_text,
conclusion_status (enum: ok / minor / major),
created_by (FK User), created_at, updated_at
### InspectionInspector
- id, inspection_id (FK), user_id (FK nullable), free_text_name (nullable)
(Supports both registered users and free-text names)
### Photo
- id, inspection_id (FK), filename, caption,
action_required (enum: none / urgent / before_next), uploaded_at
---
## SETUP SCRIPT (setup.py)
The setup script must:
1. Install all dependencies from requirements.txt using pip
2. Generate a self-signed TLS certificate and key, saved to certs/
3. Create the SQLite database and run all table migrations
4. Prompt the admin for: username, full name, email, password (with confirmation)
5. Create the admin account with is_admin=True
6. Print a success message with the local HTTPS URL (e.g. https://localhost:5000)
7. Be runnable with: python setup.py
---
## CORE FEATURES
### Authentication
- Login page (username + password)
- Session-based auth with Flask-Login
- All routes protected — redirect to login if not authenticated
- Logout route
- No self-registration — admin creates all accounts
### Admin Panel (/admin)
- List all users
- Create new user (username, full name, email, password, admin toggle)
- Edit user (change name, email, reset password, toggle active/admin)
- Deactivate (not delete) users
- Only accessible to is_admin=True users
### Dashboard (/)
- Table of all inspections the logged-in user has access to
- Columns: Reference No., Installation Name, Location, Date, Version, Conclusion Status, Actions
- Actions: View, Edit, Export PDF
- "New Inspection" button
### Inspection Form (/inspection/new and /inspection/<id>/edit)
Fields:
1. Installation Name — text input
2. Location — text input
3. Date of Inspection — date picker
4. Version — auto-incremented integer (display only, not editable)
5. Reference Number — integer input
6. Inspector(s) — pre-filled with logged-in user's full name; allow adding more via:
- Dropdown of registered users
- Free-text field for external individuals
- Display as removable tags/chips
7. Observations — large textarea
8. Photos section:
- Upload multiple photos
- For each uploaded photo display a thumbnail
- Per-photo fields: caption (text), action_required (radio buttons):
"No action required"
"Urgent action required"
"Action required before next inspection"
- Ability to remove photos
9. Conclusion section:
- Conclusion comments textarea
- Radio buttons (select exactly one):
OK for operation in current state
Minor comments — Remedial actions required for continued operation
Major comments — Operation suspended until resolution and satisfactory follow-up inspection
Buttons:
- New inspection: "Complete Report" → saves, sets version=1, redirects to view page
- Edit existing: "Update Report" → saves, increments version by 1, redirects to view page
- Cancel → returns to dashboard
### Inspection View (/inspection/<id>)
- Read-only formatted view of the report
- Shows all fields, photos (with captions and action status), inspectors, conclusion
- "Edit Report" button
- "Export as PDF" button
---
## PDF EXPORT (/inspection/<id>/pdf)
- Generated using WeasyPrint
- Formatted for A4 pages
- Include:
- App name / report title header
- All inspection fields in a clean two-column layout
- Inspector names listed
- Observations in a clearly delineated box
- Photos displayed in a grid (max 2 per row), each with caption and action status clearly labelled
- Conclusion section with selected status prominently displayed
- Footer with page number and generation timestamp
- Flows naturally across multiple A4 pages if content requires it
- Served as a file download: inspection_report_<ref>_v<version>.pdf
---
## SECURITY REQUIREMENTS
- All passwords hashed with bcrypt (min cost factor 12)
- CSRF protection on all forms via Flask-WTF
- File uploads validated: only JPEG, PNG, GIF, WEBP accepted; max 10MB per file
- Uploaded filenames sanitised with werkzeug.utils.secure_filename and stored with UUID prefix
- User input escaped in all templates (Jinja2 autoescaping enabled)
- Admin routes protected with both login_required and admin_required decorators
- Secret key loaded from environment variable SECRET_KEY or auto-generated and saved to .env on first run
- HTTPS enforced — Flask run with SSL context using certs from certs/
- .env and *.db and certs/ added to .gitignore
---
## GITHUB INSTRUCTIONS
- The repository already exists and has been initialised with prior commits
- Completely discard all prior history
- Use git checkout --orphan new-branch, add all files, commit, then force-push to main
- Commit message: "Initial commit: Inspection reporting app"
- Include a comprehensive README.md with:
- Project overview
- Requirements (Python version, OS)
- Setup instructions (python setup.py)
- How to run (python run.py)
- How to access (HTTPS URL)
- Notes on the self-signed certificate browser warning
---
## CODE QUALITY STANDARDS
- All Python files include docstrings
- Routes grouped into Blueprints
- No hardcoded secrets
- Database access only via SQLAlchemy ORM — no raw SQL
- Error pages for 403, 404, 500
- Flash messages for all user actions (success and error)
- Logging to a rotating file log (logs/app.log)
---
## EXECUTION ORDER
Build in this order:
1. requirements.txt and config.py
2. app/models.py
3. app/__init__.py (app factory)
4. Auth blueprint + templates
5. Admin blueprint + templates
6. Inspection blueprint + form + view templates
7. PDF export utility + route
8. setup.py
9. run.py
10. README.md
11. .gitignore
12. GitHub force-push
Do not proceed to the next step until the current one is complete and internally consistent.
---
## NOTES FOR THE OPERATOR
- WeasyPrint requires system-level dependencies. Install them before running setup.py:
Debian/Ubuntu: sudo apt install libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0
macOS: brew install pango
Windows: See https://doc.courtbouillon.org/weasyprint/stable/first_steps.html

47
README.md Normal file
View file

@ -0,0 +1,47 @@
# Inspection Reporting and Management App
## Project Overview
This is a production-ready Inspection Reporting and Management web application built with Python 3.11+, Flask, SQLite, and WeasyPrint for PDF generation.
## Requirements
- Python 3.11 or higher
- pip
- System dependencies for WeasyPrint (e.g., libpango, libharfbuzz, etc.)
## Setup
1. Install Python dependencies: `pip install -r requirements.txt`
2. Run the setup script: `python setup.py`
- This installs dependencies, generates a self-signed TLS certificate, creates the SQLite database, and prompts for admin account details.
3. Ensure system-level WeasyPrint dependencies are installed:
- Debian/Ubuntu: `sudo apt install libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0`
- macOS: `brew install pango`
- Windows: Follow instructions at https://doc.courtbouillon.org/weasyprint/stable/first_steps.html
## Running the Application
1. Start the server: `python run.py`
2. Access the application at `https://localhost:5000`
- Due to the self-signed certificate, your browser will show a warning. You can proceed by adding an exception or using `--no-check-certificates` if supported.
## Authentication
- Login with the admin account created during setup.
- All routes are protected; unauthenticated access redirects to the login page.
- Logout is available via the `/logout` route.
## Admin Panel
- Accessible at `/admin`.
- Admins can manage users, view inspections, and perform administrative tasks.
- Only users with `is_admin=True` can access the admin panel.
## PDF Export
- Inspection reports can be exported as PDFs via the `/inspection/<id>/pdf` endpoint.
- PDFs are generated using WeasyPrint and formatted for A4 pages.
## Security
- Passwords are hashed with bcrypt (cost factor 12).
- CSRF protection is enabled on all forms.
- File uploads are validated for allowed types and size limits.
- Input is escaped in templates.
## Notes
- The self-signed certificate may cause browser warnings. For production, consider using a trusted certificate or `mkcert` for local development.
- All database files, environment variables, and certificates are listed in `.gitignore`.

36
app/__init__.py Normal file
View file

@ -0,0 +1,36 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_bcrypt import Bcrypt
from config import Config
db = SQLAlchemy()
login_manager = LoginManager()
bcrypt = Bcrypt()
def create_app(config_class=Config):
app = Flask(__name__.name)
app.config.from_object(config_class)
# Initialize extensions
db.init_app(app)
login_manager.init_app(app)
bcrypt.init_app(app)
# Register blueprints
from app.routes.auth import auth_bp
from app.routes.inspections import inspections_bp
from app.routes.admin import admin_bp
from app.routes.export import export_bp
app.register_blueprint(auth_bp)
app.register_blueprint(inspections_bp)
app.register_blueprint(admin_bp)
app.register_blueprint(export_bp)
# Create database tables if they don't exist
@app.before_first_request
def create_tables():
db.create_all()
return app

17
app/forms.py Normal file
View file

@ -0,0 +1,17 @@
"""WTForms for the application."""
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length
class LoginForm(FlaskForm):
"""Login form."""
username = StringField(
"Username", validators=[DataRequired(), Length(min=1, max=64)]
)
password = PasswordField(
"Password", validators=[DataRequired()]
)
submit = SubmitField("Login")

89
app/models.py Normal file
View file

@ -0,0 +1,89 @@
"""SQLAlchemy models for the Inspection Reporting and Management app."""
from datetime import datetime
from typing import Optional
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from app import db
class User(db.Model, UserMixin):
"""User account for authentication and authorization."""
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
full_name = db.Column(db.String(120), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
is_admin = db.Column(db.Boolean, nullable=False, default=False)
is_active = db.Column(db.Boolean, nullable=False, default=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Password handling
def set_password(self, password: str) -> None:
"""Hash and store a password."""
self.password_hash = generate_password_hash(password)
def check_password(self, password: str) -> bool:
"""Check a plaintext password against the stored hash."""
return check_password_hash(self.password_hash, password)
class Inspection(db.Model):
"""A single inspection report."""
id = db.Column(db.Integer, primary_key=True)
installation_name = db.Column(db.String(120), nullable=False)
location = db.Column(db.String(120), nullable=False)
inspection_date = db.Column(db.Date, nullable=False)
version = db.Column(db.Integer, nullable=False, default=1)
reference_number = db.Column(db.Integer, nullable=False)
observations = db.Column(db.Text, nullable=True)
conclusion_text = db.Column(db.Text, nullable=True)
conclusion_status = db.Column(
db.String(20), nullable=False, default="ok"
) # ok / minor / major
created_by = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(
db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
)
# Relationships
creator = db.relationship("User", backref="inspections")
inspectors = db.relationship(
"InspectionInspector", backref="inspection", cascade="all, delete-orphan"
)
photos = db.relationship(
"Photo", backref="inspection", cascade="all, delete-orphan"
)
class InspectionInspector(db.Model):
"""Inspector for an inspection either a registered user or a freetext name."""
id = db.Column(db.Integer, primary_key=True)
inspection_id = db.Column(
db.Integer, db.ForeignKey("inspection.id"), nullable=False
)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
free_text_name = db.Column(db.String(120), nullable=True)
__unique__ = ("inspection_id", "user_id")
class Photo(db.Model):
"""Photo attached to an inspection."""
id = db.Column(db.Integer, primary_key=True)
inspection_id = db.Column(
db.Integer, db.ForeignKey("inspection.id"), nullable=False
)
filename = db.Column(db.String(200), nullable=False)
caption = db.Column(db.String(200), nullable=True)
action_required = db.Column(
db.String(20), nullable=False, default="none"
) # none / urgent / before_next
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)

29
app/routes/auth.py Normal file
View file

@ -0,0 +1,29 @@
"""Authentication blueprint."""
from flask import Blueprint, render_template, redirect, url_for, flash
from flask_login import login_user, logout_user, login_required, current_user
from app import db
from app.models import User
from app.forms import LoginForm
auth_bp = Blueprint('auth', __name__)
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user)
flash('Logged in successfully.', 'success')
return redirect(url_for('main.dashboard'))
else:
flash('Invalid username or password.', 'danger')
return render_template('login.html', form=form)
@auth_bp.route('/logout')
@login_required
def logout():
logout_user()
flash('Logged out successfully.', 'info')
return redirect(url_for('auth.login'))

44
app/templates/base.html Normal file
View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Inspection Reporting{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
<nav class="bg-blue-800 text-white p-4">
<div class="container mx-auto flex justify-between items-center">
<div class="text-white font-bold">Inspection App</div>
<div>
{% if current_user.is_authenticated %}
<span class="mr-4">Welcome, {{ current_user.full_name }}</span>
<a href="{{ url_for('auth.logout') }}" class="text-white hover:underline">Logout</a>
{% else %}
<a href="{{ url_for('auth.login') }}" class="text-white hover:underline">Login</a>
{% endif %}
</div>
</div>
</nav>
<main class="container mx-auto py-6">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="space-y-4">
{% for category, message in messages %}
<div class="p-3 mb-2 rounded {% if category == 'danger' %}bg-red-100 text-red-800{% elif category == 'success' %}bg-green-100 text-green-800{% elif category == 'info' %}bg-blue-100 text-blue-800{% else %}bg-gray-100 text-gray-800{% endif %}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<slot></slot>
</main>
<script>
// Place for any custom JS
</script>
</body>
</html>

View file

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold mb-4">Dashboard</h1>
<a href="{{ url_for('admin.users') }}" class="bg-green-600 text-white px-4 py-2 rounded mr-4 hover:bg-green-700">Admin Panel</a>
<!-- Content will be added here later -->
</div>
{% endblock %}

29
app/templates/login.html Normal file
View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="max-w-md mx-auto bg-white p-8 rounded shadow-md">
<h1 class="text-2xl font-bold mb-4">Login</h1>
<form method="POST" action="{{ url_for('auth.login') }}">
{{ form.hidden_tag() }}
<div class="mb-4">
{{ form.username.label(class="block text-gray-700 mb-1") }}
{{ form.username(class="w-full px-3 py-2 border rounded") }}
{% if form.username.errors %}
<p class="text-red-600 text-sm">{{ form.username.errors[0] }}</p>
{% endif %}
</div>
<div class="mb-4">
{{ form.password.label(class="block text-gray-700 mb-1") }}
{{ form.password(class="w-full px-3 py-2 border rounded") }}
{% if form.password.errors %}
<p class="text-red-600 text-sm">{{ form.password.errors[0] }}</p>
{% endif %}
</div>
<div class="mb-4">
{{ form.submit(class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700") }}
</div>
</form>
</div>
{% endblock %}

15
config.py Normal file
View file

@ -0,0 +1,15 @@
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
class Config:
SECRET_KEY = os.getenv('SECRET_KEY') or 'dev-secret-key-' + str(os.getpid())
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL', f'sqlite:///{BASE_DIR / "inspection.db"}')
SQLALCHEMY_TRACK_MODIFICATIONS = False
TESTING = False
WTF_CSRF_ENABLED = True
# File upload config
UPLOAD_FOLDER = str(BASE_DIR / 'uploads')
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10MB per file
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'webp'}

13
hats.yml Normal file
View file

@ -0,0 +1,13 @@
# hats.yml
event_loop:
starting_event: "task.start"
hats:
builder:
name: "Builder"
triggers: ["task.start"]
publishes: ["task.done"]
instructions: |
Implement the task from PROMPT.md.
Run any relevant tests.
When finished, emit task.done and print LOOP_COMPLETE.

34
ralph.yml Normal file
View file

@ -0,0 +1,34 @@
# Ralph Orchestrator Configuration
# Generated by: ralph init --backend claude
# Docs: https://github.com/mikeyobrien/ralph-orchestrator
cli:
backend: "claude"
event_loop:
prompt_file: "PROMPT.md"
completion_promise: "LOOP_COMPLETE"
max_iterations: 100
# max_runtime_seconds: 14400 # 4 hours max
# ─────────────────────────────────────────────────────────────────────────────
# Additional Configuration (uncomment to customize)
# ─────────────────────────────────────────────────────────────────────────────
# core:
# scratchpad: ".ralph/agent/scratchpad.md"
# specs_dir: ".ralph/specs/"
# Custom hats for multi-agent workflows:
# hats:
# builder:
# name: "Builder"
# triggers: ["build.task"]
# publishes: ["build.done", "build.blocked"]
#
# reviewer:
# name: "Reviewer"
# triggers: ["review.request"]
# publishes: ["review.approved", "review.changes_requested"]
# Create PROMPT.md with your task, then run: ralph run

10
requirements.txt Normal file
View file

@ -0,0 +1,10 @@
Flask>=2.3
Flask-Login
Flask-WTF
Flask-SQLAlchemy
WeasyPrint
trustme
python-dotenv
Flask-Bcrypt
Pillow
uuid