Skip to content

Latest commit

 

History

History
195 lines (151 loc) · 7.68 KB

File metadata and controls

195 lines (151 loc) · 7.68 KB

Releasing sage

Maintainer-facing checklist. End users don't read this.

Cutting a release

The root VERSION file is the single source of truth. .claude-plugin/plugin.json, the CHANGELOG's top entry, and the sage-version stamped into every project's .sage/config.yaml are all derived from it, and CI fails on drift. The marketplace entry deliberately carries no version: Claude Code silently prefers plugin.json when both are set, so a second number there is drift with no reader, and release.py --check rejects it.

# 1. Rename the ## [Unreleased] heading to ## [X.Y.Z] — <title>.
#    `--bump` refuses to write until that entry exists: a release without a
#    changelog entry is a release nobody can read.

# 2. Raise the version and propagate it into the derived files.
python3 runtime/tools/release.py --bump patch    # or minor / major

# 3. Full CI green.
bash develop/validators/gates/run-gate-tests.sh
bash develop/validators/installer/run-installer-tests.sh
python3 develop/validators/tools/test_release.py
python3 develop/validators/check-bash-arrays.py
python3 develop/validators/check-portability.py
docker run --rm -v "$PWD":/sage -w /sage bash:3.2 bash develop/validators/bash32-smoke.sh

# 4. Commit, then tag. The tag must match VERSION or the workflow rejects it.
git tag -a "v$(cat VERSION)" -m "Sage v$(cat VERSION)"
git push origin main --tags

The tag push runs .github/workflows/release.yml, which re-runs the gate tests and validators (a tag can point at a commit CI never saw), builds sage-X.Y.Z.tar.gz plus checksums.txt, verifies the checksum it is about to publish, and attaches both to a GitHub release with that version's changelog section as the notes.

install.sh and sage upgrade both refuse to install a tarball whose SHA-256 does not match the published checksums.txt. Nothing else authenticates the download — if the release assets are wrong, every user gets the wrong Sage.

Smoke the result on a clean machine or container:

curl -fsSL https://raw.githubusercontent.com/xoai/sage/main/install.sh | bash
sage version && sage new smoke-test

How the Claude Code plugin is distributed

main carries no plugin tree. It used to: tools/sage-claude-plugin/ was a hand-synced second copy of every skill, gate script, and template, and that duplication is how the Gate 4 bug shipped twice. The plugin is generated now (runtime/tools/build_plugin.py), and the only committed statement of what it contains is PLUGIN_SKILLS + FILE_MAP in that generator.

The release workflow's publish-plugin job builds the tree and force-pushes it to the plugin-dist branch, at the path tools/sage-claude-plugin/. The marketplace entry pins that branch:

"source": { "source": "git-subdir", "url": "https://github.com/xoai/sage.git",
            "path": "tools/sage-claude-plugin", "ref": "plugin-dist" }

The ref is load-bearing. Without it the source resolves to the default branch, which has no plugin tree, and the plugin silently becomes uninstallable — build_plugin.py --check fails if it ever goes missing.

plugin-dist is a build output, not history: each release replaces it wholesale. The tag and the tarball are the archive. Never hand-edit it — the next release overwrites whatever you put there.

After a release, smoke the plugin path too:

# in a throwaway Claude Code project
/plugin marketplace add xoai/sage
/plugin install sage
# then confirm a workflow routes:  /build

When sage-memory ships a new release

sage-memory is a sibling Python package (/mnt/e/Codes/sage-memory/, github.com/xoai/sage-memory) that provides the MCP server and three canonical skills (sage-memory, sage-ontology, sage-self-learning).

Sage vendors a fallback copy of those three skills under skills/sage-*/ so users without the MCP installed still get the prose. The vendored fallback is not auto-synced at runtime — it's committed to the sage repo and needs maintainer refresh when sage-memory updates.

There is one copy to refresh. The plugin ships these skills too, but its tree is generated from skills/ by build_plugin.py — sync skills/ and the next plugin build carries the change.

End users with sage-memory installed are unaffected (their sage update calls sage-memory install-skills which deploys the wheel-canonical copy on top of the vendored fallback).

One-command refresh

# Default: looks for sibling repo at ../sage-memory
runtime/tools/sync-vendored-skills.py

# Or specify an explicit path / env var:
runtime/tools/sync-vendored-skills.py --from /path/to/sage-memory
SAGE_MEMORY_SRC=/path/to/sage-memory runtime/tools/sync-vendored-skills.py

The script does:

  1. Copies SKILL.md + references/ + scripts/ from sage-memory's wheel into skills/sage-{memory,ontology,self-learning}/.
  2. Re-injects sage's fallback comment header at the top of each SKILL.md (lost when wheel content overwrites it).
  3. Patches upstream prose stragglers — sage-memory ≤ 0.10.0 still has the memory skill / the ontology skill / the self-learning skill references in a few sage-self-learning/references/*.md files without the sage- prefix. Until upstream cleans up, the script re-applies those renames every sync.
  4. Verifies:
    • No stale unprefixed skill-name prose
    • Every SKILL.md name: frontmatter matches its directory name
    • Every vendored SKILL.md has the fallback comment header

Exits non-zero if any verification fails. The script is idempotent — running it twice in a row on an already-synced state is a no-op.

After running the script

# 1. Review what changed
git diff skills/sage-*

# 2. Add a line to CHANGELOG.md under the upcoming release section:
#    "vendored fallback refreshed from sage-memory X.Y.Z"
$EDITOR CHANGELOG.md

# 3. Commit and push
git add -A skills/sage-* CHANGELOG.md
git commit -m "sync vendored fallback from sage-memory X.Y.Z"
git push origin main

If the refresh is part of a sage release, tag after the commit:

git tag v1.1.X
git push origin v1.1.X

When to refresh

  • Always after a sage-memory minor or patch release (0.X.00.X.1 or 0.X.00.Y.0).
  • Strongly recommended before tagging a sage release.
  • Optional for sage-memory patch-only updates that don't touch skill prose — but running the script is cheap and confirms nothing drifted, so default to running it.

When NOT to refresh

  • The script is for the vendored fallback only. End-user projects using the MCP get the canonical wheel content automatically via sage updatesage-memory install-skills. There's nothing to do for those users.
  • Don't manually edit the vendored fallback in skills/sage-*/. Edits there get clobbered on next sync. If you need to change behavior for users without the MCP, the right place is sage-memory's upstream repo (PR to src/sage_memory/skills/).

Architecture context

The full spec/plan for this integration lives at .sage/work/20260519-sage-memory-integration/. See particularly spec.md §4.7 (loader-then-overlay flow) and §4.6 (migration semantics) for the design reasoning.

The end-user-facing pieces are:

The maintainer-facing pieces are: