Skip to content

feat(release): install scaffolding, PR-body release notes, a confirm-mode config asset, and a seed/migrate lifecycle - #118

Merged
eaitbrahim merged 9 commits into
mainfrom
feat/init-and-seed
Jul 21, 2026
Merged

feat(release): install scaffolding, PR-body release notes, a confirm-mode config asset, and a seed/migrate lifecycle#118
eaitbrahim merged 9 commits into
mainfrom
feat/init-and-seed

Conversation

@eaitbrahim

@eaitbrahim eaitbrahim commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

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 rules table — and with zero rules, the engine has no strategies to evaluate at all, however config.yaml is set.

  • keel init-config writes a config.yaml template shipped inside the wheel.
  • keel init scaffolds a working directory: config + seed the strategy (rules) library.
  • keel rules seed --status can seed at candidate (default), paper, or live; live bypasses 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-notes output — * <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, and Co-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.yml remains its single source of truth: first matching category wins, norelease is 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.yaml is now a release asset, in confirm mode

The Release attaches config.yaml — the production config: real allowlist and caps, in auto_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.enabled also stays false.

It lives at keel/templates/config.live.yaml, committed and reviewed like any other code, and keel init-config --live writes the identical file locally. The dev template stays mode: 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

keel init      # FRESH deployment: write config.yaml + seed the strategy (rules) library
keel migrate   # EXISTING database: apply outstanding schema migrations. Never seeds.

New keel migrate [--db PATH] is an idempotent, schema-only wrapper over the existing db.migrate(), reporting 0 -> 6 or already 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 candidate and trade nothing until promoted.

New .github/workflows/migrate.yml is manual-only: give it a db_path and it migrates that database; leave it empty and it verifies the migration chain instead (a fresh DB and a downgraded DB both reach SCHEMA_VERSION).

Deliberately not done: wiring migration into release CI. keel.db is local, git-ignored and single-user, so CI has no database to reach. db_path is 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 norelease exclusion all correct), every workflow run block was bash -n-checked, both workflows YAML-parsed, and keel migrate (fresh + idempotent) and init-config --live/default were smoke-run against the real CLI.

Design: docs/superpowers/specs/2026-07-21-release-packaging-bootstrap-design.md.

eaitbrahim and others added 7 commits July 20, 2026 19:02
…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>
@eaitbrahim eaitbrahim changed the title Ship config template + keel init / init-config / seed --status live feat(release): install scaffolding, PR-body release notes, a confirm-mode config asset, and a seed/migrate lifecycle Jul 21, 2026
@eaitbrahim eaitbrahim added the enhancement New feature or request label Jul 21, 2026
eaitbrahim and others added 2 commits July 21, 2026 18:28
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>
@eaitbrahim
eaitbrahim merged commit e74e00e into main Jul 21, 2026
1 check passed
@eaitbrahim
eaitbrahim deleted the feat/init-and-seed branch July 21, 2026 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant