> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reliantlabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Generated vs yours

> How forge marks the files it writes, how it detects hand edits, how generated files merge in plain git, and the commands that move a file between forge and you

Forge writes a lot of code, and the arrangement only works if you and forge can always tell which files are whose. [Project anatomy](https://docs.reliantlabs.io/forge/project-anatomy.md#generated-files-and-yours) gives the three groups: regenerated files, files written once and then yours, and files that are purely yours. This page explains the machinery behind that table: how a file declares its owner, what `forge generate` will and will not touch, how a hand edit is caught, why generated files merge cleanly in plain git, and the commands for changing ownership on purpose.

The design rule underneath is that forge never silently destroys your work and never silently lets generated code drift. Every ambiguous case stops and tells you, naming the file.

## Reading the header

Every file forge regenerates starts with a banner:

```go theme={null}
// Code generated by forge. DO NOT EDIT.
// forge:hash=3e3832c4894a02c46e056b468476fdd8e287d0e09326ef661dde8de63515d0ad
// forge-owned: regenerated every run — do not edit (forge project disown to take ownership)
// Source: the APPLIED schema (db/migrations) of table customers.
```

The first line is the standard marker Go tooling and editors recognize. The `forge:hash` line is the file's own fingerprint: a hash of its contents. The third line says who owns it, and the `Source:` line says which input it was projected from, so you know where to make the change instead. The same four lines appear in TypeScript (`*_gen.ts`) and KCL (`config_gen.k`, with `#` comments).

A file that is yours says so as plainly:

```go theme={null}
// yours: scaffolded once, never touched again — forge will not overwrite
// this file
```

Those files carry no hash at all, because there is nothing to certify: forge does not own the bytes.

A few starters sit between the two. The `Dockerfile`, `Taskfile.yml`, `.golangci.yml` and the CI workflows carry a marker so `forge project upgrade` can tell whether you have customized them. Editing them is expected, and it is never reported as drift.

## What `forge generate` will and won't touch

The command's behavior follows from the ownership of each file:

| The file is… | `forge generate` does |
| - | - |
| Regenerated and untouched since forge wrote it | Rewrites it to the current output. If nothing it depends on changed, the bytes are identical and nothing happens. |
| Regenerated and you edited it | Refuses, names the file, and rolls back the run's writes (below). |
| Written once and present | Leaves it alone. Always, including when you change the proto or the schema. |
| Written once and you deleted it | Leaves it deleted. |
| Disowned | Leaves it alone, permanently. |
| `internal/app/compose.go` | Adds the entries new components need and drops the ones that no longer apply, never touching your expressions. |
| Your migrations, `rpc_<name>.go` files, `providers.go` and `contract.go` | Never writes them after birth. |

A few consequences are worth stating directly. Generate never changes a handler body or a migration. It appends new CRUD methods to `handlers_crud.go` when a new CRUD RPC appears, and never modifies the existing content. It is safe to run any time and with several agents in one checkout: concurrent runs queue on a lock file, `.forge/forge.lock`, rather than collide. And a run on a tree that is byte-identical to one a full run already verified says `nothing to do` and stops in seconds, so running it whenever you are unsure costs little.

### When a generated file is edited

Suppose you add a line to `internal/db/customer_orm_gen.go`. `forge lint --generated-drift` catches it before generate does:

```text theme={null}
▸ --generated-drift
❌ generated-drift — 1 error
   ✗ internal/db/customer_orm_gen.go [forge-generated-file-edited] internal/db/customer_orm_gen.go is forge-generated (it carries the `Code generated by forge. DO NOT EDIT.` banner) and was edited after forge wrote it — the next `forge generate` regenerates the file and these edits are lost.
      → revert the edit (`git checkout -- internal/db/customer_orm_gen.go`) and move the customization to a user-owned file, or run `forge project disown internal/db/customer_orm_gen.go --reason "<why>"` to take permanent ownership of it (one-way: forge stops regenerating it).
```

The same drift probe feeds `forge generate`, `forge lint`, `forge project audit` and `forge ci verify-generated`, so a finding never gates one command and not another. `forge generate` itself runs a guard before it writes: files that fail to verify are compared against what this run would render. If they match, they are quietly re-recorded. If they differ, the run fails with one batched report listing every conflict, and its writes are rolled back, so you learn about all of them at once instead of discovering them one run at a time.

You then have three ways out, in order of preference:

1. **Move the change to a file that is yours.** Nearly every generated file has an owned seam beside it: `handlers_crud.go` for CRUD behavior, `<entity>_repo_ext.go` for queries the generated store cannot express, `providers.go` for wiring, `observe_chain.go` for a package's tracing chain.
2. **Pass `--force`** to throw your edit away and regenerate. This is the explicit "discard my changes" choice, scoped to the files the guard reported.
3. **Disown the file**, below.

Because the hash covers the whole body, any edit to it is detected. That is the point: a generated file that quietly differs from its template is the worst kind of drift.

## Files that can't carry a header

JSON has no comments, so a generated JSON file (an alert default, a dashboard, an observability spec) can't hold its own hash. For those outputs, forge keeps one small certificate file per output under `.forge/hashes/`, holding the hash of the output's last render:

```text theme={null}
.forge/hashes/deploy/observability/alert-defaults_gen.json.sha256
.forge/hashes/deploy/observability/spec/project_gen.json.sha256
.forge/hashes/frontends/…
```

The certificate for `deploy/observability/alert-defaults_gen.json` contains a single line, the sha256 of that file. It is one file per output, never a shared manifest, so a change to one output touches only its own certificate. All of `.forge/hashes/` is committed.

## The state forge keeps

Everything under `.forge/` that is committed is small, per-file and keyed by path:

| Path | Records |
| - | - |
| `.forge/hashes/` | The hash of each comment-incapable generated output. |
| `.forge/scaffolded/` | The birth ledger: one empty JSON entry per scaffold-once file forge has written, named by path (`.forge/scaffolded/cmd/demo/main.go.json`). |
| `.forge/disowned.json` | Files you took over, each with its reason. |

Everything else there (the lock, logs, a failed run's output) is per-checkout scratch.

The birth ledger exists to give "written once" a precise meaning. A file that is absent could mean two different things: forge has never written it, or you deleted it on purpose. Looking only at the disk can't distinguish them, and an early version that treated "absent" as "not yet written" brought a deleted lifecycle test back three times in one session. The ledger records every path forge has ever scaffolded, so a present entry with a missing file means you deleted it and forge respects that, in this checkout and in every clone, since the ledger is committed.

## Generated files merge in plain git

Agents work on branches, many at once. If two branches each add an entity and each run `forge generate`, the generated files must merge without help. Forge treats this as a design requirement, and it is tested: the project's own end-to-end suite takes a scaffolded project, changes independent inputs on two branches, regenerates each, merges with plain `git merge` (no merge driver, no attributes, the way GitHub's server-side merge does), and then requires that a `forge generate` on the merged tree changes nothing.

Three things make that hold:

* **Sectioned self-certification.** A file that aggregates independent parts, such as a package's mocks (one per interface), the config schema (one block per section) or the database grants, divides itself into sections. Each section has a named header and its own hash on the next line, so a change to one part rewrites only that part's lines. The `mock_gen.go` generated for the `pricing` package shows the shape:

  ```go theme={null}
  // forge:section MockService
  // forge:hash=9d3a554484e0991820203cb0cacf7444c3aaed012a911b89d0dbacf1daafdcb3

  // MockService is a test mock for the Service interface.
  …
  // forge:section MockCatalog
  // forge:hash=804b68848130a966fccf04119d33f618fffe81311856285f608dbbd00cbbb81d
  ```

  Two branches that change different interfaces edit different sections and merge cleanly, and the merged file still verifies, because each section's hash depends on that section alone.

* **One file per part.** Outputs that used to be one file listing every entity (the frontend mock dispatch table, the nav and dashboard modules, the database grants) are written one file per entity, so entities born on different branches merge as separate files.

* **Per-file state.** The certificates and the birth ledger are one file per path, not a shared manifest, so two branches that each add a service never edit the same line of state.

A merge driver for generated files also ships as a convenience. It merges everything else with git's normal three-way merge and takes the fingerprint lines from your side, and the scaffolded `.gitattributes` routes `*_gen.go`, `*_gen.ts` and the state files to it. Nothing depends on it: GitHub's server-side merge and merge queues run no driver, which is exactly why the outputs themselves are designed to merge without one. `FORGE_NO_MERGE_DRIVER=1` opts out.

A conflict in a generated file usually means the inputs conflicted too, so resolve the proto, migration or config first. Then run `forge generate`: it renders the merged inputs, and you have no reason to hand-merge the output.

## Taking a file over: `forge project disown`

Sometimes the generated form can't express what you need. Disowning transfers the file to you for good:

```bash theme={null}
forge project disown pkg/app/wire_gen.go --reason "custom connection-pool wiring forge can't express"
```

After that, forge never touches the file again: no regeneration, no overwrite, not even with `--force`, no drift errors. It is a one-way door, and the `--reason` is required. The reason is recorded per path in `.forge/disowned.json`, and it is deliberately the payload: a disown means the generator couldn't do something you needed, which is design feedback `forge project audit --json` surfaces.

Disown is the last resort. Prefer an owned seam when one exists, since a seam keeps working through every regenerate while a disowned file is yours to maintain against future changes in the schema or the generator. To return a disowned file to forge, delete it and run `forge generate`; the emitter re-emits the pristine render, and your content is discarded, so copy out anything you want to keep first.

A second form, `--absent`, declines a scaffold-once file forge has not written, or one you do not want at all:

```bash theme={null}
forge project disown cmd/app/cmd/serve.go --absent --reason "our roots land later"
```

The decline is recorded in the birth ledger, and `forge generate` stops writing it.

## Getting a deleted file back: `rescaffold` and `scaffolded`

Deleting a scaffold-once file is an act of ownership that forge honors. Two commands let you see and reverse it:

```bash theme={null}
forge project scaffolded            # every scaffold-once file forge wrote, and which are absent
forge project scaffolded --absent   # only the ones you deleted
forge project rescaffold .github/workflows/ci.yml
```

`scaffolded` reads the birth ledger and marks each path `present` (your bytes, left alone) or `ABSENT` (you deleted it and forge is respecting that). On the scratch project used for these pages it listed 146 scaffold-once files, from CI workflows to the frontend's pages.

`rescaffold` drops a path's birth record, writes the file as forge would scaffold it for the project today, and records the birth again. The file is yours once more, and deleting it again sticks. It refuses a path that still exists, so it never overwrites your bytes. To replace a file you edited with the current template, use `forge project upgrade --force <path>`, which shows the diff first.

## Where to put a change

When you want forge's output to behave differently, the answer is almost always in one of these places, in this order:

1. **Change the input.** The proto, the migration, `config.proto` or the KCL. Most "generated code is wrong" cases are an input that says something different from what you meant.
2. **Use the owned file beside it.** Each generated file names its sibling: CRUD behavior in `handlers_crud.go`, custom queries in `<entity>_repo_ext.go`, construction and wiring in `providers.go` and `compose.go`.
3. **Disown the file**, with a reason.
4. **Report it.** If the generated form is wrong rather than merely different, that is a forge bug. A disown reason is read as design feedback, and `forge report-bug` is the direct route.

## Related topics

[Two sources of truth](https://docs.reliantlabs.io/forge/concepts/two-sources-of-truth.md) covers what the inputs are, and [Contracts and dependency injection](https://docs.reliantlabs.io/forge/concepts/contracts-and-injection.md) covers `compose.go` reconciliation in detail. For the lint lanes that guard these boundaries (`--generated-drift`, `--scaffolds`, `--contract`), run `forge lint --help`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.