Skip to content

Manifest adapter: publish non-Gleam packages (AI skill / plugin marketplaces) #104

Description

@tylerbutler

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

  1. trellis.toml as an alternative config home (root discovery change)
  2. The four-key [tools.trellis.adapter] table
  3. Surgical version edits for JSON and YAML (TOML exists)
  4. 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 Hexgit_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

  1. 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.
  2. 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.)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions