diff --git a/CLAUDE.md b/CLAUDE.md index 5114fad..467b35a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,229 +1,77 @@ # CLAUDE.md -Guidance for Claude Code when working in this repository. +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this is -SummerCMS in Go: a rewrite of the WinterCMS/OctoberCMS content management framework for the Golem15 stack. Read `README.md` first, then `.planning/notes/` for decisions and `.planning/research/` for ecosystem findings. +SummerCMS in Go: a content management framework modelled on WinterCMS/OctoberCMS, for the Golem15 stack. An application built on it compiles into one binary: framework modules, the application's plugins, the embedded admin SPA and console commands. Read `README.md` first (module table, quick start, development commands), then `.planning/PROJECT.md` and `.planning/notes/` for decisions. -The reference implementation is WinterCMS. The Golem15 starter lives at `../examples/golem15-wintercms-starter`, and the v1 port target (Płytarium) at `/media/nvme/dev/golem15/fonoteka`. +The core value: a WinterCMS-shaped app is ported plugin by plugin to a single Go binary without its frontend noticing. When porting endpoints, the existing frontend's requests (Nuxt app, MCP server) define the contract; response shapes are not "improved" during a port. -## GSD workflow rules (lean mode) +References: the WinterCMS starter at `../examples/golem15-wintercms-starter`, the first port target Płytarium at `/media/nvme/dev/golem15/fonoteka`. -These rules apply to every GSD phase in this project and override defaults: +## Commands -1. **Lean planning.** Prefer fewer, larger plans per phase. Skip optional agents unless a phase touches security or the plugin API. -2. **Checkpoint on plan count.** Before writing plans for a phase, present the suggested number of plans with a one-line scope for each and wait for confirmation. The user adjusts the number; only then write PLAN.md files. -3. **Unit tests are always the last plan of a phase.** Every phase ends with a dedicated plan that brings full unit test coverage for that phase's code. Earlier plans in the phase may include smoke tests but must not be blocked on coverage. -4. **Go conventions.** Standard library first (net/http ServeMux, html/template, encoding/json). Add a dependency only when the research doc or a phase decision names it. Keep `go vet` and `go test ./...` green at every commit. -5. **Compiled plugins.** Plugins are Go modules registered at build time. Do not introduce runtime plugin loading (stdlib `plugin`, yaegi) without a decision note. -6. **API parity is the acceptance test.** When porting Płytarium endpoints, the existing Nuxt app's requests define the contract. Do not "improve" response shapes during the port. +```sh +go vet ./... +go test -short ./... # fast loop, no Docker +go test ./... # also the testcontainers Postgres/Mailpit tests (needs Docker) +go test -short ./modules/cabana -run TestName # one test +cd examples/hello && go test ./... # separate workspace module, not covered by the root ./... -## Commit rules +go install ./cmd/summer # the CLI +go test ./cmd/summer -run TestDocsTree # docs checker +summer docs:build --check # docs checker (identifiers, links, anchors, src= snippets, command names) +summer docs:sync # refresh src= code copies in docs/ after changing their source -- Never add co-author tags to commit messages. -- One logical change per commit. Planning docs and code in separate commits. +npm --prefix admin ci +npm --prefix admin run test | typecheck | build | dev +scripts/check-admin-openapi.sh [--check] # swag annotations in cabana -> admin/openapi/admin.json -> admin/src/api/schema.d.ts +scripts/check-admin-dist.sh # committed modules/boardwalk/dist must match a fresh admin build +scripts/check-phaseN.sh # per-phase verification gates +``` -## Documentation +Docker-backed tests skip themselves under `testing.Short()`. Keep `go vet` and `go test ./...` green at every commit. -- Any change to a package under `modules/` that touches its exported API, config keys, CLI commands or dependencies must update that module's `README.md` in the same change (the same commit or PR). -- A new module ships with a `README.md` that follows the standard structure: H1 name, one-sentence summary, import line, then Overview, Features, Usage, API reference, the optional Configuration and CLI commands sections, Dependencies and Testing. It also gets a row in the root `README.md` modules table, whose purpose text reuses the summary sentence. -- Framework READMEs never name a consuming application. Say "the application" or "host application", and use neutral example names such as `blog` or `acme`. -- Every identifier named in a README or a docs page must exist in the package. The docs checker verifies this automatically; `go doc ./modules/ ` remains the manual check. -- A change to a module's exported API, config keys or CLI commands also updates the affected pages under `docs/` in the same change. `go test ./cmd/summer -run TestDocsTree` and `summer docs:build --check` check identifiers, internal links and anchors, `src=` snippets, command names and consuming-application names across `docs/` and every module README. -- Config keys named in README or docs pages are not checked automatically yet (deferred in Phase 11.1); review them by hand. - - -## Project - -**SummerCMS (Go)** - -SummerCMS is a Go rewrite of the WinterCMS/OctoberCMS content management framework for the Golem15 stack. It keeps what makes WinterCMS productive for us (plugins that extend each other, YAML-driven admin forms and lists, models/controllers/components, scaffolding commands, a headless API layer) and drops the parts that do not survive a compiled language (runtime plugin autoloading, PHP-style mutable magic). It is built by Golem15 developers and AI agents, for Golem15 projects that today run on the WinterCMS starter. - -v1 is a headless backend that runs one real project, Płytarium (`fonoteka`), with its existing Nuxt 4 app and MCP server unchanged. - -**Core Value:** An existing WinterCMS-shaped app can be ported plugin by plugin to a single Go binary without its frontend noticing: the PHP version's API contract is the acceptance test. - -### Constraints - -- **Tech stack**: Go 1.27, standard library first (net/http ServeMux, html/template, encoding/json); a dependency is added only when the research doc or a phase decision names it — keeps the binary boring and the dependency tree auditable -- **Data**: GORM on Postgres only — chosen for Eloquent-like DX and a line-by-line model port; River needs Postgres -- **Compatibility**: `vue-fonoteka-app` and `fonoteka-mcp` must run unchanged; the Nuxt app's requests define the contract and response shapes are not improved during the port -- **Realtime**: keep the Centrifugo server and port only the publisher and token issuing — the Nuxt client connects to Centrifugo directly -- **Plugins**: compiled at build time; no runtime plugin loading without a decision note -- **Two repositories**: `summercms.go` is the framework only (the `summer` packages, the CLI, the admin SPA shell, the parity harness tooling) and knows nothing about Płytarium; `fonoteka.go`, a sibling directory in the meta repo, is the application: a go.work workspace holding the ported plugins (user, websockets, translate, feedback, sitemap, fonoteka) and the app binary, requiring the framework by module path with a local replace during development. Roadmap phases name which repo each plan writes to; planning docs stay in `summercms.go/.planning` -- **Workflow**: lean planning (few, large plans per phase), a plan-count checkpoint before PLAN.md files are written, unit tests as the last plan of every phase, `go vet` and `go test ./...` green at every commit -- **Commits**: no co-author tags; one logical change per commit; planning docs and code in separate commits -- **Core plugin contracts**: the PHP user, blog, pages and payment plugins are shared across many projects; the Go ports must preserve their contracts and the PHP originals are not changed as part of this project - - - -## Technology Stack - -## Recommended Stack -### Core Technologies -| Technology | Version | Purpose | Why Recommended | -|------------|---------|---------|-----------------| -| Go | 1.27 (Aug 2026) | Language/runtime | Already decided. Generic methods clean up repository/query-builder APIs; `encoding/json` is now json/v2-backed (stricter: rejects duplicate keys, invalid UTF-8) — matters for hand-authored `fields.yaml`/`columns.yaml` round-tripped through JSON for the admin SPA. | -| GORM | v1.31.2 (Jun 22, 2026) | ORM, Postgres only | Already decided. Current stable line; v1.31.x added generics-based `Count` etc. Confirmed via pkg.go.dev version list. HIGH confidence. | -| gorm.io/driver/postgres | v1.6.3 (Sep 14, 2026) | Postgres driver for GORM | Latest patch release, two days before this research. Its `go.mod` pins `github.com/jackc/pgx/v5 v5.10.0` — GORM's "Postgres driver" is pgx under the hood, not lib/pq. This is the crux of the River pool-sharing question below. HIGH confidence (read go.mod directly). | -| River | v0.47.0 (confirmed via multiple independent 2026 dependabot PRs bumping to this version; GitHub releases page fetch returned stale cached 2024 dates — do not trust that page directly, cross-checked against `riverdriver`/`riverpgxv5`/`riverdatabasesql` sub-package publish dates of Apr–Jul 2026) | Postgres-backed job queue | Already decided. MEDIUM-HIGH confidence on the exact patch version; HIGH confidence it's the current v0.4x line and actively maintained. | -| zitadel/oidc | v3.51.0 (Sep 14, 2026) | OAuth2/OIDC provider | Already decided, and confirmed correct: **v4 exists only as `v4.0.0-next.4` (pre-release, Jul 30, 2026)**. v3 is the maintained stable line (v3.51.0 shipped two days before this research, newer than the v4 pre-release). Stay on v3; do not chase v4 until it ships stable. Flagged explicitly per the quality gate — this does not contradict the existing decision, it confirms it. | -| golang-jwt/jwt | v5.3.1 (Jan 28, 2026), import path `github.com/golang-jwt/jwt/v5` | JWT for the SPA and for signing Centrifugo connection/subscription tokens | Already decided. One library covers both jobs — see Centrifugo section. | -| go-playground/validator | v10.30.4 (Sep 3, 2026), import path `.../validator/v10` | Struct-tag validation from YAML `rules:` | Already decided. Current. | -| cobra | v1.10.2 (Dec 3, 2025) | CLI framework | Already decided. Current stable. | -| gocloud.dev | v0.46.0 (Jun 2, 2026) | Blob storage abstraction | Already decided. Current. | -| Vue 3 + TypeScript | (frontend, out of Go versioning scope) | Minimal admin SPA | Already decided. Types generated from OpenAPI — see OpenAPI section. | -### Supporting Libraries -| Library | Version | Purpose | When to Use | -|---------|---------|---------|-------------| -| go-gormigrate/gormigrate/v2 | latest tag as of May 26, 2026 (1.2k stars, actively maintained, PostgreSQL 18 in its own CI matrix) | Migrations, up/down, per-plugin | **Recommended primary migration tool.** See "Migration tooling" below for why this beats goose and atlas for this project's shape. | -| pressly/goose | v3.27.3 (Jul 27, 2026, 11.5k stars) | Alternative migration tool | Use instead of gormigrate only if a future plugin needs pure-SQL migrations independent of `*gorm.DB`, or a non-GORM connection. Not recommended as primary — see rationale below. | -| jackc/pgx/v5 | v5.10.0 (pinned by gorm.io/driver/postgres's own go.mod) | Postgres driver, shared by GORM and River | Not a separate app-level choice — it's already there transitively through GORM's driver. River's own driver (`riverpgxv5`) also needs pgx v5. Pin the same `pgx/v5` version across `go.sum` (Go's module resolution does this automatically via MVS; just don't force a divergent replace directive). | -| koanf/v2 (`github.com/knadh/koanf/v2`) | v2.3.4 (Mar 21, 2026) | Layered config with plugin namespaces | Already decided per go-ecosystem.md. See "Config format" below for the HOCON question. | -| koanf providers: `providers/file`, `providers/env`, `providers/confmap` | ships alongside koanf/v2, versioned independently as sub-modules | Base file + env-var overlay + programmatic plugin defaults | `file` provider loads `base.yaml`/`.yaml`; `env` provider overlays `SUMMER_`-prefixed env vars with a transform func mapping `SUMMER_DB__HOST` → `db.host`; `confmap` lets each plugin register its own namespaced defaults (`plugins..*`) before the file/env layers are merged on top. | -| koanf/parsers/yaml | tracks koanf/v2 | YAML parsing for koanf | Wraps `goccy/go-yaml` as of koanf's current release — **not** `gopkg.in/yaml.v3`, which is why the direct fields/columns parsing question below matters independently. | -| goccy/go-yaml | v1.19.2 (Jan 8, 2026) | Parse `fields.yaml` / `columns.yaml` | **Use this, not `gopkg.in/yaml.v3`.** See "What NOT to Use" — yaml.v3's upstream repo (`go-yaml/yaml`) was archived by its maintainer on Apr 1, 2025 and is explicitly marked unmaintained. goccy/go-yaml passes 355/402 cases of the YAML test suite vs 295/402 for yaml.v3, has an AST/tokenizer API useful for round-tripping comments if `summer make:*` scaffolds YAML files, and is what koanf itself has moved to. HIGH confidence — this is a load-bearing finding, not a style preference. | -| swaggo/swag | v1.16.6 (Jul 29, 2026, stable; v2.0.0-rc6 exists but is not production-ready) | Generate OpenAPI from annotated net/http handlers | **Recommended primary.** See "OpenAPI generation" below. | -| openapi-typescript | current npm release (JS ecosystem, not Go-versioned) | Generate TS types for the Vue admin SPA from the OpenAPI doc swag produces | Already decided (types generated from OpenAPI). Feeds directly off swag's output JSON/YAML. | -| typesense-go | v3.2.0 (Mar 27, 2025), confirms Typesense server API v28 support | Typesense client | Already decided. This is the most recent tagged release found; no newer tag surfaced in this research pass — treat the exact patch as MEDIUM confidence (worth a re-check at implementation time since it is over a year old relative to today) but the library itself is the only real Go client and is the correct pick. | -| centrifugal/gocent/v3 | current release, import path `github.com/centrifugal/gocent/v3` | Centrifugo HTTP API client (publish/broadcast/presence) | Official client from the Centrifugo org. Small (87 stars is normal for a niche official SDK, not a red flag — same maintainers as Centrifugo itself). Given it wraps ~4 REST calls, hand-rolling a thin `net/http` client is a legitimate stdlib-first alternative if the team wants zero non-decided dependencies; gocent is the pragmatic default. | -| — (no separate library for Centrifugo tokens) | — | Sign Centrifugo connection/subscription JWTs | Centrifugo does not need its own token library — it verifies plain JWTs signed with an HMAC secret (or RSA/ECDSA). **Use the already-decided `golang-jwt/jwt/v5` to sign these tokens too**, with claims shaped per Centrifugo's connection/subscription token spec. One JWT library, two token types (SPA auth, Centrifugo realtime auth). | -| go-i18n/v2 (`github.com/nicksnyder/go-i18n/v2`) | v2.6.1 (Jan 1, 2026) | CLDR-plural i18n | Already decided. CLDR v48 as of this release (up from CLDR 44 in older v2.3.0 — make sure any tutorial/blog post referencing go-i18n is checked against the current release, plural-rule edge cases have shifted). Also replaced an unmaintained YAML dependency internally in a recent release — another confirmation that the Go YAML-library churn described above is a live, current issue, not stale training-data noise. | -| testcontainers-go + testcontainers-go/modules/postgres | v0.44.0 (Aug 7, 2026) | Spin up real Postgres in tests | For GORM model tests, migration up/down tests, and River job tests that need real Postgres behavior (JSON columns, `LISTEN`/`NOTIFY`, constraint behavior) rather than SQLite-in-CI approximations. | -| stretchr/testify | v1.12.1 (Aug 17, 2026) | Assertions in Go tests | Standard choice; use `assert`/`require` for readability in the API-parity diff tests, not as a BDD framework — keep tests as plain `func Test...(*testing.T)`. | -| air-verse/air | v1.67.4 (Aug 1, 2026), actively maintained | Dev watch-rebuild loop | This is the "watch loop that rebuilds the compiled-plugin binary on change" the plugin architecture already calls for. Config via `.air.toml`; point `cmd`/`bin` at `go build -o ./tmp/summer ./cmd/summer && ./tmp/summer serve`. | -| golangci-lint | v2.13.2 (Aug 27, 2026) | Linting | v2's config schema (`version: "2"` in `.golangci.yml`) is a breaking change from v1 — do not copy a v1 config from an older Go project without migrating it. | -### Development Tools -| Tool | Purpose | Notes | -|------|---------|-------| -| air | live rebuild in dev | Config as above; combine with the plugin system's own `plugins.go` regeneration step so editing a plugin's route/model file triggers both codegen and rebuild. | -| golangci-lint v2 | static analysis in CI and pre-commit | Enable `govet`, `staticcheck`, `errcheck`, `revive` at minimum; the stdlib-first constraint makes `depguard` worth configuring to fail CI if an undecided dependency is imported. | -| testcontainers-go | integration tests against real Postgres | Gate these behind a build tag or `-short` skip so unit tests stay fast; the phase-ending "unit tests" plan (per this repo's lean-mode rule) should still run everywhere, with testcontainers tests as a separate, slower suite. | -| golden-file API parity tests | diff Go backend responses against recorded PHP responses | Not a library — a pattern: record Płytarium's actual JSON responses per route as fixtures (`testdata/parity/.golden.json`), replay the same requests against the Go backend in `httptest.Server`, diff with `testify/assert.JSONEq` or a small custom normalizer for non-deterministic fields (timestamps, IDs). This is the literal implementation of the PROJECT.md requirement "an API parity test suite replays the Nuxt app's and MCP server's requests against both backends and diffs responses." | -## Installation -# Core (already decided, versions confirmed this session) -# Migrations -# Config -# YAML for fields.yaml / columns.yaml (NOT gopkg.in/yaml.v3) -# i18n -# OpenAPI -# Search / realtime clients -# Dev / test dependencies -# Dev tools (not go.mod dependencies) -# golangci-lint installed via its install script, pinned to v2.13.2, not go install -## Deep Dives on the Milestone's Specific Questions -### GORM: plain structs, not gorm gen or the new GORM CLI -### Migration tooling: gormigrate, not goose or atlas -- **gormigrate** (`go-gorm/gormigrate/v2`) defines migrations as `{ID, Migrate(tx *gorm.DB) error, Rollback(tx *gorm.DB) error}` structs, operates directly on the shared `*gorm.DB`, and — confirmed by reading the source (`gormigrate.go`) directly rather than trusting a README excerpt — exposes `RollbackLast()` and `RollbackTo(id)` as first-class methods, plus `MigrateTo(id)`. This is a literal implementation of "drop the last migration and fix it": run `RollbackLast()`, edit the migration's `Migrate`/`Rollback` funcs, run `Migrate()` again. No separate migration-file format, no separate driver, no SQL string embedding required (though `tx.Exec(...)` is available inside a migration when raw SQL is easier than `Migrator()` calls). -- **goose** supports Go-code migrations (not just `.sql` files) via `goose.NewGoMigration`, which is the reason it was flagged as a candidate. But it registers migrations into its own provider backed by an `embed.FS` per source tree; making per-plugin migration sets compose cleanly (each plugin contributing its own ordered slice, aggregated by the kernel at boot from the generated plugin import list) is more natural with gormigrate's plain `[]*gormigrate.Migration` slices than with goose's filesystem-and-provider model. goose is still the right tool if a future plugin needs raw-SQL migrations decoupled from `*gorm.DB` entirely (e.g. a plugin that talks to Postgres via bare `pgx` for performance reasons) — keep it as the named fallback, not the default. -- **atlas** is a declarative schema-diff tool (desired-state HCL/SQL compared against actual state, migration generated automatically). It is the better fit when GORM's own struct tags are treated as the single source of truth and hand-written data-backfill migrations are rare. This project's port needs custom, hand-authored up/down logic per PHP migration (27 of them, with real data semantics, not just DDL) — atlas's diffing model fights that instead of helping it, and it adds a second DSL and a separate binary. Not recommended for v1. -### River + GORM: share one `*sql.DB`, not one `pgxpool.Pool` -### zitadel/oidc v3 storage interface: what to implement -- **`AuthStorage`** — the core of the authorization-code + PKCE flow: `CreateAuthRequest`, `AuthRequestByID`, `AuthRequestByCode`, `SaveAuthCode`, `DeleteAuthRequest`, `CreateAccessToken`, `CreateAccessAndRefreshTokens`, `TokenRequestByRefreshToken`, `TerminateSession`, `RevokeToken`, `GetRefreshTokenInfo`, `SigningKey`, `SignatureAlgorithms`, `KeySet`. This is the interface Płytarium's `OAuthAuthCode`, `OAuthClient`, `OAuthRefreshToken` models map onto directly. -- **`OPStorage`** — `GetClientByClientID`, `AuthorizeClientIDSecret`, `SetUserinfoFromToken`, `SetIntrospectionFromToken`, `GetPrivateClaimsFromScopes`, `GetKeyByIDAndClientID`, `ValidateJWTProfileScopes`. `SetUserinfoFromScopes` exists but is explicitly documented as deprecated in favor of the optional `CanSetUserinfoFromRequest` interface — implement the newer one, leave the old one empty. -- **`ClientCredentialsStorage`** (optional) — needed only if the client-credentials grant is used (worth checking whether the MCP server or ChatGPT connector needs it; Płytarium's OAuth models list doesn't obviously call for it, flag as an open question for the phase that implements this). -- **`TokenExchangeStorage`** / **`TokenExchangeTokensVerifierStorage`** (optional) — RFC 8693 token exchange; almost certainly out of scope for a straight port unless the existing PHP OAuth server implements it (check `wavepath.org/plugins/golem15/oauthserver` for this before assuming it's unneeded). -- **Optional `Can*` interfaces** (`CanTerminateSessionFromRequest`, `CanSetUserinfoFromRequest`, and others in the same file) let the request object itself (not just IDs) reach the storage implementation — worth implementing from the start rather than the older, more limited required methods, since they carry richer context. -### Config: koanf + YAML, not a literal HOCON parser -### go-i18n + CLDR plurals -### YAML parsing: goccy/go-yaml, confirmed necessary not just preferred -### OpenAPI generation: swaggo/swag (code-first, comment-driven), not Huma, not hand-written -### Testing stack -- **testcontainers-go** (v0.44.0, Aug 7, 2026) + its `modules/postgres` for real-Postgres integration tests: GORM migrations up/down, River job processing (needs real `LISTEN`/`NOTIFY`), JSON column round-tripping. Gate behind a build tag/`-short` so the fast unit-test loop stays fast. -- **testify** (v1.12.1, Aug 17, 2026) for assertions only (`assert`/`require`) — keep tests as plain stdlib `func TestX(t *testing.T)`, no BDD DSL, consistent with stdlib-first. -- **Golden-file API parity tests**: no library needed. Record real Płytarium PHP responses as JSON fixtures per route, replay identical requests against the Go backend via `httptest.NewServer`, diff with a normalizer that ignores non-deterministic fields (timestamps, generated IDs) before comparing. This directly implements the PROJECT.md acceptance test. -### Dev loop and linting -- **air** (v1.67.4, Aug 1, 2026) for the watch-rebuild loop the compiled-plugin architecture needs to "feel like WinterCMS's drop-in loop" (go-ecosystem.md's own phrase). Point its build command at the full `go build` of the `summer` binary plus the plugin-import-list regeneration step, not just a bare `go build ./...`. -- **golangci-lint v2.13.2** (Aug 27, 2026). v2's `.golangci.yml` schema (`version: "2"`) is a breaking change from v1 configs — do not reuse a v1 config verbatim. Configure `depguard` to enforce the stdlib-first / decided-dependency-only constraint at CI time, not just by convention. -## Alternatives Considered -| Recommended | Alternative | When to Use Alternative | -|--------------|-------------|--------------------------| -| gormigrate | goose | A future plugin needs raw-SQL migrations independent of `*gorm.DB`/GORM entirely. | -| gormigrate | atlas | Schema is treated as pure declarative state (GORM struct tags as source of truth) with rare hand-authored data migrations — not this project's shape for v1. | -| plain GORM structs | go-gorm/cli or gorm.io/gen | A later, non-ported plugin wants compile-time-checked queries and is willing to add a codegen step. | -| swaggo/swag | huma + humago | A later, non-parity API surface where request/response shape isn't constrained by an existing contract, and the team accepts Huma's handler-shape convention project-wide. | -| swaggo/swag | hand-written OpenAPI + oapi-codegen | A small, from-scratch API (not a 154-route port) where authoring the spec first is cheaper than annotating existing handlers. | -| YAML (via koanf) | literal HOCON via a Go parser | Never, for this project — no maintained Go HOCON library exists at the bar this project applies elsewhere. | -| gocent/v3 | hand-rolled Centrifugo HTTP client | Team wants zero additional non-decided dependencies; Centrifugo's HTTP API is small enough (~4 calls) that this is a legitimate, if slightly more work, stdlib-first option. | -## What NOT to Use -| Avoid | Why | Use Instead | -|-------|-----|--------------| -| `gopkg.in/yaml.v3` (`go-yaml/yaml`) | Archived by its maintainer Apr 1, 2025; README explicitly says "THIS PROJECT IS UNMAINTAINED." No further fixes, security patches, or Go-compatibility work will land. | `goccy/go-yaml` v1.19.2 | -| `gorm.io/gen` or `go-gorm/cli` for v1 model code | Adds a codegen layer that works against the explicit goal of a simple, reviewable, line-by-line Eloquent-to-GORM port of 25 models. | Plain GORM structs and methods | -| `atlas` for v1 migrations | Declarative schema-diff model fights hand-authored, data-aware up/down migrations for 27 real PHP migrations with actual data semantics, and adds a second DSL/binary. | gormigrate | -| Sharing a raw `pgxpool.Pool` directly between GORM's postgres driver and River's `riverpgxv5` driver as two independent pools | Two separate pools against the same database is wasteful and defeats the point of "share one pool"; naively wiring them both to the *same* pool object isn't how either library's constructor is designed to be used. | One shared `*sql.DB` (via `pgx/v5/stdlib`) for both GORM and River's queries, plus one small separate `pgxpool.Pool` used only for River's `LISTEN`/`NOTIFY` wake-ups | -| zitadel/oidc v4 | Only exists as `v4.0.0-next.4`, a pre-release from Jul 30, 2026 — not production-ready, and older than the current v3.51.0 stable release. | zitadel/oidc v3 (`/v3`), already decided | -| Huma for the v1 parity port specifically | Requires restructuring every ported handler into Huma's typed Input/Output convention rather than ordinary `net/http` handlers, right when byte-compatible parity with an existing contract is the entire point. | swaggo/swag, annotating existing handlers | -| swaggo/swag v2 (`v2.0.0-rc6`) | Still a release candidate as of Sep 13, 2026; OpenAPI 3.1 support and dependency cleanup are still in flux. | swaggo/swag v1.16.6 | -| A literal HOCON parser in Go (`gurkankaymak/hocon`, `go-hocon/hocon`) | Both are tiny, single-or-zero-star projects with unclear or no active maintenance — far below this project's ecosystem-depth bar for every other pick. | koanf + YAML, achieving the same layering design without literal HOCON syntax | -| golangci-lint v1-style config on a v2 install | v2 changed the `.golangci.yml` schema; a copied v1 config will not behave as expected. | A `version: "2"` config written against v2's current schema | -## Stack Patterns by Variant -- Use goose's Go-code migrations for that plugin specifically -- Because gormigrate assumes `*gorm.DB`-shaped `Migrate`/`Rollback` funcs; a plugin that deliberately bypasses GORM for a hot path has no natural `*gorm.DB` to hand it -- Consider Huma instead of hand-annotated swag comments, and `go-gorm/cli` instead of plain structs -- Because the byte-compatible-parity constraint that rules both out for v1 doesn't apply to code with no existing contract to match -## Version Compatibility -| Package A | Compatible With | Notes | -|-----------|------------------|-------| -| gorm.io/gorm v1.31.2 | gorm.io/driver/postgres v1.6.3 | Driver's own go.mod targets this gorm version; keep them bumped together. | -| gorm.io/driver/postgres v1.6.3 | jackc/pgx/v5 v5.10.0 | Pinned directly in the driver's go.mod — this is the pgx version that ends up in `go.sum` for the whole binary via GORM. | -| River v0.47.0 | jackc/pgx/v5 (River's own `riverpgxv5` driver requires v5; `riverdatabasesql` driver works with any `database/sql`-registered driver, including pgx's `stdlib` registration) | Go's module resolution (MVS) will pick one pgx/v5 version for the whole build; do not force a divergent `replace` for either GORM's or River's sake — let them converge. | -| zitadel/oidc v3.51.0 | go-jose/go-jose/v4 (imported directly in `pkg/op/storage.go`) | v3's storage interfaces use `jose.SignatureAlgorithm` and `*jose.JSONWebKey` types from go-jose v4 in method signatures — a storage implementation's key-handling code will import this directly. | -| koanf/v2 v2.3.4 | koanf/parsers/yaml (wraps goccy/go-yaml) | Confirms goccy/go-yaml is already in the dependency tree via koanf; using it directly for `fields.yaml`/`columns.yaml` doesn't add a new library, just a direct dependency on one already present transitively. | -| go-i18n/v2 v2.6.1 | golang.org/x/text v0.32.0+ | go-i18n's own recent release notes cite bumping to this x/text version alongside the CLDR v48 update. | -| golangci-lint v2.13.2 | `.golangci.yml` schema `version: "2"` | Not backward compatible with v1-style config files without migration. | -## Sources -- pkg.go.dev version pages for `gorm.io/gorm`, `gorm.io/driver/postgres`, `github.com/golang-jwt/jwt/v5`, `github.com/go-playground/validator/v10`, `github.com/spf13/cobra`, `gocloud.dev`, `github.com/goccy/go-yaml`, `github.com/stretchr/testify`, `github.com/testcontainers/testcontainers-go` — versions and dates, fetched 2026-09-16. HIGH confidence. -- `raw.githubusercontent.com/go-gorm/postgres/master/go.mod` — read directly for the pgx/v5 pin. HIGH confidence. -- `riverqueue.com/docs/gorm` — official River+GORM integration guide, the source for the shared-`*sql.DB` pattern. HIGH confidence. -- `raw.githubusercontent.com/zitadel/oidc/main/pkg/op/storage.go` — read directly for interface definitions. HIGH confidence. -- github.com/zitadel/oidc releases page — v3.51.0 vs v4.0.0-next.4 dates. HIGH confidence. -- `raw.githubusercontent.com/go-gormigrate/gormigrate/master/gormigrate.go` and `README.md` — read directly for `RollbackLast`/`RollbackTo`/`MigrateTo` API and usage pattern. HIGH confidence. -- github.com/go-yaml/yaml repo page — archived status and "UNMAINTAINED" README notice, read directly. HIGH confidence. -- WebSearch results on koanf's yaml parser migrating to goccy/go-yaml — MEDIUM confidence (WebSearch-sourced, not independently re-verified against koanf's own go.mod, but corroborated by two independent search results and by go-i18n's own release notes mentioning a similar unmaintained-YAML-dependency swap). -- github.com/swaggo/swag releases page, huma.rocks docs (`bring-your-own-router`, `humago` adapter), oapi-codegen GitHub — OpenAPI approach comparison. HIGH confidence on version/maintenance facts, MEDIUM-HIGH (judgment call) on the recommendation itself. -- github.com/gurkankaymak/hocon, github.com/go-hocon/hocon — star counts and activity, read directly. HIGH confidence these are not viable options. -- github.com/typesense/typesense-go releases — v3.2.0, Mar 27 2025. MEDIUM confidence this is still the latest tag; worth a fresh check at implementation time given the gap since this research date. -- github.com/centrifugal/gocent repo page — v3 module path, star count. MEDIUM confidence (couldn't confirm an exact latest tag/date from the page content retrieved). -- github.com/air-verse/air releases — v1.67.4, Aug 1 2026. HIGH confidence. -- golangci-lint changelog/release references — v2.13.2, Aug 27 2026, v2 config schema change. HIGH confidence. - - - -## Conventions - -Conventions not yet established. Will populate as patterns emerge during development. - - - ## Architecture -Architecture not yet mapped. Follow existing patterns found in the codebase. - +- **One Go module**, `git.golem15.com/golem15/summercms`; each package is imported as `.../modules/`. Module names follow a beach theme, and each README carries a `**Name:**` mnemonic; a new module gets a fitting beach name, a README in the standard structure and a row in the root `README.md` table. +- **Compiled plugins.** A plugin implements capability interfaces from `pact` (routes, config, migrations, middleware, commands, admin screens, translations, mail, jobs). `summer build` reads the application's `summer.yaml`, generates `plugins.gen.go` and `main.go` (`internal/build`) and builds the binary. `party` orders plugins by `Requires()` and runs `Register(app)` then `Boot(app)`; `backpack.App` is the per-instance container (config, typed service registry via `app.Lookup[T]()`, event bus, activated plugins). No runtime plugin loading (stdlib `plugin`, yaegi) without a decision note. +- **Request path.** `surf` assembles plugin routes and named middleware onto a stdlib `net/http` ServeMux; `bouncer` authenticates (JWT, and personal API tokens resolved via cabana); `towel` carries actor/locale through `context.Context`; `wire` keeps JSON byte-compatible with the PHP (Laravel) backend. +- **Admin.** Plugins describe lists, forms, filters and relations in WinterCMS-style YAML (`columns.yaml`, `fields.yaml`). `cabana` compiles them at boot and serves a JSON admin API (swag-annotated, see `admin_openapi.go`); `boardwalk` embeds the Vue 3 SPA from `admin/`, whose build output is committed in `modules/boardwalk/dist`. Before tagging a release that changes `admin/`, rebuild, commit the dist and pass `check-admin-dist.sh` (`.planning/notes/2026-10-07-embedded-admin-release-gate.md`). +- **Data.** `lagoon`: GORM on Postgres only, per-plugin gormigrate migrations, model helpers, attachments (`lagoon/attach`). `conga`: River jobs on the same pool plus the `summer_jobs` record. YAML is parsed with `goccy/go-yaml`, never `gopkg.in/yaml.v3` (archived). +- **Dependencies.** Standard library first. A new dependency needs a research doc or a phase/decision note naming it (`.planning/research/STACK.md` has the decided set and versions). - -## Project Skills +## Sibling repositories -No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, `.github/skills/`, or `.codex/skills/` with a `SKILL.md` index file. - +The meta repo (`..`) holds the framework and its consumers side by side. Consumers `require` a tagged framework version (`vX.Y.Z` tags on `master`) and `replace` it with `../summercms.go` during development: - -## GSD Workflow Enforcement +- `fonoteka.go`: Płytarium application (go.work workspace; app-specific plugins stay in-repo). +- `sm-*-app`: other applications (`sm-grzybyfunkcjonalne-app`, `sm-summercmsio-app`, `sm-bm-app`). +- `sm-*-plugin`: shared core plugins in their own repos, module path = repo path, mounted in apps as git submodules at `plugins//` (`.planning/notes/core-plugins-own-repos.md`). Commit and push the plugin first, then the pointer bump in the app as a separate commit. +- `vue-journal-layer`: public Nuxt layer consumed by site apps as a pnpm git dependency pinned by tag. -Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync. +Framework code never names a consuming application (READMEs, docs, examples use neutral names like `blog` or `acme`). -Use these entry points: -- `/gsd-quick` for small fixes, doc updates, and ad-hoc tasks -- `/gsd-debug` for investigation and bug fixing -- `/gsd-execute-phase` for planned phase work +## Documentation rules -Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it. - +- A change to a module's exported API, config keys, CLI commands or dependencies updates that module's `README.md` and the affected `docs/` pages in the same commit. +- Module README structure: H1 name, one-sentence summary, import line, `**Name:**` mnemonic, then Overview, Features, Usage, API reference, optional Configuration and CLI commands, Dependencies, Testing. The root table's purpose text reuses the summary sentence. +- Every identifier in a README or docs page must exist in the package (the docs checker enforces it). Config keys are not checked automatically; review them by hand. - -## Developer Profile +## MSD workflow -> Profile not yet configured. Run `/gsd-profile-user` to generate your developer profile. -> This section is managed by `generate-claude-profile` -- do not edit manually. - +Work goes through MSD (`/msd-*` commands; the older GSD tooling is retired): `/msd-quick` for small tasks (`.planning/quick/`), `/msd-fast` for trivial edits, `/msd-debug`, and `/msd-discuss-phase` → `/msd-plan-phase` → `/msd-execute-phase` for roadmap phases (`.planning/ROADMAP.md`, `.planning/phases/`). Urgent work is inserted as decimal phases (`/msd-phase --insert`). Don't edit the repo outside an MSD workflow unless the user says so. + +Project rules that override MSD defaults: + +1. **Lean planning.** Few, large plans per phase. Before writing PLAN.md files, present the suggested plan count with a one-line scope each and wait for confirmation. +2. **Unit tests are the last plan of every phase.** Earlier plans may carry smoke tests but are not blocked on coverage. +3. **State through handlers.** Update `.planning/STATE.md` with `msd_run query state.*` handlers, not raw edits. +4. **g15office sync.** Todos and phase milestone tasks are mirrored in g15office (`.planning/G15OFFICE.md`); post a short comment when a phase advances. + +## Commits and concurrency + +- No co-author tags. One logical change per commit; planning docs and code in separate commits. +- Several Claude sessions often work in this repo at once (different phases, quick tasks). Stage files explicitly by path (never `git add -A`), leave other sessions' uncommitted edits (often `STATE.md`, `state.json`) alone, and check for in-flight edits before touching shared files such as cabana. +- Don't push, tag or deploy without the user's go-ahead; deploys are documented per application (for example `../sm-grzybyfunkcjonalne-app/DEPLOY.md`).