reliant-labs/forge/orm-and-safety

Claude Code and agent skill by reliant-labs/forge

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Where the generated ORM lands (database.orm.package), and the two per-project database opt-outs that live next to the code they affect, not in forge.yaml — `//forge:no-orm` in internal/db to skip the generated ORM, and `-- forge:allow-destructive` in a migration to allow one destructive change — plus how the database itself is derived, and the lint rules for a migration file that cannot run as one transaction (CONCURRENTLY, enum values).

Install

npx vskill@latest install reliant-labs/forge/orm-and-safety

SKILL.md

ORM opt-out and migration-safety directives

forge.yaml has no database.driver, database.migrations_dir, database.seed, or allowed_destructive — each was derived, defaulted, or moved to the code it affects.

The database is derived

A service project with db/migrations/ has a postgres database and that directory is where migrations live. Delete the directory and the project has no database: no ORM, no migrations feature. forge project features shows the reason for each.

Skipping the ORM: //forge:no-orm

Some projects hand-write their data layer and must not get generated entity structs over it. Put the marker in the doc comment of internal/db, with the reason:

go
// Package db is the hand-owned repository layer.
//
//forge:no-orm: the repository is hand-written; entities are not projected
package db

Why a marker, not a forge.yaml key. The exemption describes a property of internal/db, so a reader of that package sees it, with its reason, where //forge:exclude-contract: <why> already works. It opts out of the ORM projection only — migrations, codegen and the database stay on.

With no ORM there is no generic CRUD to wire, so every rpc is custom on the server: a CRUD-named one (CreateTask over a tasks table) gets a pb-through stub and a scaffold test row like any other, for your data layer to implement. To keep the ORM and opt ONE rpc out, mark it // forge:custom instead (forge project annotations --kind method).

forge reads it from non-test, non-generated .go files directly in internal/db/. A line that merely mentions it in prose, a _gen.go file, a _test.go file, or another package does not count. forge project features shows orm off with (//forge:no-orm in internal/db). With database.orm.package set, the marker is read in THAT directory instead.

Placing the ORM: database.orm.package

The one ORM setting forge.yaml does carry, because it is a property of the PROJECT (where generated code goes), not of a package that already exists:

yaml
database:
orm:
package: internal/store # default internal/db

Every projected file — <entity>_orm_gen.go, <entity>_store_gen.go, store_gen.go, the scaffold-once <entity>_repo_ext.go seams — lands in that directory and declares its package (package store). Everything compiled against the ORM follows it: the CRUD wiring imports it (as db, the qualifier every generated CRUD file uses), compose.go provides store.Store / store.<Entity>Store Deps fields, the test factories, forge project shapes --kind store, and the drift guard. The name-collision refusal and //forge:no-orm read that package, not internal/db. The _gen.go files open with Go's // Code generated by forge. DO NOT EDIT. banner, so golangci-lint and gopls treat them as generated with no exclusion of yours; the _repo_ext.go seams are yours and carry no banner.

Use it when internal/db is already a data layer of yours whose names (a Chat alias, a Repo, a Store) collide with the projection's, and you are porting onto the generated store one table at a time: the two packages coexist, and nothing about your internal/db has to change first. Moving the key reclaims forge's previous projection from the old directory; your _repo_ext.go seams there are yours and stay.

Allowing one destructive migration: -- forge:allow-destructive

forge lint --migration-safety refuses DROP COLUMN, DROP TABLE and TRUNCATE. When the change is intentional, mark that migration file with a SQL comment line:

sql
-- forge:allow-destructive: the column was never read; removed after the 2026-09 release
ALTER TABLE daemons DROP COLUMN last_heartbeat;
  • It is per file and in the file: the exemption sits next to the SQL it excuses. There

is no forge.yaml allowlist and no second spelling (-- forge-safety: allow-destructive is gone and no longer silences anything).

  • forge project audit lists every migration that carries it (allow_destructive_files).
  • NOT NULL findings have their own separate directive, -- forge:allow-unsafe-not-null.

Editing an applied migration. forge does not checksum migrations, so adding the comment to an already-merged file does not disturb forge. Whether your migration RUNNER records a hash of the file is the runner's business — check it before touching a merged file, and never change the statements themselves.

A migration file runs as one transaction

The migrator (and AutoMigrate, and testkit) sends a migration file to postgres as ONE query. A query holding several statements runs as one implicit transaction, so two kinds of file pass review, fail at deploy, and leave the schema dirty. forge lint --migration-safety refuses both, at the statement's line.

  • `non-transactional-statement` — a statement postgres refuses inside a transaction block

(SQLSTATE 25001) shares its file with another statement. CREATE/DROP INDEX CONCURRENTLY, REINDEX … CONCURRENTLY (and REINDEX SCHEMA|DATABASE|SYSTEM), ALTER TABLE … DETACH PARTITION … CONCURRENTLY, VACUUM, a table-less CLUSTER, and the database, tablespace, ALTER SYSTEM and subscription statements. Give each one a file of its own (forge db migration new <name>): a lone statement runs outside any transaction block.

  • `enum-value-used-before-commit` — a file uses an enum value it also adds

(ALTER TYPE … ADD VALUE 'x', then 'x' in an UPDATE, INSERT or DEFAULT). A value added in a transaction cannot be used until it commits (SQLSTATE 55P04). Move the ADD VALUE to an earlier migration of its own. A value added to a type the same file creates is fine.

Both read statements, not the database. When postgres does run the file as one transaction (a subscription created with slot_name = NONE; a string that only shares an added enum value's spelling), say so in the file with -- forge:allow-in-transaction: <why>. forge lint --show-suppressed lists it.

ALTER TYPE … ADD VALUE on its own is not refused: postgres has allowed it inside a transaction since 12, and every postgres forge provisions or tests against is 16 or newer.

Entity field types

The entity struct's field names and types are Go's, and lossless against the column:

  • Names spell initialisms in capitals: id → ID, org_id → OrgID,

api_key → APIKey (a plural stays: user_ids → UserIds). The <E>Field* and <E>Constraint* constants follow.

  • An integer is as wide as its column: INTEGER → int32, SMALLINT →

int16, BIGINT → int64.

  • JSONB / JSON is json.RawMessage — the document's bytes, nil for NULL

(jsonb[] stays []string).

  • COMMENT ON COLUMN t.c IS 'forge:go-type=<type>' declares another type for

one column: a named enum over an integer, map[string]any over json, time.Duration over a bigint, or <import path>[;<pkg>].<Type>. The type must scan and bind by Bun's rules. forge generates no wire conversion for such a column: a CRUD rpc carrying it fails generate naming it.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
5

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/reliant-labs/forge/orm-and-safety/badge)](https://verified-skill.com/skills/reliant-labs/forge/orm-and-safety)

Something wrong with this skill or its page? Tell us.