Generate a complete Rails application from a natural-language prompt. Output is a clean Rails repo (your own git history, your own dependencies — no vendor lock-in).
Inspired by Lovable, bolt.new, and v0 — built for the Rails ecosystem.
Hosted version: hifumi.dev — try it without installing anything.
The "describe an app, get a working app" experience has so far been concentrated in JavaScript-stack tools. Rails deserves the same magic: an AI generator that produces idiomatic, Rails Way applications a real team would be happy to maintain.
This project exists to:
- Lower the barrier to entry for Rails. Newcomers can pick up the framework through a working app instead of a tutorial, and see what "convention over configuration" actually buys you.
- Make the Rails ecosystem richer. Generators are part of Rails' DNA —
rails new,rails generate— and AI-driven generators are a natural continuation of that lineage. - Keep new projects in Rails. When developers reach for the trendiest AI generator, they end up on whatever stack it targets. A first-class Rails option keeps Rails on the table for the next generation of projects.
- Bring more people into the Rails community. Faster bootstrapping means more side projects, more demos, more conference talks, more people who'd otherwise never have written a
Gemfile.
Output is intentionally a plain Rails repository: your own git history, your own gems, no proprietary runtime, no hosting lock-in. Push it to GitHub, run it wherever, hand it off to a team. You own the code from minute one.
- Ruby 4.0.2 (pinned in
.ruby-version; install withfrum install 4.0.2) - SQLite
- Docker (any recent Desktop or Engine; verify with
docker --version) — required for the preview pane - Claude CLI logged in (
claude login) — used by the per-revision implementation step on the subscription plan - Optional:
OPENROUTER_API_KEYin env — needed only for the paidbin/roast-openrouterrunner
bin/setupInstalls gems, creates the dev DB, and prepares Solid Queue.
user message in chat
│
▼
[1] ChatRespondJob assistant turn; chat-LLM may call `start_generation`
│
▼
[2] StartGeneration tool ─► CreatePlan plan-LLM expands intent into N revisions
│
▼
[3] ExecuteInstructionJob workspace bootstrap (rails new, scaffolding) + per-revision loop
│
▼
[4] bin/roast workflow implement → verify (rails test) → commit, with remediation
Each CLI entry point below covers one or more of these stages. Pick the one that matches what you want to drive.
Start the full dev stack (web + Tailwind watcher + Solid Queue worker — see Procfile.dev):
bin/devOpen http://localhost:3000:
- Click New project and describe what to build, e.g. "Personal blog with posts and comments".
- The chat assistant streams a reply. If it asks for clarification, answer briefly.
- Once it calls
start_generation, three or more revision cards appear and run live (pending → generating → completed). - Final
✅ Generation finished.Output lives at~/projects/hifumi-dev-workspaces/project_<id>/.
Each project has a ▶ Start preview button on its page. Clicking it launches the generated app in a hardened Docker container; the iframe loads at http://localhost:#{3000 + project.id} once the in-container Rails responds on /up.
One-time setup:
bin/preview-rebuild-base # builds preview-base:latest (~2-4 min on a clean Docker)Re-run bin/preview-rebuild-base after any change to lib/preview/skeleton/Gemfile. Re-run bin/preview-regen-skeleton to bump Rails (rare).
Notes:
- Per-project build is fast on a warm cache (10-30 s); first build with cold base image is 2-4 min.
- Idle previews auto-stop after 30 min (
CleanupIdlePreviewsJob, every 5 min). - A new generation auto-stops the running preview first (containers don't autoload prod-mode code).
- Troubleshooting:
docker logs preview-<project_id>for in-container logs;docker ps --filter name=preview-to list active previews;docker network inspect preview-internalto inspect the shared network.
Bypasses the chat-LLM and plan-LLM. Uses a hardcoded plan fixture, then runs the real bin/roast subprocess for each revision.
bin/generate full --prompt "Simple todo list, Tailwind"
bin/generate full --prompt "..." --plan todo_list # explicit fixturePlan fixtures live in test/fixtures/plans/ (currently: todo_list). Add new ones by defining PlanFixtures.<name> returning a CreatePlan::Result.
The --prompt is stored on the project name and instruction intent; the actual revisions executed always come from the named plan. This is a debug tool, not a free-form generator — for free-form, use the UI.
bin/generate respond --project-id 42Re-runs ChatRespondJob.perform_now against the project's latest user message. Burns chat-LLM tokens. Useful if the previous turn errored or you want to retry.
bin/generate execute --instruction-id 99Synchronous ExecuteInstructionJob.perform_now. Runs every revision of the instruction from position 0 upwards; the loop stops on the first failure. There is no built-in skip for already-completed revisions, so this is not a retry tool — re-running an instruction whose revisions previously succeeded will re-execute their prompts against the current workspace state. To retry a partially failed instruction, reset the workspace (or just the failing revision's commits via git reset) before re-running. Useful for debugging the W2 chain without a Solid Queue worker.
The lowest level. The args mirror what ExecuteInstructionJob produces; the workspace must already exist.
bin/roast lib/roast/revision_workflow.rb -- \
revision_id=N \
revision_summary='Add Todo model' \
revision_prompt='Create a Todo model with title, body...'Use this when you want to iterate on a single Roast workflow file without going through DB-backed orchestration. Switch to bin/roast-openrouter for the paid per-token runner (needs OPENROUTER_API_KEY).
Stages with no dedicated CLI:
# Stage 2 in isolation: plan-LLM only
result = CreatePlan.call(intent: "blog with posts and comments", clarifications: {}, context: {})
result.instruction_description
result.revisions # => [{ summary:, prompt: }, ...]
# Stage 1 setup: create a project + chat + first user message
project = Project.create!(name: "blog demo")
chat = GeneratorAgent.create!(project: project)
chat.messages.create!(role: :user, content: "Personal blog with posts and comments")
# then: bin/generate respond --project-id <project.id>
# Skip the chat turn: invoke the StartGeneration tool the same way the chat-LLM would.
# Creates an instruction + revisions and emits `instruction.requested`, which the
# worker (or :inline adapter) picks up to run ExecuteInstructionJob.
StartGeneration.new(project: project).execute(intent: "blog", clarifications: {})bin/watch-instruction # watches Instruction.last
bin/watch-instruction 42 # watches instruction #42Polls every 2s and prints status per revision. Equivalent to the live revision cards in the UI.
bin/execute-instruction 99Thin wrapper around ExecuteInstructionJob.perform_now(99). Same caveats as bin/generate execute --instruction-id 99 (no skip for already-completed revisions; not a retry tool).
bin/rails runner bin/inspect-chat 15 # local
kamal app exec --primary "bin/rails runner bin/inspect-chat 15" # against prodDumps a project's chat messages and tool_calls rows in order, then runs a structural pairing analysis (every tool_result must follow its matching assistant tool_use). Use when the chat fails with RubyLLM::BadRequestError ("unexpected tool_use_id...") — the dump tells you whether a tool was called twice in one turn or a tool_result is orphaned.
bin/rails test # unit + integration; gated tests below are skipped by default
E2E_GENERATE=1 bin/rails test test/integration/generate_todo_list_test.rb
E2E_PREVIEW=1 bin/rails test test/integration/preview_lifecycle_test.rbE2E_GENERATE=1 runs the real bin/roast subprocess and burns Claude tokens (~15 min wall). E2E_PREVIEW=1 exercises the full Docker preview chain locally — no LLM calls, just docker build/run/curl/rm (~30-60 s on a warm base image; requires bin/preview-rebuild-base to have run once). Both are gated so the default suite stays fast.
Generated apps live at $HIFUMI_DEV_WORKSPACE_ROOT/project_<id>/ (default: ~/projects/hifumi-dev-workspaces/project_<id>/).
WS=~/projects/hifumi-dev-workspaces/project_42
cd "$WS" && git log --oneline # one commit per revision + scaffolding baseline + rails-new initial
cd "$WS" && bin/rails test # generated app's own test suite
cd "$WS" && bin/rails server # try the generated app
cat "$WS/docs/domain.md" # generator-maintained: glossary + business rules
cat "$WS/docs/conventions.md" # patterns the generator applied
cat "$WS/docs/revision_notes.md" # per-revision decision log| What | How |
|---|---|
| Dev DB | bin/rails db:drop db:setup |
| One generated app | rm -rf ~/projects/hifumi-dev-workspaces/project_<id> |
| Stuck instruction | bin/rails runner "Instruction.find(N).update!(phase: :failed)" |
| Solid Queue jobs | bin/rails runner "SolidQueue::Job.delete_all" |
| Env var | Default | Purpose |
|---|---|---|
HIFUMI_DEV_WORKSPACE_ROOT |
~/projects/hifumi-dev-workspaces |
Where generated apps live. The test suite overrides to Dir.tmpdir. |
HIFUMI_DEV_MODEL |
sonnet |
Claude model for the per-revision Roast subprocess. |
E2E_GENERATE |
unset | Set to 1 to opt the gated end-to-end generation test in. |
E2E_PREVIEW |
unset | Set to 1 to opt the gated preview-lifecycle Docker test in. |
OPENROUTER_API_KEY |
unset | Required by bin/roast-openrouter. |
The /privacy page surfaces operator (data controller) details. These are read from Rails.application.config.operator, which is fed by config/operator.yml from these production ENV vars:
| Env var | Purpose |
|---|---|
HIFUMI_OPERATOR_NAME |
Legal name of the entity running this deployment. Surfaces on /privacy and in the footer copyright. |
HIFUMI_OPERATOR_TAX_ID |
Optional. Tax / VAT identifier. Rendered on /privacy if set. |
HIFUMI_OPERATOR_BUSINESS_URL |
Optional. Link to the public business registry entry. Rendered on /privacy if set. |
HIFUMI_OPERATOR_EMAIL |
Optional. Contact email shown on /privacy. |
If HIFUMI_OPERATOR_NAME is unset in production, /privacy renders a warning notice telling the deployer what to set. Forks should set these in their Kamal secrets (or whatever deploy environment they use) before pointing real users at the deployment. Per memory, .kamal/secrets pulls these from the deployer's shell at deploy time — scope them to the deploy shell, not generic dotfiles.
ExecuteInstructionJob picks one of two wrappers based on environment:
bin/roast-claudesubscription— dev default. Uses the localclaudeCLI's OAuth subscription. Free given a paid Claude plan; throttled by the plan's quota. The wrapper unsetsANTHROPIC_*env vars and pins frum's Ruby on PATH.bin/roast-openrouter— production default (and dev whenFORCE_OPENROUTER=1). Uses OpenRouter's Anthropic-compatible API. Paid per-token; needsOPENROUTER_API_KEY.
bin/roast itself is the bundler binstub (bundle exec roast raw, no env setup) — for direct testing only. Don't rely on it from ExecuteInstructionJob.
The generator is a Rails 8 app with RubyLLM + Solid Queue. Phase notes and the active plan live under docs/; start at docs/03-plans/ for what's in flight. See CLAUDE.md for status, conventions, and the canonical reading order.
The visible UI follows the Hifumi design system — warm paper background, IBM Plex stack, Rails-red accent, rectangular outlined status tags. Tokens and component classes live in app/assets/tailwind/application.css; the full reference (token map, component-to-view inventory, voice rules, anti-patterns) is at docs/02-architecture/04-design-system.md. Read it before redesigning any chrome.
Issues and pull requests are welcome — especially on the rougher edges: workflow prompts, the verify/remediation loop, the design system, and anything in docs/09-ideas/. Start with CLAUDE.md for the project's conventions and current phase status.
- Hosted: hifumi.dev
- Source: github.com/pstrzalk/hifumi-dev
- Design system:
docs/02-architecture/04-design-system.md
MIT — see LICENSE.