14 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-cli-scaffolding-i18n-and-mail | 01 | execute | 1 |
|
true |
|
|
Phase Goal
As a plugin developer, I want to generate compiling plugin artifacts, resolve translated strings, and send registered mail, so that I can port WinterCMS plugins into one SummerCMS binary.
Generate every Phase 4 plugin artifact through the summer CLI and compile the resulting plugin.Purpose: A porting agent can move each WinterCMS artifact into its corresponding Go package without hand-maintaining a plugin registry. Output: six make commands, embedded stub templates, generated registry, and a build-time models leaf check.
<execution_context> @/home/jin/.codex/get-shit-done/workflows/execute-plan.md @/home/jin/.codex/get-shit-done/templates/summary.md </execution_context>
@CLAUDE.md @.planning/ROADMAP.md @.planning/REQUIREMENTS.md @.planning/phases/04-cli-scaffolding-i18n-and-mail/04-CONTEXT.md @.planning/phases/04-cli-scaffolding-i18n-and-mail/04-RESEARCH.md @.planning/phases/04-cli-scaffolding-i18n-and-mail/04-PATTERNS.md @.planning/notes/plugin-layout-winter-directories.md @.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md @.planning/phases/03-first-vertical-slice-genres-end-to-end/03-CONTEXT.md Existing: build.MakePlugin(ctx, startDir, id) (string, error); build.AddPlugin(ctx, startDir, pluginDir) error; build.App(ctx, appDir, out) error. pluginIDFromGo(path) reads a literal Plugin.ID return. pact.HasMigrations.Migrations() returns []*gormigrate.Migration; pact.HasCommands.Commands() returns []bonfire.Command; pact.HasModels.Models() returns []any. A scaffolded plugin implements party.Plugin with ID, Requires, Register, Boot. The app manifest lists plugin ID and module path. Keep the current plugin:add and go.work behavior.New contract to define before generated consumers: pact.JobArgs has Kind() string; pact.Job has Work(context.Context, pact.JobArgs) error; pact.HasJobs returns []pact.Job; pact.AdminController has ID() string, ModelName() string, ConfigDir() string; pact.HasAdminControllers returns []pact.AdminController. Add pact.HasLang.LangFS() fs.FS and pact.HasMailTemplates with MailTemplatesFS() fs.FS, MailTemplates() []string, MailLayouts() map[string]string for Plans 02 and 03. Generated root plugin.go calls generatedModels, generatedMigrations, generatedCommands, generatedJobs, and generatedAdminControllers from registry.gen.go; existing plugins opt in by appending those same accessors.
Task 1: Prove and create the compiling Winter-shaped plugin path internal/build/build_test.go, internal/build/scaffold.go, internal/build/registry.go, internal/build/stubs/plugin.tmpl, internal/build/stubs/registry.tmpl, pact/capabilities.go internal/build/build_test.go; internal/build/scaffold.go; internal/build/build.go; internal/build/manifest.go; pact/capabilities.go; examples/hello/plugins/base/plugin.go; examples/hello/go.mod; .planning/notes/plugin-layout-winter-directories.md; 04-CONTEXT.md D-10, D-12, D-16 Start with a failing end-to-end TestScaffoldPluginSmoke in internal/build/build_test.go that copies examples/hello, creates and adds a plugin, then builds it. Per D-16, migrate make:plugin's source and go.mod string builders to embedded text/template under internal/build/stubs: plugin.tmpl defines separately executable named templates for plugin.go, routes.go, each leaf doc.go, and go.mod; registry.tmpl defines the registry.go template. Parse embedded files once and execute each named definition, formatting rendered Go before writes. Per D-10, create root routes.go and a doc.go in each leaf package, plus nonhidden embed-compatible lang and mail assets. Per D-12, generate deterministic registry.gen.go with empty accessors. Define the pact contracts in the interfaces block so generated plugin.go compiles and its capability methods can return registry slices. Preserve party.Register, the current go.mod/toolchain/replacement flow, and hand-written plugin.go ownership. Record the initial red test result, then make it green before committing so every commit keeps root go vet and go test ./... green. go test ./internal/build -run TestScaffoldPluginSmoke -short -count=1 && go vet ./... && go test ./... A new plugin has the Winter directory shape, working embedded assets and registry.gen.go; build succeeds before any individual artifact is added. TestScaffoldPluginSmoke passes for plugin creation and existing scaffold behavior remains green. Task 2: Generate model, migration, and command slices internal/build/scaffold.go, internal/build/registry.go, internal/build/stubs/artifacts.tmpl, cmd/summer/main.go, cmd/summer/main_test.go, internal/build/build_test.go internal/build/scaffold.go; internal/build/registry.go; internal/build/stubs/plugin.tmpl; internal/build/stubs/registry.tmpl; cmd/summer/main.go; cmd/summer/main_test.go; internal/build/build_test.go; pact/capabilities.go; lagoon/migrations.go; examples/hello/plugins/greeter/plugin.go; 04-CONTEXT.md D-12, D-13, D-17 Add make:model, make:migration, and make:command to bonfire with the D-17 argument forms and pluginIDFromGo inference. Validate identifier, path containment, duplicate artifact, and module ownership before writes. In artifacts.tmpl, define separately executable named templates for model.go, migration.go, and command.go. Per D-13, render model table vendor_plugin_names with timestamps; emit an explicit gormigrate Up/Down create-table migration by default, with --no-migration omitting only that file. A standalone make:migration creates an ordered gormigrate entry; make:command creates a console/ bonfire.Command with plugin-prefixed colon name. Regenerate sorted slices and imports atomically, preserving handwritten plugin.go per D-12. Add TestScaffoldCoreArtifacts to build/vet these artifacts and check repeated generation leaves registry bytes unchanged. go test ./internal/build ./cmd/summer -run 'TestScaffoldCoreArtifacts|TestToolCommandNames' -short -count=1 && go vet ./... && go test ./... The three commands create compiling artifacts and expose them through the generated registry; --no-migration omits only the model migration. A temporary generated plugin builds and vets after model, migration, and command creation. Task 3: Generate jobs and admin controllers, then enforce model imports internal/build/scaffold.go, internal/build/registry.go, internal/build/leaf.go, internal/build/build.go, internal/build/stubs/artifacts.tmpl, cmd/summer/main.go, cmd/summer/main_test.go, internal/build/build_test.go internal/build/scaffold.go; internal/build/registry.go; internal/build/stubs/artifacts.tmpl; internal/build/build.go; internal/build/manifest.go; cmd/summer/main.go; cmd/summer/main_test.go; internal/build/build_test.go; pact/capabilities.go; .planning/notes/plugin-layout-winter-directories.md; 04-CONTEXT.md D-11, D-14, D-15 Add make:job and make:admin-controller with the same validated argument resolution. In artifacts.tmpl, add separately executable named definitions for job.go, admin_controller.go, fields.yaml, and columns.yaml. Per D-15, job args implement pact.JobArgs.Kind and the job implements pact.Job.Work, returning an error for an unexpected args type; no River import. Per D-14, admin controller implements the pact interface and has controllers//fields.yaml and columns.yaml in the Winter shape. Extend registry slices for both. Per D-11, in build.App inspect Go imports in each manifest plugin's models/ package before invoking go build; reject an import of its own classes/, controllers/, console/, jobs/, middleware/, or updates/ package with plugin ID, source file and import path. Keep the check limited to this rule, including manually written models. Complete the smoke case for all six artifact types and explicit leaf rejection. go test ./internal/build ./cmd/summer -run 'TestScaffoldAllArtifacts|TestModelsLeaf|TestToolCommandNames' -short -count=1 && go vet ./... && go test ./... All six make commands generate buildable, vet-clean output; a sibling import in models fails before go build with the required diagnostic. The full scaffold smoke case passes, registry output is deterministic, and handwritten plugin.go remains byte-identical.<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| CLI input to filesystem | Plugin IDs and artifact names choose generated paths and source identifiers. |
| Plugin source to build subprocess | User-written imports enter the compiled plugin dependency graph. |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-04-01 | Tampering | make:* path/source generation | mitigate | Validate Go identifiers, module path and underRoot containment before writing; reject duplicates; format before atomic registry rename. |
| T-04-02 | Tampering | models/ imports | mitigate | Parse imports before build and fail with plugin ID, file, and sibling import; test an injected violation. |
| T-04-03 | Denial of service | repeated make:* | mitigate | Refuse duplicate artifact names and keep registry generation deterministic/idempotent. |
| T-04-SC | Tampering | Go module resolution | mitigate | Use only research-named existing dependencies for this plan; no npm, pip, or cargo install. |
| </threat_model> |
<success_criteria> CLI-02 is observable through six commands; each generated artifact compiles and vets; registry.gen.go is stable and auto-wired; a models/ sibling import fails with an actionable message. No file outside summercms.go is edited. </success_criteria>
Create .planning/phases/04-cli-scaffolding-i18n-and-mail/04-01-SUMMARY.md when done.