Claude
Skills
Sign in
Back

forge-idiomatic-engineer

Included with Lifetime
$97 forever

Forge-focused engineering workflow for Rust applications with generated frontend bindings. Activate this skill for repositories containing a `forge.toml` file, Forge macros, or code generated by the Forge CLI.

Web Devscripts

What this skill does


# Forge Idiomatic Engineer

Full-stack Rust framework. Single binary, PostgreSQL-backed. Axum + Tokio + SQLx. Macros generate runtime wiring and frontend bindings; each handler must be registered in `src/main.rs` (macros alone do not wire it in).

## Compile-Loop Hard Rules

These cause hours of wasted debugging if missed. Internalize before writing code.

- **`SQLX_OFFLINE=true` is mandatory** for any `cargo check` / `cargo build` you run by hand. CI sets it globally. Without it, sqlx tries to validate every `sqlx::query!()` against your live `DATABASE_URL` — including queries inside published `forge-runtime` crate files you cannot edit — and you get a wall of "column does not exist" errors in third-party code. The simplest fix is `eval "$(forge env)"` in your shell rc; otherwise `export SQLX_OFFLINE=true` by hand.
- **`forge check` auto-prepares the offline cache.** It detects when `src/` is newer than `.sqlx/` and runs `cargo sqlx prepare --workspace` before the rest of the pipeline, so you don't need to think about prepare ordering. (For raw `cargo check`, you still need to run `forge migrate prepare` after editing any `sqlx::query!()`.) Pass `--no-prepare` in CI where the cache should already be correct.
- **`forge migrate prepare` hard-fails if `cargo-sqlx` is missing.** Install with `cargo install sqlx-cli --no-default-features --features postgres` and re-run.
- **All `forge` commands walk up to find `forge.toml`.** Run them from any subdirectory; the resolved root is printed at start. No need to `cd` first.
- **If anything in the compile loop feels off, run `forge doctor` first.** It checks rustc, `cargo-sqlx`, `SQLX_OFFLINE`, `DATABASE_URL` reachability, Docker, frontend tooling, `forge.toml` syntax, `.sqlx/` freshness, and that the latest migration file isn't empty in one shot.
- **A passing `cargo sqlx prepare` is a passing compile.** Don't run `forge check` purely to "confirm" what prepare just proved — prepare invokes a full `cargo check` internally. Run `forge check` only when you have new edits since the last prepare, or to exercise the rest of the validation suite (registration, schema, clippy).

## Session Start: Read Once, Trust Memory

Run `bash docs/skills/forge-idiomatic-engineer/scripts/orient.sh` first. The script walks up to find `forge.toml`, then prints a structured dump: project name + auth mode + frontend, environment readiness (`SQLX_OFFLINE`, `DATABASE_URL`, `cargo-sqlx`, Docker), `.sqlx/` cache freshness, the contents of `src/main.rs` / `src/functions/mod.rs` / `src/schema/mod.rs`, every registered handler grouped by kind, the latest migration, reactivity-enabled tables, and concrete `NEXT` action hints. One invocation replaces five separate reads.

Always read `references/pitfalls.md` and `references/resilience.md` once per session — the script doesn't print them.

Fallback when the script is unavailable (older checkout, sandboxed env): read `forge.toml`, `Cargo.toml`, `src/main.rs`, `src/functions/mod.rs`, and the most recent file under `migrations/`.

These files change only when you write to them — re-reading mid-session is almost always wasted context.

After auto-compaction, **trust the summary's file inventory**. Don't re-read `main.rs` / `mod.rs` to confirm something the summary already documented — only re-read if you're about to write to them and need exact current content. If a compaction attaches a file as an `<attachment>`, treat it as already in context; don't issue a fresh Read.

When you do need to read a file, **read it fully in one call**. Don't issue overlapping ranges (offset 200 then offset 1) — combine into a single read at offset 1 with a wide limit. Forge handler files are rarely larger than 600 lines.

## Handler Types

| Concept | Macro | Struct suffix | Registration |
|---|---|---|---|
| Read-only query | `#[forge::query]` | `Query` | `.register_query::<FnNameQuery>()` |
| Data mutation | `#[forge::mutation]` | `Mutation` | `.register_mutation::<FnNameMutation>()` |
| Background job | `#[forge::job]` | `Job` | `.register_job::<FnNameJob>()` |
| Scheduled task | `#[forge::cron]` | `Cron` | `.register_cron::<FnNameCron>()` |
| Durable workflow | `#[forge::workflow]` | `Workflow` | `.register_workflow::<FnNameWorkflow>()` |
| Long-running process | `#[forge::daemon]` | `Daemon` | `.register_daemon::<FnNameDaemon>()` |
| External HTTP event | `#[forge::webhook]` | `Webhook` | `.register_webhook::<FnNameWebhook>()` |
| AI agent tool | `#[forge::mcp_tool]` | `McpTool` | `.register_mcp_tool::<FnNameMcpTool>()` |

Or use `.auto_register()` to pick up all handlers via `inventory`.

### Naming Rules
- `pub async fn` handlers only — private functions fail codegen.
- `snake_case` fn names → macro generates `PascalCase` + type suffix. Do **not** include the type in the fn name (`heartbeat`, not `heartbeat_daemon`, or you get `HeartbeatDaemonDaemon`).
- `#[forge::model]` must be the **first** attribute on a struct.

### Context API at a Glance
Memorize so you don't reach into the `forge-core` source mid-implementation:

- `ctx.db()` → `ForgeDb` (a `sqlx::Executor`). Pass directly to query macros: `sqlx::query_as!(...).fetch_one(ctx.db()).await?`.
- `ctx.conn().await?` → `ForgeConn<'_>` (transactional, mutations only). Pass `&mut conn` to query macros.
- `ctx.user_id()` → `Result<Uuid>` on `QueryContext` and `MutationContext`. Returns `ForgeError::Unauthorized` if no principal. **There is no `ctx.auth()` method on `MutationContext`.**
- `ctx.db_conn()` → `DbConn<'_>` for shared helpers that must work in both queries and mutations. `DbConn` has an inverted convention (call `.fetch_*` on the `DbConn`, passing the query) — see `references/patterns.md`.
- Let type inference name the bindings (`let mut conn = ctx.conn().await?`). Don't import `ForgeConn` / `ForgeDb` / `DbConn` and write explicit types unless a helper signature requires it.

## Workflow

1. **Orient** — read the session-start file list above plus `references/pitfalls.md` and `references/resilience.md`. Detect frontend via `frontend/package.json` (Svelte) or `frontend/Cargo.toml` (Dioxus).
2. **Plan the slice** — decide which handlers, migrations, and frontend changes belong in this PR before writing code. Surgical, vertical, one feature.
3. **Checkpoint loop, one handler at a time:**
   1. Run `forge new <kind> <name>` instead of writing the file by hand. It scaffolds the right macro defaults, appends `pub mod <name>;` to `src/functions/mod.rs`, and inserts `mod functions;` in `src/main.rs` if missing. Kinds: `query`, `mutation`, `job`, `cron`, `workflow`, `daemon`, `webhook`, `mcp_tool`, `model`, `enum`.
   2. Edit the scaffolded file: replace placeholder SQL/business logic with the real implementation.
   3. `forge check`. It auto-prepares the `.sqlx/` cache when sources are newer, so you don't need a separate `forge migrate prepare` step in the common case.
   4. If it fails, fix the root cause and re-run **only the failing step**. Do not write the next handler with errors outstanding.
   5. Move to the next handler.
4. **`forge generate`** after backend changes settle. Never edit generated files.
5. **Frontend** — wire the UI against the generated bindings. `forge test` for Playwright E2E.
6. **Final pass** — `forge check` clean, `forge test` green, write a brief change summary.

When the user says "fix it" / "can you fix it" after you've diagnosed a problem, fix it — don't ask for re-confirmation. Only pause to confirm for destructive actions (data deletion, schema drops, force pushes).

### Architectural Defaults (choose upfront)
- **Auth**: Social OAuth, password + HS256, or RS256 — pick before coding (see `patterns.md`). Social logins must link via the `user_identities` table.
- **Env**: `ctx.env_require()` / `ctx.env_or()`, never `std::env::var()`.
- **HTTP**: `ctx.http()` for RPC, `ctx.raw_http()` when you need `bytes_stream()` or custom redirect policy.
- **SQL**: `sqlx::query!()` / `query_as!()` bang-macros only. Run `forge migrate 
Files: 11
Size: 132.2 KB
Complexity: 69/100
Category: Web Dev

Related in Web Dev