- new note .planning/notes/core-plugins-own-repos.md: shared core plugins live in sm-<name>-plugin repos mounted as submodules - 01-CONTEXT deferral points to the note; PROJECT constraint and Key Decisions row - ROADMAP Phase 12 repos and the 12-01 entry, and Phase 12 plans 12-01, 12-02, 12-05 name sm-user-plugin and the submodule commit workflow
120 lines
14 KiB
Markdown
120 lines
14 KiB
Markdown
# Phase 1: Framework kernel foundation - Context
|
|
|
|
**Gathered:** 2026-09-16
|
|
**Status:** Ready for planning
|
|
|
|
<domain>
|
|
## Phase Boundary
|
|
|
|
Phase 1 delivers the framework kernel in `summercms.go` only: config (`compass`), plugin descriptor and registry with the generated import list and `summer build` (`party`), typed event bus (`festival`), app container and service registry (`backpack`), console command framework with rich output (`bonfire`), and a dev watch loop. It is built only as far as Phase 3's `GET /_fonoteka/api/v1/genres` slice will need, per the interleaved-not-sequential rule. No HTTP router (`surf`), no ORM (`lagoon`), no auth (`bouncer`), no i18n (`phrasebook`) and no scaffolding beyond what `summer build` and the `examples/hello` testbed require. Requirements: KERN-01 through KERN-09 and CLI-01.
|
|
|
|
</domain>
|
|
|
|
<decisions>
|
|
## Implementation Decisions
|
|
|
|
### Binary and repo layout
|
|
- **D-01:** Two binaries, xcaddy-style. `summer` is a framework tool installed once (`go install git.golem15.com/golem15/summercms/cmd/summer`) providing `make:plugin`, `plugin:add`, `build` and `dev`. It generates `main.go` and `plugins.gen.go` in an app repo and runs `go build` there. The app binary (for example `fonoteka`) embeds the framework's runtime commands plus plugin commands. The framework never links an app.
|
|
- **D-02:** One command kernel, two entry points. The framework exposes a `bonfire` kernel function that builds the root cobra command with runtime commands (`serve`, `migrate:*`, `queue:work` as later phases add them). The `summer` tool wraps that kernel plus the build and scaffold commands; the generated app `main.go` calls the same kernel. No duplicated command wiring.
|
|
- **D-03:** The Phase 1 testbed is `examples/hello`, kept permanently: a tiny real app inside the framework repo with its own `go.mod`, linked through a root `go.work`, containing two or three trivial plugins (enough to prove Register-before-Boot, `Requires()` ordering and an optional capability interface). `summer build` and `summer dev` are exercised against it, it is the template source for `make:plugin`, and integration tests run it.
|
|
- **D-04:** Canonical module path is `git.golem15.com/golem15/summercms` (go.mod as committed; origin added and master pushed). Research docs that say `github.com/golem15/summercms` get a one-line correction. Apps require this path and use `go.work` or a local `replace` during development.
|
|
- **D-05:** Pin the toolchain: add `toolchain go1.27.x` to go.mod so every developer and agent builds with the same compiler. Generated app `go.mod` files copy the same line.
|
|
|
|
### Config layout and keys
|
|
- **D-06:** Plugin config keys use the bare plugin ID as namespace: `golem15.fonoteka.posts_per_page`. App-level sections (`app`, `database`) stay top-level. Rule: a top-level key containing a dot-separated vendor prefix is a plugin namespace. This follows the summer-compass design and reads like WinterCMS's `golem15.fonoteka::key`; STACK.md's `plugins.<name>.*` sketch is superseded.
|
|
- **D-07:** File layout follows summer-compass: `config/<section>.yaml` for base config, `config/env/<env>/<section>.yaml` for environment overlays, filename becomes the section key. Plugins ship `config/*.yaml` inside their module (embedded FS via the `HasConfig` capability) and are loaded under their namespace. Deep merge, not top-level replace.
|
|
- **D-08:** Environment variables use the `SUMMER_` prefix with `__` as the path separator: `SUMMER_DATABASE__HOST` maps to `database.host`, `SUMMER_GOLEM15__FONOTEKA__POSTS_PER_PAGE` maps to `golem15.fonoteka.posts_per_page`. Single underscores stay literal so snake_case leaf keys are unambiguous. A `.env` file in the app root is loaded when present and never overrides variables already set in the real environment.
|
|
- **D-09:** The full compass override surface is carried over: in-memory `Set` (highest priority), `Persist` writing `config/env/<env>/overrides.yaml`, and `Reload` that clears runtime overrides and re-reads disk. Priority, highest wins: runtime Set, persisted overrides, env vars, env overlay file, base file, plugin defaults.
|
|
|
|
### Plugin lifecycle and events
|
|
- **D-10:** A plugin whose `Requires()` names an unregistered plugin fails boot with an error naming both the plugin and the missing dependency; the binary exits non-zero. Cycles in `Requires()` fail the same way. No warn-and-disable behavior.
|
|
- **D-11:** Optional integration (KERN-05) has two mechanisms, both free of imports of the optional plugin's package: `app.HasPlugin("golem15.translate")` for coarse checks mirroring PHP `class_exists` guards, and a typed lookup on the `backpack` container returning `(value, ok)` for interface-based integration where the optional plugin publishes a service under a `pact` interface.
|
|
- **D-12:** Event listeners have an optional integer priority, default 0. Higher priority runs first; ties keep registration order (stable). Both a plain `Listen` and a priority-taking variant exist. This makes fire-until-handled deterministic and lets ported PHP listeners keep their priorities.
|
|
- **D-13:** Listener error handling per dispatch mode. Fire-and-forget: run every listener, return the joined errors (`errors.Join`). Fire-and-collect: run every listener, return the partial merged payload together with the joined error. Fire-until-handled: the first error stops dispatch and is returned. Panics inside a listener are recovered and wrapped as an error carrying the listener's owning plugin ID.
|
|
|
|
### CLI and dev loop
|
|
- **D-14:** Rich output widgets are hand-rolled on the standard library plus `golang.org/x/term` for TTY detection and raw mode: braille spinner, gradient progress bar, box-drawing table, ask/confirm/choice/secret prompts, with the non-TTY degradation table from the bonfire plan (static `[...]` line, `[N/M] pct%` updates, tab-separated tables, stdin prompts, `NO_COLOR`/`FORCE_COLOR`/`TERM=dumb` respected). No Charm stack, no pterm.
|
|
- **D-15:** Command names are colon-style like WinterCMS/Artisan: `summer fonoteka:reindex`, `summer make:plugin`, `summer migrate:rollback`. Plugin commands must carry a namespace prefix; kernel commands use their group prefix. cobra accepts colons in `Use`.
|
|
- **D-16:** The dev watch loop is built into the tool as `summer dev` using `fsnotify`: watch the app workspace (Go sources, config, plugin manifests), run the same build step as `summer build`, restart the app binary with a debounce, and print the measured rebuild latency after every cycle. No dependency on an external `air` install; STACK.md's air recommendation is superseded for this purpose.
|
|
- **D-17:** Plugins implement a `bonfire.Command` interface (name, description, flag and argument definition, `Run(ctx, Input, Output) error`). The kernel wraps each into a `cobra.Command`. Output is an injected value so tests capture it and non-TTY degradation is decided once, not per command.
|
|
|
|
### Claude's Discretion
|
|
- Event identity: typed Go struct events (generic `Listen[T]`), as sketched in ARCHITECTURE.md Pattern 3a; no string-named events. Dispatch is synchronous on the caller's goroutine; async fan-out is a listener's own concern.
|
|
- The event bus and the service registry are fields of the app instance, not package globals, so tests build isolated apps. Only `party`'s `init()` self-registration uses a package-level ordered slice, drained into the app at boot.
|
|
- Plugin IDs are lowercase `vendor.plugin` (`golem15.fonoteka`); matching is exact.
|
|
- Environment detection reads `SUMMER_ENV`, defaulting to `production` as compass did; an explicit constructor argument overrides it.
|
|
- Typed section loading uses koanf's `Unmarshal` into a struct with a `koanf` tag; dot-path getters mirror the compass API (`String`, `Int`, `Bool`, `Has`, plus a `Lookup` returning ok).
|
|
- `plugins.gen.go` is regenerated from an explicit ordered manifest (a `summer.yaml` or the `go.work` use list), never from a map, per the init-order pitfall. `summer build` measures and prints build time.
|
|
- What `make:plugin` scaffolds in Phase 1: the minimum that compiles and registers (go.mod, plugin.go with the required interface, an empty config dir). Model, migration, controller and command stubs belong to Phase 4 (CLI-02).
|
|
- Package layout follows ARCHITECTURE.md's list (`pact`, `towel`, `compass`, `festival`, `backpack`, `party`, `bonfire`, `cmd/summer`, `examples/hello`); `internal/` is used for build codegen helpers not meant as public API.
|
|
|
|
</decisions>
|
|
|
|
<canonical_refs>
|
|
## Canonical References
|
|
|
|
**Downstream agents MUST read these before planning or implementing.**
|
|
|
|
### Kernel architecture and pitfalls
|
|
- `.planning/research/ARCHITECTURE.md` §Component Responsibilities, §Recommended Project Structure, §Pattern 1, §Pattern 2, §Pattern 3, §Anti-Patterns, §Framework vs Application Repo Boundary, §Suggested Build Order — plugin interface sketch, capability interfaces, generated import list, container usage rules, package names. Note: it says `github.com/golem15/summercms` and `cmd/summer` lives in the app repo; D-01, D-02 and D-04 supersede those two points.
|
|
- `.planning/research/PITFALLS.md` §Pitfall 1 (bottom-up kernel), §Pitfall 2 (globals vs context), §Init-order plugin registration nondeterminism, §Dev rebuild loop friction, §Pitfall-to-Phase Mapping — the tests and acceptance criteria Phase 1 must include (`go test -race`, reordered-input test, measured rebuild latency).
|
|
- `.planning/research/SUMMARY.md` §Architecture Approach, §Phase 1, §Research Flags — kernel phase is flagged as safe to plan without deep research.
|
|
- `.planning/research/STACK.md` §Core Technologies (koanf, cobra, goccy/go-yaml versions), §Config: koanf + YAML — the `plugins.<name>.*` sketch there is superseded by D-06; air for the watch loop is superseded by D-16.
|
|
- `.planning/research/go-ecosystem.md` §2 Plugin architecture — the Caddy/xcaddy verdict and the hand-rolled event bus verdict.
|
|
|
|
### Design carry-overs from the Scala modules
|
|
- `../modules/summer-compass/README.md` — config file layout, environment layering priority, plugin namespaces, set/persist/reload API and thread-safety notes; port the design, not the code.
|
|
- `../modules/summer-bonfire/README.md` and `../modules/summer-bonfire/PLAN.md` §Input trait, §Output trait, §Command trait, §CommandRunner, §Non-TTY Graceful Degradation — the command and output interfaces and the degradation table to port.
|
|
- `../IDEA_LIB_NAMES.md` — Go package names (backpack, compass, festival, party, bonfire, pact, towel).
|
|
|
|
### Project-level decisions
|
|
- `.planning/PROJECT.md` §Constraints, §Key Decisions — stdlib-first rule, two-repo split, compiled plugins only.
|
|
- `.planning/REQUIREMENTS.md` KERN-01..KERN-09, CLI-01 — the phase's requirement text.
|
|
- `.planning/notes/why-go-not-scala.md` — the bottom-up failure mode this phase must not repeat.
|
|
- `CLAUDE.md` (repo root) §GSD workflow rules — lean planning, plan-count checkpoint, unit tests as the last plan, `go vet` and `go test ./...` green at every commit.
|
|
|
|
</canonical_refs>
|
|
|
|
<code_context>
|
|
## Existing Code Insights
|
|
|
|
### Reusable Assets
|
|
- No Go code exists yet; the repo holds `go.mod` (module `git.golem15.com/golem15/summercms`, `go 1.27.0`), `README.md`, `CLAUDE.md` and `.planning/`. Everything in Phase 1 is greenfield.
|
|
- Design assets to port: summer-compass (config layering, namespaces, overrides) and summer-bonfire (command interfaces, widgets, degradation table). Their READMEs are the spec; their Scala code is not ported.
|
|
|
|
### Established Patterns
|
|
- Stdlib-first: new dependencies in Phase 1 are limited to koanf v2 with its file, env and confmap providers and yaml parser, goccy/go-yaml, cobra, golang.org/x/term, fsnotify, and testify for tests. Anything else needs a decision note.
|
|
- Request state travels only in `context.Context` with unexported struct key types and exported accessor functions.
|
|
- Container used only at Register and Boot for constructor injection; application code holds typed fields.
|
|
|
|
### Integration Points
|
|
- `examples/hello` is the first consumer of every kernel package and the target of `summer build` and `summer dev`.
|
|
- Phase 3 will add `surf`, `lagoon` and `bouncer` on top of `backpack` and `party`; the capability interfaces they need (`HasRoutes`, `HasModels`, `HasMigrations`) may be declared in `pact` now but must not be implemented before Phase 3 pulls them.
|
|
- Phase 2's parity harness has no dependency on Phase 1 and runs in parallel.
|
|
|
|
</code_context>
|
|
|
|
<specifics>
|
|
## Specific Ideas
|
|
|
|
- "It should feel like WinterCMS's drop-in loop": `summer dev` prints rebuild latency every cycle so the friction pitfall is visible, and the phase's acceptance includes a measured rebuild time for a single-plugin change in `examples/hello`.
|
|
- Command spelling must let ported PHP commands keep their names (`fonoteka:reindex`, `fonoteka:oauth-client`).
|
|
- The user asked whether `__` is common in Go; answer recorded: single underscore is more common in Go libraries but ambiguous with snake_case keys, `__` is the cross-ecosystem convention (ASP.NET Core, Rust config, dynaconf) and was chosen deliberately.
|
|
|
|
</specifics>
|
|
|
|
<deferred>
|
|
## Deferred Ideas
|
|
|
|
- Scaffolding of model, migration, command, job and admin-controller stubs by `make:*` commands — Phase 4 (CLI-02). Phase 1's `make:plugin` produces only the compiling minimum.
|
|
- Admin config editor consuming `Persist` overrides — later admin phase; Phase 1 only provides the API.
|
|
- Extraction of shared stack plugins into their own repos — only when a second app (keios.eu) needs one. Superseded 2026-10-02 by `.planning/notes/core-plugins-own-repos.md` (golem15.user moved to sm-user-plugin).
|
|
- One-line corrections to research docs (module path, air, `plugins.<name>` sketch) — do as a docs commit alongside Phase 1 planning, not as code.
|
|
|
|
</deferred>
|
|
|
|
---
|
|
|
|
*Phase: 01-framework-kernel-foundation*
|
|
*Context gathered: 2026-09-16*
|