Motivation
Trellis's most valuable subsystems — the changelog engine (fragments, version plan/apply, ripple), tag machinery (exact + moving series tags, GitHub Releases), release pr, the scheduler, and ci matrix/outputs — are language-agnostic in their logic. And the git_only lifecycle already is the publishing model for registry-less package types: changelog → version → tag → GitHub Release.
The concrete motivating case is AI skill / Claude Code plugin marketplace repos: a root marketplace.json index over plugins/* directories, each with a .claude-plugin/plugin.json carrying name and version, consumed via git refs. The index duplicates member versions — exactly the drift trellis exists to prevent ("Configure nothing that can be derived. Verify anything that must be duplicated.").
The only hard coupling to Gleam is the manifest seam: discovery (gleam.toml presence), identity (name/version fields), dependency edges (path deps), and version write-back all assume one file format.
Proposal: a declarative manifest adapter
No plugin system, no registry backends. A small config table that redefines the manifest seam; everything downstream reuses existing code.
Target repo shape
my-marketplace/
├── trellis.toml # config home — no gleam.toml to live in
├── .claude-plugin/
│ └── marketplace.json # the index: { plugins: [{name, source, version, …}] }
└── plugins/
├── code-reviewer/
│ ├── .claude-plugin/plugin.json # { "name": "code-reviewer", "version": "1.2.0", … }
│ ├── commands/…
│ └── skills/…
└── deploy-helper/
└── .claude-plugin/plugin.json
Config sketch
# trellis.toml — same [tools.trellis] table, new home. Root discovery walks up
# to the first gleam.toml OR trellis.toml carrying the table.
[tools.trellis]
members = ["plugins/*"]
[tools.trellis.adapter]
# The manifest's presence marks a member (replaces "has a gleam.toml"),
# relative to the member dir. Format derived from extension: .json/.toml/.yaml.
manifest = ".claude-plugin/plugin.json"
name = "name" # dotted path into the manifest
version = "version" # dotted path; version apply does a surgical edit here
# no `deps` key → flat graph. Graph consumers degrade cleanly: topo order =
# member order, ripple bumps never fire, path-dep rewrite is a no-op.
[tools.trellis.publish.lifecycle]
default = "git_only" # no registry; `hex` under an adapter is a doctor error
# Version sync — modeled on the existing manifest.toml lock-patching:
# surgical, not templated, so hand-written fields (description, owner,
# category) are never touched. `files` is a glob. A rule with entries/match
# is index-shaped: walk to the array at `entries`, find the element whose
# `match` field equals the member's name, write into it. A rule without
# them is direct-shaped: each matched file is one object belonging to the
# member whose directory contains it.
[tools.trellis.adapter]
sync = [
{
files = "**/marketplace.json",
entries = "plugins", # path to the array of entries
match = "name", # entry field ↔ member's name
sync = { version = "version" }, # index field ← member manifest path
},
]
[tools.trellis.publish]
package_tags = ["exact", "major"] # moving v1 tags are the pinning
exact_tag_format = "{name}-v{version}" # story for git-ref marketplace
series_tag_format = "{name}-v{series}" # consumers
Behavior per subsystem under an adapter
| Subsystem |
Behavior |
Discovery, list, ci matrix |
Unchanged — glob + manifest presence |
Changelog, version plan/apply |
Unchanged, plus: bump writes to the adapter's version path, then patches files matched by sync rules |
tag, release pr, GitHub Releases |
Unchanged verbatim |
run built-ins |
Absent — custom [tools.trellis.tasks] only |
publish, lockfile |
Refused (exit 2) — git_only tops out the lifecycle |
| Idempotency |
Already covered: tag plan computes "current version not yet tagged" |
doctor |
Drops gleam checks; adds: manifest parses, name matches directory, every index entry resolves to a member, every releasable member has an index entry, index version matches manifest (fixable, like stale locks today) |
A release, end to end
trellis changelog new --package code-reviewer --kind Added --body "Support monorepo diffs"
trellis version apply # plugin.json 1.2.0→1.3.0, marketplace.json patched, CHANGELOG rendered
trellis tag create --push --github-release # code-reviewer-v1.3.0 (immutable) + code-reviewer-v1 (moved)
New surface required
trellis.toml as an alternative config home (root discovery change)
- The four-key
[tools.trellis.adapter] table
- Surgical version edits for JSON and YAML (TOML exists)
- The
sync rules + matching doctor checks
Everything else is reuse.
Note: the multi-line inline tables in the sync array assume TOML 1.1 (released 2025-12). Verified empirically: toml = "1.1.2" parses them and toml_edit = "0.25.12" round-trips edits preserving formatting — both already pinned in Cargo.toml. Users whose editor/linter TOML tooling still targets 1.0 may see false syntax errors in their config; trellis itself is fine.
Deliberately out of scope (for now)
- Registry backends beyond Hex —
git_only covers the motivating case
- Artifact packaging (zip a plugin dir onto the GitHub Release) — possible follow-up for archive-consuming marketplaces
- Adapter-declared dependency edges (e.g.
package.json workspace deps) — flat graph only in v1
- Standalone skills as members. Skills version through their containing plugin's
plugin.json; SKILL.md frontmatter needs no version field of its own, so no frontmatter parsing is required.
Open questions
- One adapter per workspace, or per-glob? Sketch says per-workspace. Mixed repos (Gleam packages + a
plugins/ dir) would need adapters = [{members = [...], …}] — deferred; it's the feature that turns four keys into a subsystem.
- Positioning. This makes "A workspace CLI for Gleam monorepos" inexact. Cheapest honest framing: Gleam stays the native, batteries-included ecosystem; the adapter is the escape hatch. (Also touches the
trellis-gleam crate name.)
Motivation
Trellis's most valuable subsystems — the changelog engine (fragments,
version plan/apply, ripple), tag machinery (exact + moving series tags, GitHub Releases),release pr, the scheduler, andci matrix/outputs— are language-agnostic in their logic. And thegit_onlylifecycle already is the publishing model for registry-less package types: changelog → version → tag → GitHub Release.The concrete motivating case is AI skill / Claude Code plugin marketplace repos: a root
marketplace.jsonindex overplugins/*directories, each with a.claude-plugin/plugin.jsoncarryingnameandversion, consumed via git refs. The index duplicates member versions — exactly the drift trellis exists to prevent ("Configure nothing that can be derived. Verify anything that must be duplicated.").The only hard coupling to Gleam is the manifest seam: discovery (
gleam.tomlpresence), identity (name/version fields), dependency edges (path deps), and version write-back all assume one file format.Proposal: a declarative manifest adapter
No plugin system, no registry backends. A small config table that redefines the manifest seam; everything downstream reuses existing code.
Target repo shape
Config sketch
Behavior per subsystem under an adapter
list,ci matrixversion plan/applysyncrulestag,release pr, GitHub Releasesrunbuilt-ins[tools.trellis.tasks]onlypublish,lockfilegit_onlytops out the lifecycletag plancomputes "current version not yet tagged"doctornamematches directory, every index entry resolves to a member, every releasable member has an index entry, indexversionmatches manifest (fixable, like stale locks today)A release, end to end
New surface required
trellis.tomlas an alternative config home (root discovery change)[tools.trellis.adapter]tablesyncrules + matching doctor checksEverything else is reuse.
Note: the multi-line inline tables in the
syncarray assume TOML 1.1 (released 2025-12). Verified empirically:toml = "1.1.2"parses them andtoml_edit = "0.25.12"round-trips edits preserving formatting — both already pinned in Cargo.toml. Users whose editor/linter TOML tooling still targets 1.0 may see false syntax errors in their config; trellis itself is fine.Deliberately out of scope (for now)
git_onlycovers the motivating casepackage.jsonworkspace deps) — flat graph only in v1plugin.json;SKILL.mdfrontmatter needs no version field of its own, so no frontmatter parsing is required.Open questions
plugins/dir) would needadapters = [{members = [...], …}]— deferred; it's the feature that turns four keys into a subsystem.trellis-gleamcrate name.)