- 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
14 KiB
Phase 1: Framework kernel foundation - Context
Gathered: 2026-09-16 Status: Ready for planning
## Phase BoundaryPhase 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.
Binary and repo layout
- D-01: Two binaries, xcaddy-style.
summeris a framework tool installed once (go install git.golem15.com/golem15/summercms/cmd/summer) providingmake:plugin,plugin:add,buildanddev. It generatesmain.goandplugins.gen.goin an app repo and runsgo buildthere. The app binary (for examplefonoteka) 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
bonfirekernel function that builds the root cobra command with runtime commands (serve,migrate:*,queue:workas later phases add them). Thesummertool wraps that kernel plus the build and scaffold commands; the generated appmain.gocalls 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 owngo.mod, linked through a rootgo.work, containing two or three trivial plugins (enough to prove Register-before-Boot,Requires()ordering and an optional capability interface).summer buildandsummer devare exercised against it, it is the template source formake: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 saygithub.com/golem15/summercmsget a one-line correction. Apps require this path and usego.workor a localreplaceduring development. - D-05: Pin the toolchain: add
toolchain go1.27.xto go.mod so every developer and agent builds with the same compiler. Generated appgo.modfiles 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'sgolem15.fonoteka::key; STACK.md'splugins.<name>.*sketch is superseded. - D-07: File layout follows summer-compass:
config/<section>.yamlfor base config,config/env/<env>/<section>.yamlfor environment overlays, filename becomes the section key. Plugins shipconfig/*.yamlinside their module (embedded FS via theHasConfigcapability) 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__HOSTmaps todatabase.host,SUMMER_GOLEM15__FONOTEKA__POSTS_PER_PAGEmaps togolem15.fonoteka.posts_per_page. Single underscores stay literal so snake_case leaf keys are unambiguous. A.envfile 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),Persistwritingconfig/env/<env>/overrides.yaml, andReloadthat 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 inRequires()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 PHPclass_existsguards, and a typed lookup on thebackpackcontainer returning(value, ok)for interface-based integration where the optional plugin publishes a service under apactinterface. - D-12: Event listeners have an optional integer priority, default 0. Higher priority runs first; ties keep registration order (stable). Both a plain
Listenand 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/termfor 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=dumbrespected). 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 inUse. - D-16: The dev watch loop is built into the tool as
summer devusingfsnotify: watch the app workspace (Go sources, config, plugin manifests), run the same build step assummer build, restart the app binary with a debounce, and print the measured rebuild latency after every cycle. No dependency on an externalairinstall; STACK.md's air recommendation is superseded for this purpose. - D-17: Plugins implement a
bonfire.Commandinterface (name, description, flag and argument definition,Run(ctx, Input, Output) error). The kernel wraps each into acobra.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'sinit()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 toproductionas compass did; an explicit constructor argument overrides it. - Typed section loading uses koanf's
Unmarshalinto a struct with akoanftag; dot-path getters mirror the compass API (String,Int,Bool,Has, plus aLookupreturning ok). plugins.gen.gois regenerated from an explicit ordered manifest (asummer.yamlor thego.workuse list), never from a map, per the init-order pitfall.summer buildmeasures and prints build time.- What
make:pluginscaffolds 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.
<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 saysgithub.com/golem15/summercmsandcmd/summerlives 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 — theplugins.<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.mdand../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.mdKERN-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 vetandgo test ./...green at every commit.
</canonical_refs>
<code_context>
Existing Code Insights
Reusable Assets
- No Go code exists yet; the repo holds
go.mod(modulegit.golem15.com/golem15/summercms,go 1.27.0),README.md,CLAUDE.mdand.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.Contextwith 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/hellois the first consumer of every kernel package and the target ofsummer buildandsummer dev.- Phase 3 will add
surf,lagoonandbounceron top ofbackpackandparty; the capability interfaces they need (HasRoutes,HasModels,HasMigrations) may be declared inpactnow 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>
## Specific Ideas- "It should feel like WinterCMS's drop-in loop":
summer devprints 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 inexamples/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.
- Scaffolding of model, migration, command, job and admin-controller stubs by
make:*commands — Phase 4 (CLI-02). Phase 1'smake:pluginproduces only the compiling minimum. - Admin config editor consuming
Persistoverrides — 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.
Phase: 01-framework-kernel-foundation Context gathered: 2026-09-16