Release pipeline

Component spec. Constrained by ADR-0042, ADR-0041, ADR-0003, ADR-0025.

Scope

This spec covers the CI pipelines, release workflows, dependency resolution rules, and distribution artifacts for the kaged project. It does not cover the daemon binary build itself (see daemon.md + ADR-0041) or the install script's runtime behavior (see ADR-0042 §5–§6).

1. Package classes and resolution

Three package classes exist. Resolution rules differ by class.

Class Examples Lives Dependency spec Published
Internal workspace @kaged/daemon, @kaged/harness, @kaged/dsl, @kaged/wire, … packages/ in the monorepo workspace:* + TS source paths (ADR-0025) Never
External published @kaged/utils, @kaged/natives, @kaged/sso, @kaged/sso-fixtures, reference plugins Own repos (kaged-dev/kaged-<thing>) Registry semver (^x.y.z) or exact pin for contracts npm with provenance
In-tree plugins @kaged/plugin-memory-markdown, @kaged/plugin-memory-hindsight, @kaged/plugin-webhook-notify plugins/ in the monorepo workspace:* for internal deps; registry semver for externals From their own repos (mirrors)

1.1 Resolution rules

  • Internal packages use workspace:* in package.json and declare TS source paths in their tsconfig.json per ADR-0025. The paths are monorepo-internal only — they do not appear in published packages.
  • External packages use registry semver ("@kaged/utils": "^0.0.2"). No link:, no file:, no workspace:*.
  • Contract packages (e.g. @kaged/sso-fixtures) use exact-pin ("@kaged/sso-fixtures": "0.0.1") per docs/conventions.md §Cross-repo contract packages.
  • bun link is a documented local override for active co-development of extracted packages. The committed lockfile always reflects registry versions. CI and Docker never link.

1.2 TS paths policy

tsconfig.json paths entries exist only for internal workspace packages. External packages (@kaged/utils, @kaged/natives, @kaged/sso-fixtures) must not have paths entries — TypeScript resolves their types from the installed npm package.

2. Clean-clone invariant

⛔ invariant: a fresh git clone of the monorepo + bun install --frozen-lockfile (no sibling repos, no bun link, no environment variables beyond Bun itself) must:

  1. Typecheck: bun --filter '*' typecheck exits 0.
  2. Test: bun test --recursive packages/ plugins/ — all tests pass.
  3. Format: bunx biome ci . exits 0.
  4. Build the release binary: bun run scripts/build-release.ts --target=linux-x64 --outfile=dist/kaged-daemon exits 0.
  5. Build the site: bun run packages/site/build.ts exits 0.

This invariant is the acceptance criterion for Phase 0 (ADR-0042) and is enforced by the dev CI workflow.

3. Monorepo CI (dev)

Workflow: .github/workflows/ci.yml Trigger: every push to main, every pull request.

Jobs

check (ubuntu-latest, Bun):

  1. bun install --frozen-lockfile
  2. bun --filter '*' typecheck
  3. bun test --recursive packages/ plugins/
  4. bunx biome ci .

All four steps must pass. Failure blocks merge.

4. Monorepo CI (release)

Workflow: .github/workflows/release.yml Trigger: push of a v* tag.

Jobs

build (matrix: linux-x64 on ubuntu-22.04, linux-arm64 on ubuntu-22.04-arm):

  1. bun install --frozen-lockfile
  2. bun run scripts/build-release.ts --target=<arch> --outfile=dist/kaged-daemon
  3. Copy the matching-arch .node beside the binary.
  4. Upload dist/ as a build artifact.

image (needs build, ubuntu-latest):

  1. Download build artifacts.
  2. docker buildx build multi-arch (amd64 + arm64).
  3. Push to ghcr.io/kaged-dev/kaged-daemon:<tag> + :latest (skip :latest for prerelease tags).

release (needs build, environment: release with required reviewer):

  1. Download build artifacts.
  2. Generate SHA256SUMS.
  3. Create a GitHub Release in kaged-dev/kaged-releases via the cross-repo GitHub App token.
  4. Upload per-arch binaries + SHA256SUMS as release assets.
  5. Copy changelog markdown into kaged-releases.
  6. Mark as prerelease if the tag contains - (e.g. v0.1.0-rc.1).

Invariants

  • Release artifacts contain no embedded sourcemap (asserted by assertNoSourcemap in build-release.ts).
  • The image package is public on GHCR.
  • The tag matches the version in packages/daemon/package.json.

5. Extracted-package CI

Each extracted repo carries its own CI, generalizing the pattern established by kaged-sso (ADR-0036).

Common pattern

Dev CI (push to main, PR):

  1. bun install --frozen-lockfile
  2. Typecheck
  3. Biome format check (bunx biome ci .)
  4. Test

Release CI (push of v* tag):

  1. Dev CI steps (gate).
  2. Verify tag matches package.json version.
  3. npm publish --provenance --access public (requires NPM_TOKEN secret + id-token: write for provenance).

Package-specific additions

  • kaged-natives: multi-platform matrix build (4 targets), platform packages published first, then the loader package with optionalDependencies pinned.
  • kaged-sso: lockstep publish of @kaged/sso + @kaged/sso-fixtures (same version, same tag). Container image pushed to GHCR.
  • Reference plugins (kaged-plugin-memory-hindsight, kaged-plugin-memory-markdown, kaged-plugin-webhook-notify): standard dev + release CI.

6. kaged-releases repo

Scope: passive artifact host + script home. Builds no source and no binaries.

Contents (authored in-repo)

  • README.md — install instructions, quick start.
  • install.sh — Docker MVP install script (§6.1).
  • kaged — Docker wrapper CLI (§6.2).

Contents (received from monorepo release CI)

  • Per-arch binaries + SHA256SUMS as GitHub Release assets.
  • Changelog markdown copies.

CI

  • Shellcheck on install.sh and kaged.
  • Optional install smoke-test in a container.
  • No secrets, no npm publish.
  • main is branch-protected.

6.1 install.sh (Docker MVP)

Detects Docker, pulls the image, writes a compose file with the project directory volume-mounted and port exposed, sets KAGED_UI_URL / KAGED_PUBLIC_URL, and installs the kaged wrapper to ~/.local/bin/ (or /usr/local/bin/ with --system).

Root/user host install modes are deferred to the v1 roadmap.

6.2 kaged wrapper

A thin shell shim on PATH that fronts the containerised daemon:

  • kaged startdocker compose up -d
  • kaged stopdocker compose down
  • kaged statusdocker compose ps
  • kaged auth launchdocker exec kaged kaged-daemon auth launch-url then xdg-open / open
  • kaged logsdocker compose logs -f
  • kaged exec <cmd>docker exec -it kaged <cmd>
  • kaged updatedocker compose pull && docker compose up -d

7. site-manifest.json descriptor (§1a contract)

Packages the site documents but does not build-depend on emit a site-manifest.json descriptor. The site consumes this descriptor instead of importing the package.

Schema

{
  "name": "@kaged/<thing>",
  "version": "0.1.0",
  "description": "...",
  "tools": [{ "name": "...", "description": "..." }],
  "hooks": ["on_session_start", "..."],
  "knobs": { "<key>": { "type": "range", "description": "..." } },
  "config_schema": { "...JSON Schema..." },
  "readme_excerpt": "First 500 chars of README"
}

Generation

A build:site-manifest script in the package generates the descriptor from the kaged-plugin.yaml manifest and package.json.

Freshness enforcement

CI regenerates the descriptor and diffs against the committed copy. A stale descriptor fails the build. Same anti-drift posture as schema regeneration and the sso-fixtures lockstep.

Delivery

The descriptor ships inside the published npm package (a file in the tarball). The site consumes the versioned copy at the pinned version.

Amendment log

  • 2026-06-14: Initial spec, per ADR-0042.