Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 4. Ecosystem enablement: detection enables, config opts out

Date: 2026-07-19

## Status

Accepted. Implementation tracked in [#554](https://github.com/goosewobbler/releasekit/issues/554).

## Context

ReleaseKit's pipeline is registry-agnostic and spans multiple ecosystems (npm, crates.io, pub.dev today; more planned). Enablement had grown asymmetric: npm published by default while crates.io and pub.dev were opt-in (`publish.*.enabled: false`), and npm alone had no version-handling toggle at all — the other two did.

That asymmetry is a footgun that worsens with every ecosystem added. A contributor wiring a new adapter has to decide, per ecosystem, whether it versions and whether it publishes by default, and a user reading the config finds the same key means different things in different ecosystems. A detected-but-off ecosystem also fails silently — the packages are simply never published, with no error.

The question this settles: when ReleaseKit detects an ecosystem, what does it do with it by default?

## Decision

**No ecosystem is special — detection enables, config opts out.** Every ecosystem ReleaseKit detects is versioned, and (where publishing is configured and authenticated) published, by default. Explicit config is only ever an *opt-out*.

- `publish.<eco>.enabled` defaults `true` for every ecosystem (was `false` for crates.io/pub.dev).
- `version.<eco>.enabled` exists for every ecosystem (npm gained `version.npm.enabled`) and defaults `true`.
- The two opt-outs are kept deliberately separate because they express different real needs: `version.<eco>.enabled: false` means "don't touch this ecosystem's manifests at all" (e.g. a vendored crate, or Rust versioning handled by release-plz); `publish.<eco>.enabled: false` means "version and tag this ecosystem, but publish it elsewhere".

Default-on is safe because the pipeline already fails safe *structurally*, independent of the toggle: an enabled registry that discovers zero targets no-ops, and publishing without credentials fails or skips at the auth check. The toggle is an opt-out, not a safety mechanism.

## Consequences

- One symmetric mental model that scales: a newly-added ecosystem adapter is on-by-default with no per-ecosystem special-casing, and "detected" and "released" no longer diverge silently.
- Flipping the crates.io/pub.dev publish defaults from `false` to `true` is a behaviour change — a pre-existing config relying on the old opt-in would start publishing. Acceptable here (pre-1.0, single maintainer, configs updated alongside the change); a `!`/`BREAKING CHANGE` release note carries it.
- Two independent per-ecosystem opt-outs are more config surface than one, but collapsing them would conflate "don't manage this ecosystem" with "manage but don't publish" — two outcomes users genuinely need to select between.
30 changes: 30 additions & 0 deletions docs/adr/0005-require-an-explicit-llm-model-no-defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 5. Require an explicit LLM model; ship no defaults

Date: 2026-07-19

## Status

Accepted. Implementation tracked in [#541](https://github.com/goosewobbler/releasekit/issues/541).

## Context

LLM-enhanced release notes need a model id per provider. The obvious convenience is to ship a curated default per provider so a user can set `provider` and omit `model`.

Model ids rot. Verifying the defaults while implementing this showed the openai default we were about to ship — `gpt-4o-mini` — was already deprecated and removed from OpenAI's API. A shipped default is therefore two liabilities at once: a per-release maintenance treadmill for ReleaseKit (every default must be re-checked and bumped as providers churn), and a silent failure mode for consumers (anyone relying on the default breaks the day the provider removes that model).

Crucially, there is no zero-config path to protect: LLM enhancement is already an explicit opt-in that requires an API key (or a running Ollama with a model already pulled). Naming the model is a marginal addition to a setup the user has already committed to.

## Decision

**ReleaseKit ships no model defaults. `llm.model` is required and validated at config load.**

- `llm.model` is a required, non-empty string (Zod `z.string().min(1)`) — a missing or empty model fails at config validation, not silently inside the notes soft-fail catch.
- `provider` is a closed Zod enum (`openai | openai-compatible | anthropic | ollama`); `openai-compatible` additionally requires `baseURL`.
- Configs assembled in-process (the CLI's `--llm-*` flags, which bypass `loadConfig`'s Zod pass) are run through the same schema before use, so a bad provider or missing model fails loud (non-zero exit) rather than degrading to non-LLM notes.

## Consequences

- Zero model-id maintenance for ReleaseKit: no default to track, re-verify, or get wrong. The consumer owns the fast-moving model choice for their account and budget — the party best placed to keep it current.
- Fail-loud at config validation is consistent with the `provider`/`baseURL` validation this decision also introduces; misconfiguration surfaces at load, not three stages deep.
- The "zero-config local-first Ollama" story loses its literal zero-config-ness — a user must name their local model. Acceptable: an Ollama user already has to run the server and pull a model, so they know its name.
- Docs use a `<your-model>` placeholder in examples rather than a real id, so the examples themselves can't rot into recommending a dead model — the same rot this ADR refuses to ship in code.
2 changes: 2 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ Roadmap sequencing lives in [ROADMAP.md](../../ROADMAP.md); implementation detai
| [0001](./0001-language-remains-typescript.md) | ReleaseKit remains a TypeScript codebase | Accepted |
| [0002](./0002-no-mcp-server-cli-is-the-agent-surface.md) | The agent-facing surface is the CLI, not an MCP server | Accepted |
| [0003](./0003-optional-change-file-input.md) | Change files are supported as an optional input, in changesets format | Accepted |
| [0004](./0004-ecosystem-enablement-detection-enables-config-opts-out.md) | Ecosystem enablement: detection enables, config opts out | Accepted |
| [0005](./0005-require-an-explicit-llm-model-no-defaults.md) | Require an explicit LLM model; ship no defaults | Accepted |