feat(release): install scaffolding, PR-body release notes, a confirm-mode config asset, and a seed/migrate lifecycle - #118
Merged
Merged
Conversation
…status live
Makes a fresh installed release usable without copying files out of the
repo, and gives the supervised live test a first-class seeding path.
1. CONFIG TEMPLATE IN THE WHEEL. keel/templates/config.yaml is packaged
via pyproject `artifacts`, and `keel init-config` writes it into the
working directory (refuses to clobber without --force). Verified the
template is actually INSIDE the built wheel and that init-config runs
from a clean installed artifact -- packaging that silently drops a
data file is exactly the trap here.
A test guards that the packaged copy stays in sync with the repo
config.yaml, and that the template parses as a valid config.
2. `keel init` -- convenience: init-config then `rules seed` (candidates).
Scaffolds a working dir in one command.
3. `rules seed --status {candidate|paper|live}` (default candidate). Live
bypasses the promotion gate and prints a loud warning -- it is the
supervised live-order test's seeding path, replacing the runbook's
hand-rolled insert_rule() poke. Live-seeded rules are still
confirm-gated and rail-guarded; the warning says to remove them after.
Verified end to end: init-config from an installed wheel writes a valid
config; `rules seed --kinds dca --products BTC-USD --status live` creates
one live DCA rule; rules list shows status=live.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR-body release notes, config.yaml as a confirm-mode live release asset, and a seed/migrate lifecycle with a manually-dispatchable migration workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renders '### <title> (#N)' + the cleaned PR description, grouped by the categories in .github/release.yml (still the single source of truth). Strips the Claude Code footer, HTML comments and Co-Authored-By trailers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Counterpart to 'keel init': init bootstraps a FRESH deployment (config + candidate rule library); migrate evolves an EXISTING database's schema and never seeds, so deliberately deleted/refuted rules are not resurrected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
keel/templates/config.live.yaml is the config.yaml attached to a Release: real allowlist/caps in mode: confirm, so it is ready for live use but never trades unattended off a fresh download. Dev template stays mode: paper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verifies a fresh DB and a DB stamped below HEAD both reach SCHEMA_VERSION. Gives the manual migrate workflow a real job until a hosted DB exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… workflow - release.yml composes notes via scripts/release_notes.py (PR bodies, not links) - verifies keel/templates/config.live.yaml is mode: confirm, then attaches it to the Release as config.yaml -- an armed config fails the release loudly - migrate.yml: manual-only; migrates a given db_path, else verifies the migration chain. CI has no DB to reach until the app is server-hosted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The categories referenced 11 labels that did not exist in the repo; they are now created. 'documentation' already existed, so it is treated as a synonym of 'docs' rather than duplicated -- the same pattern as feature/enhancement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… limit Both found by composing the REAL 70 merged PRs, not by unit tests: 1. PR bodies carry their own '##' headings, which rendered as siblings of the category headings and flattened the outline. Headings in a body are now shifted so the shallowest becomes h4, preserving relative depth, capped at h6, and ignoring '#' inside fenced code blocks. 2. The composed notes were 168,066 chars against GitHub's 125,000 limit -- 'gh release create' would have rejected the release outright. The budget is now shared across entries so EVERY PR stays listed and only bodies give ground; truncation cuts at a paragraph boundary and re-closes an orphaned code fence. Real output is now 98,718 chars with all 70 entries. Truncation only binds on this first all-history release: at <=20 PRs per release the budget exceeds the largest body (3,686 chars) and nothing is cut. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything a release ships, and how a fresh deployment comes up correctly.
Install scaffolding
A freshly installed wheel had no config to start from and an empty
rulestable — and with zero rules, the engine has no strategies to evaluate at all, howeverconfig.yamlis set.keel init-configwrites aconfig.yamltemplate shipped inside the wheel.keel initscaffolds a working directory: config + seed the strategy (rules) library.keel rules seed --statuscan seed atcandidate(default),paper, orlive;livebypasses the promotion gate and is for the supervised live-order test only, and says so loudly.Release notes now carry each PR's content, not a link to it
The notes used to be GitHub's
generate-notesoutput —* <title> by @author in #N— so learning what actually shipped meant clicking through every PR. Each entry now inlines the PR description under a### <title> (#N)heading.scripts/release_notes.py(repo-level; deliberately not shipped in the wheel) composes them, stripping the Claude Code footer and everything after it, HTML comments, andCo-Authored-By:trailers, and collapsing blank-line runs. A PR with an empty body renders_(no description)_— visible, so it gets fixed rather than silently vanishing.Grouping is unchanged and
.github/release.ymlremains its single source of truth: first matching category wins,noreleaseis dropped, unlabelled PRs fall to the catch-all.Consequence worth internalising: the PR body is the release note. Write it for someone reading the release page.
config.yamlis now a release asset, in confirm modeThe Release attaches
config.yaml— the production config: real allowlist and caps, inauto_trade.mode: confirm, so keel previews every order and waits for approval. It is ready for live use but cannot trade unattended off a fresh download;auto_trade.enabledalso staysfalse.It lives at
keel/templates/config.live.yaml, committed and reviewed like any other code, andkeel init-config --livewrites the identical file locally. The dev template staysmode: paper(places nothing).A release step fails loudly if that file is ever not
confirm— shipping an armed config is precisely what this project refuses to do, so it is a build failure rather than a review question.Seeding and migrating are separate, on purpose
New
keel migrate [--db PATH]is an idempotent, schema-only wrapper over the existingdb.migrate(), reporting0 -> 6oralready at 6, nothing to do. It is safe to re-run and safe against a live database.It never seeds. Re-seeding on migrate would resurrect rules that were deliberately deleted or refuted — the dip-buyers removed after measuring them would come back. Seeded rules stay
candidateand trade nothing until promoted.New
.github/workflows/migrate.ymlis manual-only: give it adb_pathand it migrates that database; leave it empty and it verifies the migration chain instead (a fresh DB and a downgraded DB both reachSCHEMA_VERSION).Deliberately not done: wiring migration into release CI.
keel.dbis local, git-ignored and single-user, so CI has no database to reach.db_pathis the seam for when the app is server-hosted; building the coupling now would mean a CI step with no target.Verification
1290 tests pass, ruff clean. Beyond unit tests, this was checked by running it: the notes composer was dry-run over realistic PR JSON (grouping, stripping and
noreleaseexclusion all correct), every workflowrunblock wasbash -n-checked, both workflows YAML-parsed, andkeel migrate(fresh + idempotent) andinit-config --live/default were smoke-run against the real CLI.Design:
docs/superpowers/specs/2026-07-21-release-packaging-bootstrap-design.md.