diff --git a/.planning/phases/34-plugin-marketplace/34-CONTEXT.md b/.planning/phases/34-plugin-marketplace/34-CONTEXT.md new file mode 100644 index 00000000..55c50931 --- /dev/null +++ b/.planning/phases/34-plugin-marketplace/34-CONTEXT.md @@ -0,0 +1,124 @@ +# Phase 34: Plugin marketplace - Context + +**Gathered:** 2026-10-29 +**Status:** Ready for planning + + +## Phase Boundary + +People browse the public Golem15 plugins at summercms.io/marketplace and add one to an application with `summer plugin:install `; the same command installs private plugins from a git URL. Each public `sm-*-plugin` repo carries a generated `plugin.yaml`; summercms.io ingests manifests, READMEs and tags into a registry with a public read-only API; the admin SPA gains a read-only Plugins screen (Installed + Marketplace tabs). v1 has no paid plugins, no developer registration, no themes and no installs from the admin panel (all settled in `.planning/notes/2026-10-29-plugin-marketplace.md`). + +Discussion added one capability to the roadmap text: `summer plugin:update`, so the admin's "update available" badge has a matching command (D-10). + + + + +## Implementation Decisions + +### Manifest: plugin.yaml is generated, never hand-edited +- **D-01:** `plugin.yaml` is generated by a new command `summer plugin:manifest` run in a plugin repo. `plugin.go` stays as it is (no change to `party.Plugin`, no breaking change to core plugins). +- **D-02:** Every field is derived from the repo: `id` and `requires` from `plugin.go` (AST: the literals returned by `ID()` / `Requires()`), module path from `go.mod`, minimum framework version and minimum required-plugin versions from `go.mod`'s `require` lines, `name` and `description` from the README's H1 and its one-sentence summary (the standard module README structure). A plugin whose `ID()`/`Requires()` are not literal returns is an error that names the function. +- **D-03:** Optional `--name="…"` / `--description="…"` override the README-derived text. Overrides persist in `plugin.yaml` and survive regeneration until changed or cleared (exact storage is planner's choice, e.g. an `overrides:` block). +- **D-04:** `summer plugin:manifest --check` fails when the committed `plugin.yaml` is stale. It runs in each plugin repo's tests (a pact/test helper or `go test`-runnable check) and in the tag/changelog step (`scripts/changelog-post.py publish … --repo`). `plugin.yaml` is committed. — **Reversibility:** costly — the format is read by the registry ingester, `plugin:install` for git URLs, and every public plugin repo; changing it means regenerating all repos and migrating the ingester. +- **D-05:** Version constraints are Go-style minimums: `framework: v0.4.0`, `requires: {golem15.translate: v0.2.0}`. No semver ranges (Go's minimal version selection; a major bump changes the module path). + +### Runtime metadata for the Installed tab +- **D-06:** `summer build` reads each plugin's `plugin.yaml` and bakes name and description into `plugins.gen.go`. Plugins without a `plugin.yaml` show only their ID. +- **D-07:** Version: module installs use the version from `go.mod`/build info; submodule or local-`replace` plugins get `git describe --tags` run in the plugin directory at build time (e.g. `v0.3.1-4-gabc123`), also baked into `plugins.gen.go`. + +### Install resolution +- **D-08:** `summer plugin:install golem15.journal` picks the newest tag whose minimum framework version the app's `go.mod` already satisfies; `golem15.journal@v0.3.0` pins. If only tags needing a newer framework exist, it fails and names the framework version to move to (it does not bump the framework silently). +- **D-09:** Missing required plugins are resolved transitively, the full list of additions is printed, and the user confirms; `--yes` skips the prompt and non-interactive runs require `--yes`. Dependencies install in the same mode as the requested plugin (module, or submodule under `--source`). +- **D-10:** `summer plugin:update [id]` is in scope: module installs move to the newest compatible tag (`go get`), submodule installs are checked out at the newest compatible tag. Without an ID it updates all plugins it can resolve. +- **D-11:** Git-URL installs are submodule-only in v1; no `--module`. Close the open question in `.planning/research/questions.md` as "not in v1". — **Reversibility:** reversible — adding `--module` later is additive. +- **D-12:** A git-URL plugin's own repo is never sent to summercms.io, but its required plugin IDs (e.g. `golem15.translate`) are resolved through the registry like any ID install. `--offline` disables registry calls and then requires the dependencies to be installed already. +- Both modes finish with the same registration `plugin:add` does (`summer.yaml`, `go.mod`, `go.work` where applicable), and module-installed plugins must work with the build steps that currently inspect plugin directories (`internal/build/leaf.go`, `internal/build/registry.go`) — resolve their directory from the module cache. + +### Registry ingestion and API (summercms.io, `golem15.summercms` site plugin) +- **D-13:** Pull ingester: given a repo URL it clones anonymously, reads tags, each tag's `plugin.yaml`, and the README. It runs as a River periodic job (around hourly) and on an authenticated "ingest now" endpoint that `changelog-post.py publish --repo` calls. No forge webhooks. +- **D-14:** The listing is admin-curated: a summercms.io admin adds a repo URL; the ingester fills the facts. A plugin is published only once it has a tag with a valid `plugin.yaml`; tags without one are skipped. +- **D-15:** Every `vX.Y.Z` tag is stored as a version row with that tag's framework and plugin minimums (needed by D-08). README is stored for the latest tag only. +- **D-16:** Public read-only JSON API, no auth, open CORS, cache headers: `GET /api/registry/v1/plugins?q=&category=` (list/search), `GET /api/registry/v1/plugins/{id}` (detail incl. README as markdown and rendered HTML), `GET /api/registry/v1/plugins/{id}/versions`. Consumed by the Nuxt marketplace pages, the CLI and cabana. — **Reversibility:** one-way — once released `summer` binaries call `/api/registry/v1`, its shape cannot change without a v2 path. +- Presentation fields (category, featured, screenshots, icon, sort order) are edited in the summercms.io admin (from the decision note). + +### Admin Plugins screen +- **D-17:** The SPA talks only to its own backend: cabana gets swag-annotated admin endpoints that call the registry with a timeout and short cache, and compute "update available" in Go against installed versions. No CSP/CORS change in the admin. +- **D-18:** Config key `plugins.registry_url` (default `https://summercms.io`); empty disables registry calls, which hides the Marketplace tab and update badges while Installed still works. The CLI reads the same setting (allows a staging or self-hosted registry). Exact key name is planner's choice if it clashes with existing config. +- **D-19:** New permission (a `system.plugins.view`-style key in cabana's permission set), granted to superusers by default. + +### Claude's Discretion +- Marketplace page layout and visuals on `vue-summercmsio-app` (list, search, category, detail); `/msd-ui-phase 34` can produce a UI contract if wanted. +- Storage of D-03 overrides inside `plugin.yaml`. +- Registry DB schema, ingester error handling, cache durations, periodic interval. +- Whether older tags (before `plugin.yaml` existed) get backfilled; default per D-14 is to skip them. +- Exact CLI output/prompt wording and the semantics of `plugin:update` with no ID when some plugins can't be resolved. + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Marketplace decisions +- `.planning/notes/2026-10-29-plugin-marketplace.md` — v1 scope, listing data split, install-mode table, read-only admin screen, out-of-scope list +- `.planning/research/questions.md` — "Is `--module` for git-URL installs worth supporting?" (answered by D-11: not in v1) +- `.planning/ROADMAP.md` § Phase 34 — goal, repos, success criteria + +### Plugins and repos +- `.planning/notes/core-plugins-own-repos.md` — `sm-*-plugin` repos, module path = repo path, submodules at `plugins//`, commit plugin first then pointer bump +- `.planning/notes/why-go-not-scala.md` and `.planning/research/FEATURES.md` (anti-features) — no runtime plugin loading +- `.planning/research/STACK.md` — decided dependency set (YAML via `goccy/go-yaml`; any new dependency, e.g. a markdown renderer or git library, needs a note) + +### Release flow +- `../sm-summercmsio-app/DEPLOY.md` § "Posting to the changelog" and `../sm-summercmsio-app/scripts/changelog-post.py` — plugin tag step that D-04 and D-13 hook into +- `.planning/notes/2026-10-07-embedded-admin-release-gate.md` — rebuild and commit `modules/boardwalk/dist` when `admin/` changes + + + + +## Existing Code Insights + +### Reusable Assets +- `cmd/summer/main.go` `addPluginCommand()` and `internal/build.AddPlugin` — the registration step both install modes end with. +- `internal/build/manifest.go` — `Manifest`, `LoadManifest`/`SaveManifest`, `ValidatePluginID`, `validateModulePath`: reuse for `summer.yaml` edits and ID validation in `plugin.yaml`. +- `internal/build/build.go` `generatePluginsGen` — where D-06/D-07 metadata gets baked in. +- `modules/party` `Activated` — the installed list cabana's Installed tab reads. +- `conga` (River periodic jobs) — the ingester schedule on summercms.io. +- `scripts/check-admin-openapi.sh` — swag → `admin/openapi/admin.json` → `admin/src/api/schema.d.ts` for the new cabana endpoints. + +### Established Patterns +- `party.Plugin` is only `ID()`, `Requires()`, `Register`, `Boot`; plugin metadata does not exist at runtime today, hence D-06. +- `plugin:add` assumes a local directory + `replace`; module installs (no `replace`) are new and must satisfy `leaf.go`/`registry.go` directory inspection. +- Commands are `bonfire.Command` with Long text, Examples and Args/Flags; docs checker (`summer docs:build --check`) verifies command names in docs. +- New admin screens follow the cabana admin API + boardwalk SPA split; the dist is committed. + +### Integration Points +- `../sm-summercmsio-app/plugins/golem15/summercms` — currently routes/static only (docs, `/new`, `/update`, site tree); gains models, migrations, admin (presentation fields, repo list), registry API routes and the ingester job. +- `../sm-summercmsio-app/vue-summercmsio-app` — marketplace pages consuming D-16. +- Every public `../sm-*-plugin` repo — commit generated `plugin.yaml` plus the `--check` test (plugin commits first, app pointer bumps separately). + + + + +## Specific Ideas + +- The user wanted hand-editable text despite generation, hence `--name` / `--description` overrides on `plugin:manifest`. +- Modelled on octobercms.com/marketplace for browsing; install is CLI only. + + + + +## Deferred Ideas + +- `--module` for git-URL (private) installs. Revisit if submodule-only proves limiting. +- Already out of scope per the decision note: paid plugins, licensing, developer accounts, third-party submissions, themes, installing from the admin panel. + +### Reviewed Todos (not folded) +- 17 todos matched on keywords only (e.g. gate scripts naming apps, OIDC parity, MFA findings, menu plugin port). None concern the marketplace; none folded. + + + +--- + +*Phase: 34-plugin-marketplace* diff --git a/.planning/phases/34-plugin-marketplace/34-DISCUSSION-LOG.md b/.planning/phases/34-plugin-marketplace/34-DISCUSSION-LOG.md new file mode 100644 index 00000000..dfcfa15c --- /dev/null +++ b/.planning/phases/34-plugin-marketplace/34-DISCUSSION-LOG.md @@ -0,0 +1,81 @@ +# Phase 34: Plugin marketplace - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-10-29 +**Phase:** 34-plugin-marketplace +**Areas discussed:** plugin.yaml vs plugin.go, Install resolution, Registry ingestion & API, Admin ↔ registry link + +--- + +## plugin.yaml vs plugin.go + +| Option | Description | Selected | +|--------|-------------|----------| +| Both stay, build checks | plugin.go unchanged; build/validator fail on mismatch | | +| plugin.go reads plugin.yaml | embed and derive ID()/Requires(); edits every plugin.go | | +| Generate plugin.yaml | command writes it from plugin.go plus extra fields | ✓ | + +**Field sources:** derive everything from the repo (plugin.go AST, go.mod, README H1 + summary). **Notes:** user asked for optional `--name` / `--description` overrides for hand-edited text. + +**When generated:** `summer plugin:manifest` + `--check` before tagging (selected) vs generated only at ingestion. + +| Runtime meta | Selected | +|---|---| +| Baked into plugins.gen.go by summer build | ✓ | +| Optional pact Details() capability | | + +| Local version | Selected | +|---|---| +| git describe at build | ✓ | +| Show "local" | | +| version field in plugin.yaml | | + +| Constraint format | Selected | +|---|---| +| Go-style minimum | ✓ | +| Semver ranges | | + +--- + +## Install resolution + +| Question | Options | Selected | +|---|---|---| +| Version pick | Newest compatible + @ pins / Always latest tag | Newest compatible + @ pins | +| Missing deps | Install transitively after listing / Fail with command | Install transitively after listing | +| Update command | plugin:update in scope / Defer | plugin:update in scope | +| `--module` for git URLs | Let research decide / No, submodule only / Yes with diagnostics | No, submodule only | +| Git-URL plugin's public deps | Registry for IDs only / Never contact, fail / From go.mod alone | Registry for IDs only | + +--- + +## Registry ingestion & API + +| Question | Options | Selected | +|---|---|---| +| Trigger | Pull job + changelog step / Forge webhook / changelog-post.py push only | Pull job + changelog step | +| Listing | Admin-curated repo list / Auto-discover sm-*-plugin | Admin-curated repo list | +| Versions | Every tag + its plugin.yaml / Latest only | Every tag + its plugin.yaml | +| API shape | Small read-only JSON / Static index.json | Small read-only JSON | + +--- + +## Admin ↔ registry link + +| Question | Options | Selected | +|---|---|---| +| Path | Server-side via cabana / Browser calls summercms.io | Server-side via cabana | +| Opt-out | Config key, on by default / Always on | Config key, on by default | +| Permission | New permission, superuser default / Any admin | New permission, superuser default | + +--- + +## Claude's Discretion + +- Marketplace page layout; override storage in plugin.yaml; registry schema, cache durations, ingest interval; backfilling older tags; CLI wording. + +## Deferred Ideas + +- `--module` for git-URL installs.