ワンクリックで
migration
Migrate an existing project to forge — pre-flight, project shape, module path strategy, package porting order, common gotchas.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Migrate an existing project to forge — pre-flight, project shape, module path strategy, package porting order, common gotchas.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Visual-design discipline for forge frontends — brief the work before building (never invent an aesthetic without user input), declare the design system before coding, lean on the component library, use restrained color/type systems, never hand-draw complex SVGs, and verify visually before declaring done.
Write Next.js frontends — generated hooks, component library, Tailwind v4, visual verification, and Connect RPC clients.
Outbound boundary translators. One adapter per third-party system / queue / storage backend; narrow interface, vendor-neutral callers.
Write Connect RPC handlers — proto-driven codegen, the thin-translation handler pattern (validate, extract auth, convert proto↔internal, call service, wrap errors via `svcerr.Wrap`), middleware, and testing. Business logic lives in `internal/handlers/<svc>/contract.go`, never in handlers.
Forge project conventions and architecture — project structure, generated vs hand-written code, the generate pipeline, proto annotations, contracts, wiring, and naming.
Use-case orchestrators that compose two or more adapters/services. Deps are interfaces only — designed for unit tests with all-mock collaborators.
| name | migration |
| description | Migrate an existing project to forge — pre-flight, project shape, module path strategy, package porting order, common gotchas. |
Use this skill when porting an existing Go codebase onto a forge-generated scaffold. For greenfield work see getting-started.
No BSR (buf.build) auth required — Go or frontend. Fresh forge projects scaffold with no deps: in buf.yaml and local: plugins on both halves of codegen:
local: protoc-gen-go / local: protoc-gen-connect-go resolved from $PATH. forge new auto-runs forge tools install to put them there.local: ./frontends/<name>/node_modules/.bin/protoc-gen-es from @bufbuild/protoc-gen-es pinned in the frontend package.json. forge new runs npm install in each frontend dir before bootstrap codegen.Opt back into BSR-hosted plugins with forge new --buf-plugins=remote if you prefer no-install (and accept rate limits) on both halves.
Install dev tooling forge expects on PATH but does not ship: buf (no BSR auth needed), goimports, npm (only if you have frontends — and frontend TS codegen requires it), go. forge new warns and continues without them but downstream forge generate and lint passes degrade silently.
Make sure your host Go version is >= the version forge's pkg/orm requires. Forge clamps go.work upward when it detects an older host, but a host mismatch can still surprise go build calls outside forge's own subprocesses.
PATH visibility in your shell does NOT propagate to forge subprocesses unless you export it. Add export PATH=/path/to/go/bin:$PATH to your shell init or invoke forge with the env inline. buf generate is run as a forge subprocess and will silently misbehave if it cannot find tools.
Load relevant skills upfront — they are the most current source of truth on conventions:
forge skill load architecture
forge skill load services
forge skill load packs
forge skill load contracts
forge new <name>-next --kind <service|cli|library> --mod <module-path>
| Kind | Use when |
|---|---|
service (default) | Network-facing app: Connect-RPC server, middleware stack, observability, k8s deploy. See migration-service. |
cli | Cobra-based CLI binary. No internal/middleware/, no cmd/server.go, no deploy/, no service protos. See migration-cli. |
library | Pure Go module with an internal/ skeleton (and pkg/ only if you have real external importers); no cmd/ at all. For shared libraries. |
Pick this at scaffold time. --disable flags only toggle forge.yaml features; they do NOT prevent server-shaped files from being emitted. If you scaffold with the wrong kind, wipe and start over rather than pruning by hand.
Use a -next suffix (e.g. github.com/owner/project-next) during the migration so old and new repos build side-by-side. Rename the module path as the final cutover step. The friction is small and the safety upside is large — never try to migrate in-place.
forge add service admin-server). Forge stores the kebab-case form as the display name in forge.yaml and snake-cases the form used for the on-disk directory path (internal/admin_server/), with a single-token lowercase Go package decl (package admin).forge.yaml path: is always the snake form. Don't hand-edit it to use hyphens.stripe is db.v1 (not <project>.db.v1) — package names align with directory layout under the buf module root, not the project name.forge.yaml, see the Naming conventions table in architecture.Same shape for both service and cli targets:
forge new <name>-next --kind <kind> --mod <path> — scaffold the empty project.forge add operator/service/worker/webhook) and packs (forge pack install <pack>) one at a time. forge generate after each.forge generate && go mod tidy && go build ./... && forge lint. All four must pass.forge.yaml:
contracts:
strict: true
allow_exported_vars: false
allow_exported_funcs: false
exclude: []
See the contracts skill. Enabling this upfront prevents a 5-hour backfill at the end.internal/. Everything not imported outside the module lives there — services (internal/handlers/<svc>/, contract + impl + handlers_gen.go co-located in ONE dir), workers (internal/workers/<name>/), operators (internal/operators/<name>/). Order: utility code and domain types first, then the service impls and handlers, then wiring. The wiring is the explicit composition (internal/app/compose.go NewComponents(infra *Infra) (*Components, error), generated), off the owned internal/app/providers.go Infra/OpenInfra provider set. NewComponents is forge-owned (regenerated; forge disown internal/app/compose.go to hand-own); providers.go is yours, scaffolded once. There is NO generated bootstrap.go/wire_gen.go re-emitted under you, so porting wiring is filling each component's Deps off infra.<Field> (resolved by type) in NewComponents and declaring any new collaborator on the Infra provider set. (pkg/ is reserved for code you'll support as public API; absent real external importers, port into internal/, not pkg/.)forge lint --contract to the gate after every port phase. If a port leaves a package without contract.go, lint fails — fix before moving on.forge pack install stripe.subpath: field. Auth providers nest under internal/middleware/auth/<provider>/ (e.g. internal/middleware/auth/jwtauth/, internal/middleware/auth/clerk/, internal/middleware/auth/apikey/); the audit pack lives at internal/middleware/audit/auditlog/; external-service clients (e.g. stripe, twilio) live at internal/clients/<service>/. forge pack list shows the SUBPATH column. Compose interceptors explicitly in the explicit composition in internal/app/compose.go (NewComponents) — connect.WithInterceptors(otel, ratelimit, jwtauth.Interceptor(), audit, ...). The ordering is load-bearing (otel-outermost → rate-limit → auth → audit) and must be written out explicitly in NewComponents; it is never implied by registration order, and Forge ships no chain helper.forge generate after every forge pack install to wire pack contributions through the codegen pipeline.For each existing package you want under the new tree:
cp -r /path/to/source/internal/<pkg> /path/to/<name>-next/internal/
find /path/to/<name>-next/internal/<pkg> -name '*.go' \
-exec sed -i 's|<old-module-path>|<new-module-path>|g' {} +
cd /path/to/<name>-next && go mod tidy && go build ./... && go test ./internal/<pkg>/...
For templates / packs / manifests, restrict the rewrite to the runtime-import path (pkg/...) only. Doc-strings and go install ...@version references that point at the canonical tool MUST be left alone:
find /path/to/<name>-next/internal/<pkg> -type f \
\( -name '*.tmpl' -o -name '*.go' -o -name 'pack.yaml' \) \
-exec sed -i 's|<old>/pkg|<new>/pkg|g' {} +
grep -rn '<old-module-path>[^-]' /path/to/<name>-next/internal/<pkg>
Triage every grep hit. README/go install references should remain canonical; runtime imports get rewritten.
^\s*"<old-module>/ in the source-side package to enumerate every internal import path before copying. The per-internal counts tell you which other packages are needed and surface non-internal/ deps (repo-root packages, cli/ public-embed) that are easy to miss.go mod tidy, copy version pins for any dep with a fast-moving API (Delve, gRPC, k8s libs, cobra) into the target go.mod. A blind tidy resolves to latest-compatible and silently breaks API skew.//go:embed of in-repo assets. Use cp -r (preserves dotfiles like .dockerignore.tmpl) and verify any string constants that match against embedded content still align after the import-path rewrite. Without that check, embed mismatches no-op silently and downstream scaffolds break at runtime.sed s|forge|forge-next| rewrites the go_package string inside *.pb.go rawDesc bytes but does NOT update the varint length prefix → runtime panic in protobuf/internal/filedesc.unmarshalSeedOptions. Regenerate via buf generate instead of sed-rewriting compiled descriptors.pack.yaml, etc.) are a third file class alongside .go and templates. Pack manifests have a top-level dependencies: list of Go module paths. Include manifests in the rewrite glob.When you migrate an existing project, you arrive with db/migrations/
already authoritative — and in forge, that IS the entity model. SQL is
the schema language: forge generate applies the migrations to an
in-memory shadow database, introspects the tables, and projects entity
structs, ORM, and CRUD wiring from them. There is no proto-side schema
declaration to keep in sync (the legacy (forge.v1.entity) annotations
are retired and ignored).
Copy the migrations over as-is, then decide per table:
Create<X>/Get<X>/Update<X>/Delete<X>/List<Xs>) in the
service proto — the table plus the RPCs make an entity, and
forge generate projects the ORM, conversions, and frontend pages.One gotcha: the shadow apply (and your project's tests via
pkg/testkit) run the migrations on a real ephemeral postgres, so just
write plain postgres DDL — DEFAULT (now())
parenthesized, no ::type casts. Postgres-only auxiliary DDL
(extensions, functions, triggers, comments) is skipped harmlessly; a
failing CREATE/ALTER/DROP TABLE is a hard generate error you fix in a
new migration. See the db skill for details.
If the source project was an older forge project with annotated entity
protos (or a proto/db/ directory), see
migrations/proto-entities-to-schema-truth for the flip.
If you hit a forge issue mid-migration, halt the migration, file/fix the forge bug, then resume. Don't paper over — friction is exactly what dogfooding is for. Forge improvements take priority over completing the migration on schedule.
architecture, services, contracts, migration plus this skill's children).forge package new or copy source directly.export PATH=/path/to/go/bin:$PATH.migration-service — server-shaped projects (services, operators, workers, webhooks, packs, k8s)migration-cli — CLI / library projects (cmd/, second binaries, when contract.go isn't worth it)migration-upgrade — upgrading an already-forge project to a newer forge binary versionFor contract design — applies during migrations and to greenfield code alike — see the top-level contracts skill.