Changelog
All notable changes to the ika100/claude-platform marketplace and templates.
Format: each section lists changes for a tagged release. Plugin and template versions are independent — a release may bump only one channel.
[Unreleased]
Section titled “[Unreleased]”[5.9.0] - 2026-11-05
Section titled “[5.9.0] - 2026-11-05”A feature of one component takes the component route (spec 140). /system:spec recognises a description that names exactly one component and needs no resource, exposure or environment change, and asks once whether to specify and ship it in that component’s repo with /component:spec and /component:ship (recommended): one spec, one PR, one ship, no plan. Features that span repos or need a resource keep the system route. /system:new and a new gitops repo now recommend the route by scope for the first feature.
An approved system plan asks no new questions (spec 141). When /system:ship writes a repo’s spec from the approved plan, open questions that have a suggested answer no longer stop the unattended ship: the answers are taken, folded into the repo spec’s criteria and listed under ## Decisions taken during the ship. The system ship’s final report lists them per repo (“Decisions taken for you”), the run record counts them, and the repo’s PR gets one comment with them. A question without a suggested answer, or one that would change the system spec or the plan’s contract, still stops the ship. The product manager now prefers the system spec’s answers over a new question.
No avoidable stops in the journey (spec 142). /component:set --expose merges its PR on green CI without another question (say so to leave it open). After /system:ship merges a gitops change under operators/, it applies the operators itself on the local cluster of devbox run cluster-up (k3d-<system>-local); other clusters keep the human step. devbox run bootstrap fast-forwards a stale main first, or stops before applying anything. A green instance verify dev promotes staging when it is set to after-dev-smoke, as a ship does, so fix and release builds follow too. A gitops command whose PR step fails leaves the clone clean (back on main, nothing changed, its local branch deleted), and a stale local branch of a merged PR no longer blocks the next one.
A dev or staging pin PR in a public gitops repo no longer stays blocked (found in journey 2 of 2026-11-05). GitHub holds the runs of a PR that github-actions[bot] opened; pin-dev and pin-staging approved only the first held run they saw (the specs run), and when the PR’s ci run then failed at startup without a job, branch protection blocked the merge forever. Both workflows now approve every held run until the PR’s ci run is running with jobs; if it is missing or never started, they close and reopen the PR once (GitHub then runs its checks again) and turn auto-merge back on. Existing gitops repos get the new workflows with /platform:update.
[5.8.3] - 2026-11-05
Section titled “[5.8.3] - 2026-11-05”The generated-lines exclusion of the gitops secrets scan works on real files (#420, found again in the 2026-11-05 journey). In a whole YAML document detect-secrets reads checksum/config: c746… with the value quoted, so the 5.8.0 pattern, written for the bare line, did not match: enabling observability or promoting to a new environment still failed the scan, and the agent added baseline entries by hand. The pattern now accepts the quoted form. Existing gitops repos keep their baseline (_skip_if_exists): copy the should_exclude_line entry of templates/gitops-app/.secrets.baseline.json into yours.
Resuming a merged ship never opens a second, empty PR (#452). A ship run whose dev smoke could not run ended done and was archived; a new /component:ship of the done spec started at phase pr and opened a PR from a branch identical to main. Its squash commit changed nothing, so CI built no image and deploy waited for a dev pin that never came. A new start of a done spec whose code is merged now resumes at deploy with the earlier PR and merge commit, and ship pr refuses a pull request that changes nothing on main.
A system ship merges a repo’s PR that started as the spec’s draft (found in the 2026-11-05 journey). In the system flow a repo’s spec is published as a draft PR on feature/<spec>, and the ship reuses that PR for the code. It stayed a draft, so GitHub refused the merge, and the error blamed merge conflicts. ship pr now marks a reused draft ready for review, land does so before it merges, and a refused merge of a draft says so.
Web and Python images take the Debian security updates of their slim base (found in the 2026-11-05 journey). node:24-slim shipped apt 2.6.1 with a fixable HIGH CVE, so the release image scan of a web UI failed, and rebuilding on the same base tag did not help. The runtime stages of the web-nextjs and service-python templates now run apt-get update && apt-get upgrade -y (lists removed afterwards). Existing repos get it with /platform:update (the Dockerfile is template-owned).
No false prod WARNING when staging runs the release commit itself (found in the 2026-11-05 journey). For a release PR that changes only the release files (CHANGELOG, version file), the prod evidence looked at the commit before it, assuming the release commit had no image of its own. When CI did build it and staging ran exactly that commit, the evidence warned staging runs cbc36a0, not a550472 although the digests matched and the staging smoke was green. When staging runs the tagged commit, that commit is now the evidence.
[5.8.2] - 2026-11-05
Section titled “[5.8.2] - 2026-11-05”The dev smoke tests run again after a deploy (found in the 2026-11-05 journey). component ship deploy reads the image digest dev runs from the pods, but it looked for a container named after the service, while render names every component’s container app. So every deploy with --context reported image digest unknown, skipped the smoke tests and escalated, and nothing was promoted to staging. The lookup now reads the app container.
[5.8.1] - 2026-11-05
Section titled “[5.8.1] - 2026-11-05”An organization-level GITOPS_TOKEN counts (found in the 2026-11-05 journey). component ship start refused with the secret GITOPS_TOKEN is not set when the token was set once for the whole organization, because only the repo’s own secrets were read; /platform:doctor warned the same way for SERVICE_REPOS_READ_TOKEN and ALERTS_TOKEN. They now count the organization secrets that GitHub makes visible to the repo, and /system:new and /component:new no longer ask you to create a per-repo token that the organization already provides.
[5.8.0] - 2026-11-05
Section titled “[5.8.0] - 2026-11-05”Changed
Section titled “Changed”The console has a new look and is easier to scan (spec 122). The Inbox, Board and System pages have a new design: an indigo accent,
raised cards, coloured pills for states and decisions, icons in the sidebar, a segmented view switch and a sticky panel header with
Esc. All pairs still pass WCAG AA in light and dark. Usability fixes: the Inbox no longer says “Nothing waits for you” before it has
read the decisions; a source that cannot be read is named in words (“The list of open decisions is not available”) with a pointer to
/platform:doctor; Board cards show the spec title instead of only its slug and are coloured by state (stopped, running, waits for
you) rather than by column; the Board no longer scrolls sideways: a strip shows all eight stages with their counts, and each stage is a row whose cards wrap (an empty stage is one slim line; the Columns view is now called Stages);
costs are rounded to cents; System shows the system as one tree (Components, Libraries, Resources, APIs; a row opens for its repo and relations) and every change starts from its item (+ Add on a group, Remove and Change APIs on a row, Change rules on an environment), with its form opening at the right, filled in and with selection lists for what already exists; the separate Add to the model section is gone (the palette keeps the forms). A background refresh no longer throws away a form that is being filled in. Help: a ? next to every term (decision kinds, card colours, instance states, promote rule, policy, autonomy level, guardrails, freshness) explains it in place, and How it works (/help, in the menu) shows the eight stages and every term by topic from the same texts; the Board and System pages have a legend, and the sidebar names the system it shows. The palette asks for the sentence once: its search box is the only text field, Describe a change and Ask quote it, and with an empty box their buttons say what to type first.
A release waits for the upgrade e2e once, not twice (#400). cplat platform release waited about 37 minutes for the upgrade e2e on the release pull request and then again on its merge commit, although a squash merge of an up-to-date branch has exactly the tree that was tested. The release, and the gate scripts/release-check.py gate (also in the release-guard workflow), now take a green upgrade e2e on the head of the merge commit’s pull request when that head has the same tree; otherwise they wait for and require the run on the merge commit as before. Splitting the upgrade e2e itself is still open in #400.
cplat waits out GitHub’s rate limit (spec 137, #409). A gh call that GitHub refuses for its rate limit no longer ends the step: cplat prints GitHub rate limit (<kind>): waiting until HH:MM UTC, waits until the reset (or backs off up to 10 min when the reset is unknown) and runs the call again, for at most 65 minutes. cplat spec publish and spec review now show gh’s own error, not only gh exit 1. The local console stops reading its sources after 10 minutes without a browser request and refreshes at once on the next one, so an open but unwatched console no longer uses up the account’s GitHub budget.
The secrets scan no longer flags .copier-answers.yml (#389). The answers file records where the template came from (_src_path). For a render from a local checkout, some paths (for example /home/ci/Dev/sdlc-foundry-fix389/templates/library-python) look like a high-entropy secret to detect-secrets, so the repo’s security check failed, and the template test failed depending on the folder it ran in. Repos created with /component:new were rarely affected, because their _src_path is rewritten to gh:ika100/sdlc-foundry/…. Every template’s secrets-scan recipe and the Python templates’ pre-commit hook now skip the file; it holds template answers only. Action: run /platform:update.
The CI failure summary shows the log of a failed job whose run is still in progress (#399). gh run view --log-failed serves
nothing until the whole run is complete, so a check that failed while its sibling jobs still ran (the three parallel e2e parts of
spec 134) was summarised without a log line and called “not a GitHub Actions run”. cplat pr wait and ship now fetch the failed job’s
own log when the run’s log is empty, and a check that still has no log says why (run still in progress, gh error: …, or no log
lines); “not a GitHub Actions run” is left for checks without an Actions run link.
Resuming an escalated component ship keeps its PR and merge commit (#408). The finish step of /component:ship archives the run’s state even when it escalated, so a later /component:ship <spec> started a new run at phase pr without the PR or merge commit: deploy refused (the run has no merge commit) and ship pr would have opened a duplicate PR for merged code. ship start now resumes the newest archived run of the same spec when that run ended escalated, at the phase where it stopped.
A component ship’s PR no longer conflicts with main on its own spec (#410). cplat spec land squash-merges the spec PR, but the local feature/<spec> branch kept the original spec commits, so the feature PR conflicted with main’s squash commit and needed a merge of main and a second CI round (about 4 minutes in the demo). When the ship starts and everything on the branch is already on origin/main (and the remote branch was deleted by the merge), it restarts the branch from origin/main. A branch with work of its own, or one still on origin, is left as it is.
A platform bucket adopts the bucket its database created first, so backups to it no longer fail with AccessDenied (#412). A database that backs up to an in-cluster bucket starts WAL archiving at once, and barman-cloud creates the bucket in SeaweedFS when it is missing. If that happened before the operator reconciled the rendered Bucket, the object failed with BucketAlreadyExists, its access grant was never applied, and every backup ended in AccessDenied. The rendered Bucket now sets adoptExisting: true. Run /platform:update in each gitops-app repo with an in-cluster bucket; a bucket stuck in Failed is adopted on the next sync.
A system plan can declare and use any resource, not only postgres (#414). plan-check rejected every resource but postgres (unknown resource 'backups'), so a plan with a bucket, cache or broker kept CI red. It now accepts a resource that system.yaml declares, and a new one that names its type: {resource: files, type: bucket}. /system:ship passes that type on as cplat resource add files --type bucket. Existing gitops-app repos get the new check with /platform:update.
/resource:restore says which actions need --confirm prod (#415). Only --switch and --discard change prod, so only they need --confirm prod; --to adds a copy next to the running database, which keeps serving. The command and its --confirm help said that prod as a whole needed the confirmation.
/system:status marks a database failing when its scheduled backup stopped running (#416). A database was failing only when continuous archiving failed or the newest base backup failed; a ScheduledBackup that silently never ran (missing, suspended, operator down) showed ok with an ever older last success. Now no completed base backup within one schedule period plus 2 hours (26 h for the default daily schedule; counted from the cluster’s creation before the first backup) makes it failing, with the reason.
A backed-up database states its restore point objective in git (#417). render now sets postgresql.parameters.archive_timeout: 5min on every database cluster with a backup, so a committed change reaches the archive within 5 minutes and can be restored. Before, this relied on CloudNativePG’s default, which was neither visible in git nor checked. The value is the same, so running databases behave as before; the next render changes their cluster.yaml.
cluster-up on an existing cluster prints the real port, not :default (#419). Without LOCAL_HTTP_PORT, the port was only resolved when the cluster was created, so a second devbox run cluster-up printed every URL as http://<host>:default/ (Grafana and every exposed component). A reused cluster now reads the host port it was created with from its load balancer. Action: run /platform:update in each gitops-app repo.
A gitops-app’s secrets scan no longer fails on lines the render writes itself (#420). A collector’s checksum/config: <16 hex> annotation and an S3Credentials’ field names (accessKeyField: accessKeyId, secretKeyField: secretAccessKey) looked like secrets to detect-secrets, so a new collector config or bucket failed the repo’s security check and needed a hand-made baseline entry each time. The template’s .secrets.baseline.json now carries one narrow exclude-lines pattern for exactly these lines; a real value on such a line is still reported. Action: new repos get it; an existing gitops-app repo adds it once with detect-secrets scan --baseline .secrets.baseline.json --exclude-lines '^\s*(checksum/config: [0-9a-f]{16}|accessKeyField: accessKeyId|secretKeyField: secretAccessKey)\s*$' (the baseline is repo-owned, so /platform:update does not replace it).
A web component’s release is no longer blocked by npm’s bundled undici (#421). The web-nextjs runtime image kept npm, npx and corepack from the Node base image; npm bundles undici 6.28.0 with fixable HIGH/CRITICAL CVEs, so the release image scan refused every web release, while main builds only reported them. The newest node:24-slim and node:26-slim still ship that version, so a base-image bump does not help. The runtime stage now removes npm, npx and corepack (the server runs with plain node). Action: run /platform:update in each web repo.
A smoke run by hand counts as evidence for prod (#422). cplat instance verify <env> now writes its outcome to the run store (a verify record with the environment, the pinned image and the result), and the prod promotion’s evidence reads those records next to ship records. A build that reached staging through /instance:promote and passed instance verify staging no longer shows WARNING: … no green staging smoke: no staging smoke record. A run against a --base-url you name is not recorded, because that URL may serve another build.
platform update --verify smoke-tests prod at its release tag (#423). For a prod pin X.Y.Z the verify step cloned the branch X.Y.Z, which does not exist (fatal: Remote branch 0.1.0 not found), so every update of a system with prod ended NOT VERIFIED. It now clones the git tag vX.Y.Z, the commit the release image was built from.
/resource:restore --discard finishes in an Argo CD cluster, and an unfinished discard can be completed (#424). The database addon’s Argo application never prunes (data is never removed by automation), so after a discard the removed cluster stayed OutOfSync and the command timed out after 10 minutes waiting for Synced, before it deleted the cluster. It now continues once the application has the commit and only that resource waits for pruning, then deletes the cluster itself and waits for its volumes. A discard that stopped half way (its state change merged, the cluster still there) is finished by running --discard again: it finds the cluster that restore-state.yaml no longer lists and deletes it (in prod with --confirm prod).
Agents know that the platform files platform escalations itself (#428). The closing note of the commands said “Never file anything without their OK”, so a coordinating agent reported the platform’s own deduplicated escalation issue (spec 088, ADR-033) as a broken rule. The note now says that a platform escalation is the one thing the platform files itself. /component:build and /component:fix keep the short note to stay within their size budgets.
/system:status shows a database’s restore window in order, and /system:set writes a new key inside its environment (#429). Right after the first base backup, the window read backwards (20:37:56Z … 20:30:06Z), because the newest archived WAL was older than the end of that backup; the newest point is now never before the earliest. And a new key such as host: for prod was appended below the comment lines that follow the environment’s block, so it looked like part of the next section; it now goes inside the block, before those comments.
[5.7.0] - 2026-11-03
Section titled “[5.7.0] - 2026-11-03”cplat spec test-diff --approve records an acceptance-test change the user approved (#324). A build stopped on every later task for the same change to an acceptance test, even after the user had approved it, because nothing remembered the approval. cplat spec test-diff <red> --approve <file>... [--task <task>] [--reason '...'] records the file’s current content hash for that red commit (in the checkout’s git dir) and adds an approved_test_diffs entry to the active run record; later runs report the file as approved by the user and pass, and any further change to it is flagged again. /component:build records the approval when the user gives it (references/build-test-approval.md) and never approves on its own. Update the plugins (/plugin marketplace update).
cplat platform doctor says why agent worktrees start at main (#319). Claude Code reads the project’s .claude/settings.json only from the directory a session starts in, so a session started outside the repo (for example in ~/Dev) and changed into it later does not apply worktree.baseRef: head: the worktree probe of /component:build sees main’s HEAD and every task runs one after another. Doctor now warns when the current directory is not the repo’s root and when .claude/settings.json lacks "worktree": {"baseRef": "head"}, and the probe step of /component:build tells you to start the next session in the repo root. Update the plugins (/plugin marketplace update).
Build PRs show how they were built (#338). A build PR said what was built and verified, but not where the user decided, which task went back to which agent and why, which deviations the reviewer accepted, or how the run went; a reviewer had to piece that together from commits and verification.md. cplat pr open now appends a collapsed “How this was built” block to the PR body when the checkout has a build or ship run: how it ran (wall time, parallel or sequential and why, re-plans, CI rounds and reruns, gates, budget used against the limit, agents), user decisions (raised budget limits, stops for a decision, refused test diffs, answered open questions), hand-backs and fix rounds, and the deviations verification.md accepts. cplat generates it from the run’s local records, so it works without a run store; values are redacted and nothing of the prompts or diffs is included. cplat platform runs report --pr <run-id> prints the same block, and cplat platform runs agent execution parallel|sequential --reason <why> records the sequential fallback. User-approved test changes show as “not recorded (#324)” until #324 records them (spec 133, ADR-079). Update the plugins (/plugin marketplace update).
Platform changelog entries are fragments, so parallel PRs no longer conflict in the changelog (#321). Every platform PR added its entry at the top of [Unreleased] in docs/CHANGELOG.md, so parallel PRs conflicted there one after another and each needed a rebase and another CI round. A change now adds one file docs/changelog.d/<id>-<slug>.<added|changed|fixed>.md; cplat platform release assembles the fragments with any text still under [Unreleased] into the new version section (Added, Changed, Fixed) and deletes them in the release commit. scripts/release-check.py rows and the release preflight count fragments as unreleased content and reject a misnamed or empty one. The old changelog acceptance tests now find their release by its version heading instead of the first section that mentions their spec, and plugin version tests check >=, so a later entry or bump no longer breaks them. Contributors to this repo: write a fragment instead of editing docs/CHANGELOG.md (format in docs/changelog.d/README.md); text written under [Unreleased] keeps working. Spec 132, ADR-078: Platform changelog entries are fragments the release assembles.
Changed
Section titled “Changed”A pull request runs only the e2e it needs, and the cluster e2e runs in three parallel parts (#281). The cluster e2e took a median 41 minutes and the upgrade e2e 36 (10 runs each), and most pull requests ran both: the cluster e2e started on any change under scripts/cplat/** or templates/gitops-app/**, and the upgrade e2e compared against the last release tag, so after one versions change every later pull request ran it. Now a decide job runs scripts/e2e-scope.py, which starts the cluster e2e only for the gitops-app and cluster-gitops templates (not their copied workflow files or Markdown), the templates’ Dockerfiles, the e2e files, shapes.yml, the scripts/cplat modules the e2e scripts call or import (closed over every import, read from the code), and the release pull request. On a pull request the scenario runs as three parallel jobs (E2E_PART core, scaling, backups, about 25 minutes); the nightly run keeps one job with every step in order. The upgrade e2e decides on the pull request’s own diff; push to main and the release gate are unchanged (spec 134, ADR-080).
In the platform repo itself, cplat runs the checkout (#320). The launcher ran the installed release there, so a cplat fix merged to main (for example to pr wait) did not apply to the work that followed until the next release was installed, and a new command such as platform release needed CPLAT_PLATFORM=$PWD. When the current directory is inside a git checkout that declares shape: platform in .platform-settings.yml, the launcher now runs that checkout’s scripts/cplat; CPLAT_PLATFORM and CPLAT_REF still win, and cplat platform doctor shows platform scripts: checkout: <path>. Other repos are unchanged. Update the plugins (/plugin marketplace update).
CONTRIBUTING.md has rules for AI-assisted PRs, security paths, issue claims and fork CI (#326). AI-assisted PRs are welcome with the local test output in the description; the redaction, secret handling and permissions are changed by maintainers only; an issue claim expires after 7 days without a draft PR; and CI of a fork PR is approved only after a maintainer has read the diff.
The e2e probes share one probe-pod helper, and a lint keeps $$ out of pod commands (#323). tests/e2e/run.sh and tests/e2e/upgrade.sh each built their probe pods with their own copy of the code, and the copies drifted: some copied only Secret-referenced variables, and only the upgrade e2e resolved prefixed names such as FILES_S3_*. Both now use probe_run from tests/e2e/lib/probe.sh, which copies Secret references and plain values, resolves a prefix (-p FILES_) and runs the script in a plain Pod; what the e2e checks is unchanged. scripts/check-e2e-probes.py (in devbox run quality, ci-local and CI) rejects a literal $$ in a string the e2e scripts could send as a pod command, since Kubernetes turns it into $ there. The upgrade e2e prints skipped: <step> (previous release has no <type>) for every step it leaves out because the previous release lacks a resource type, and lists them again at the end.
cplat platform release runs the test suite before it opens the release PR (#334). It checked only release-check rows and devbox run validate after moving [Unreleased] under the new version heading, so tests that read the changelog failed first in the release PR’s CI (5.5.0, #331) and cost a CI round. It now also runs devbox run test-fast on the moved changelog; on failure it prints the output and stops with nothing committed or pushed, like the other two checks.
Runs that finish on their own. Unattended ships rarely finished: 16 of 52 were done, and about half of the spend went to runs that ended escalated or failed (spec 131, ADR-075: An unattended ship or build runs as one session per unit, each saved at a checkpoint, ADR-076: An unattended run gets its git and gh permissions per session from a checked run allowlist, never from a settings file, ADR-077: Run budgets scale with the plan, and verification gets one grace). cplat headless now runs a ship or build as one short session per unit (plan, red-tests, one per task, verification, land), each stopping at a checkpoint commit; the Stop hook reaches unit sessions through CPLAT_UNIT, and two sessions without a commit on a unit escalate. A preflight before the first session checks the repo allowlist, the settings rules, the git identity, gh login and push access, and fails with preflight failed: <category>: <detail> (no-repo-allowlist, blocked-by-settings, git-identity, gh-auth, no-push-access) instead of stopping at the first refused command; the sessions get a run allowlist for the feature branch, commit -s, push without force and gh pr create. Default budgets scale with the plan: cost max(15, 10 + 1.5·T + 0.5·C), minutes max(180, 60 + 15·T + 5·C), invocations max(60, 20 + 5·T + C), so spec 128 (8 tasks, 14 criteria) gets $29, 250 minutes and 74 invocations; verification gets one grace of 20 % when only it is left. A subagent’s cost is no longer missing from the ledger. cplat platform runs report gains an “Unattended ships” section with the four targets. Action: none, runs in flight resume by themselves. Not breaking.
Plugins: component 1.6.0, shared 1.2.0, system 1.6.1.
cplat pr wait no longer reads a GitHub API error as “no CI checks” (#356). While GitHub’s rate limit refused gh pr checks, the wait ended with no CI checks were reported after 300s and a hint about runners, although the PR had 36 green checks. A gh failure (rate limit, outage, auth) is now named as such and retried with a growing pause (up to 5 min); the time it lasts does not count towards the no-checks grace period, and a wait that times out on it reports gh’s own error. cplat platform release keeps polling through it.
cplat platform release proposes a patch when [Unreleased] holds only fixes (#346). It proposed only a major or a minor, so a release of three bug fixes was offered as 5.6.0. It now proposes X.Y.Z+1 when [Unreleased] has a ### Fixed section and nothing else; any other entry still makes a minor, a change that needs a major still makes a major, and --version still overrides.
cplat shows its progress when its output goes to a file or a pipe (#335). Python buffers output that does not go to a terminal, so cplat platform release, cplat pr wait and cplat spec land run in the background (as /platform:release does) wrote nothing until they exited, sometimes 40 minutes later. The launcher sets PYTHONUNBUFFERED=1 and cplat writes each line as it is printed. Update the plugins (/plugin marketplace update).
The console never treats a folder with another origin as a system’s clone (#344). POST /api/open-system passed its repo on without the owner/name check the actions use; it now answers 400 for anything else. When no candidate had the expected GitHub origin, the switcher, “Open” and “Open in Claude Code” still used a same-named folder; such a folder is now reported as no local clone. A clone in the configured workspace keeps its exception for forks.
cplat pr wait no longer reports RED for the run a label cancelled (#322). gh pr create --label no-spec (what cplat pr open --no-spec runs) fires opened and labeled, and the ci concurrency group cancels the first run. When the wait looked before gh run list showed the second run, the cancelled checks counted as red. A cancelled check now waits up to 90 s for the run that superseded it; once that run is listed, the cancelled one is ignored as before (#244). A run that nothing replaces is still red after those 90 s.
Specs planned in parallel no longer pick the same ADR number (#337). The architect numbered a new ADR from the highest file on its branch, so two specs planned while the first one’s PR was open both wrote ADR-073, and cplat spec check passed. /component:plan now gets the number from the new cplat spec adr-next, which skips the numbers every pushed branch and open PR uses, and cplat spec check fails when two files share a number or when an ADR the branch adds has a number another pushed branch already uses. Update the plugins (/plugin marketplace update).
The notifier no longer posts old escalations as a batch, and identical escalations collapse (#355). An escalation is a needs item of every escalated run of the last 30 days, and the notifier signed it by its run id. When the notifier started with no state for the system (the first /platform:ui after an update, or a new state file), it sent every one of them at once: 19 GitHub issues for specs merged five days before, each with its own signature although seven said the same thing. An escalation is now signed by repo, spec, stage and its message with run ids, times, counts, shas and durations normalised; before one is sent for the first time, it is dropped when it is older than 72 hours, when a run of its spec finished after it, or when the spec is done or superseded in its repo, and when another escalation with the same signature is open or sent in the same pass. An open notify issue with the same signature is reused instead of filing a new one. The notifier reports on stderr how many it dropped and why.
Release notes keep working links, and a duplicated changelog entry fails the PR (#336). The GitHub release took the ## [X.Y.Z] section as is, so links relative to docs/ (specs/…, adr/…, ../README.md) resolved against /releases/tag/ and returned 404; the release notes now point them at https://github.com/<owner>/<repo>/blob/vX.Y.Z/docs/…. The 5.5.0 notes also repeated an entry left over from a merge conflict: release-check.py rows, which runs on every PR, now fails when a changelog section repeats a paragraph and names the section and the entry.
cplat platform release reruns an upgrade e2e that a newer push cancelled (#380). On main only one upgrade e2e run waits behind the running one, so a merge right after the release commit cancelled the release commit’s waiting run, and the release stopped with red. A cancelled run is now rerun once and waited for. Cancelled a second time, the release stops, says the run was cancelled and gives the rerun command.
[5.6.1] - 2026-11-03
Section titled “[5.6.1] - 2026-11-03”A build no longer stops because lint-fix rewrote the acceptance tests (#367). The tester committed the acceptance tests without running lint-fix, so the first lint-fix in Phase 2 applied ruff’s autofixes to them (for example timezone.utc → UTC), cplat spec test-diff saw a changed statement and the build escalated. The tester agents of every shape now run cplat quiet lint-fix before they commit, and /component:build runs it once more before it records the RED commit, committing any change to the tests as test(<slug>): lint-fix acceptance tests for <id>. Update the plugins (/plugin marketplace update).
Ship’s dev smoke no longer gives up on one failed read of the gitops-app, and says why when it does (#369). cplat component ship deploy listed the gitops-app’s applications/ with gh api and treated any failure as an unreadable repo. The smoke tests and the promotion to staging were skipped with “could not read the gitops-app”, even though the component was exposed and answering. The error text was dropped, so the cause could not be found afterwards. The listing is now retried once after 5 s, and when it still fails, the reason carries gh’s own error, for example HTTP 401: Bad credentials.
Pin PRs merge by themselves on public gitops repos (#368). On a public repo, /system:new protects main, so pin-dev and pin-staging fall back to auto-merge, which waits for the PR’s own checks. GitHub holds the runs of a PR that github-actions[bot] opened until someone approves them, so dev never updated without a click. In that fallback, the workflows now approve the held runs themselves, but only on public repos, and only the pull_request runs of their own pin branch at the commit they pushed. If GitHub refuses, the job says where to approve. Action: run /platform:update in public gitops repos.
cplat platform doctor warns when the run store’s retro workflow is out of date. cplat platform runs init installs .github/workflows/cplat-retro.yml in the store once and never changes it, so a store set up before 5.0 still called cplat retro, which 5.0 removed, and the 2026-11-02 retro failed. Doctor now compares the file with this platform version’s asset and says how to fix it. Action: if doctor warns, replace that file in the store with scripts/cplat/assets/workflows/cplat-retro.yml.
The weekly retro posts again when a spec id is a bare number. cplat platform retro post checked the public issue for private names by substring match, so a spec recorded as 112 or 130 matched inside run ids (20261027T112130Z-…) and the retro of 2026-W44 was refused. The check now matches whole names, the same way the redaction replaces them. A spec id that is only a number is no longer treated as a private name, so see #130 stays readable. A refused post prints the names it found to the job log, which is in the private store.
Reading the run store no longer times out on a busy fortnight. The store was read with one GraphQL query per 40 days, which returns the text of every record. With up to 83 records a day, the two weeks the retro reads took longer than the 10 s a single call may take on the runner, and the retro of 2026-W44 failed at its first run. The store is now read 3 days per query, and a query that times out is retried once.
cplat pr wait no longer reports false errors (#370, #371). One empty answer from gh pr checks during a long CI run ended the wait with no CI checks were reported after 300s, although checks had been reported before. It now keeps waiting; only a PR that never reported a check stops after the grace period. With --merge in a git worktree, gh pr merge merged on GitHub and then failed to check out main locally, and cplat said merging failed and exited 1. cplat now asks GitHub whether the PR is merged; if it is, it reports the merge and names the local error.
Changed
Section titled “Changed”The gitops template’s CLAUDE.md says who creates generate: secrets. It still named External Secrets Operator; since 5.5.0 a credentials Job (an Argo CD PreSync hook) creates them once (ADR-072). The text reaches gitops repos with /platform:update.
[5.6.0] - 2026-11-01
Section titled “[5.6.0] - 2026-11-01”The console finds component clones beside the gitops clone. Console actions prefer a configured workspace, then a sibling of the active gitops clone, and finally ~/src. Configured-workspace clones keep the .claude/settings.json check; sibling and fallback clones must also have the expected GitHub origin. The active gitops clone is passed through actions, panels, the board, run handoff and the system switcher. (#272)
The gitops upgrade-check no longer crashes on a system with an external kustomize component (#340). It loaded the base from a copy of system.yaml alone, so the component’s kustomize package was missing there and building the error message raised a ValueError: every pull request of such a system had a red upgrade-check. The base is now the app directory at the base ref, external packages included, and the message works for paths outside the repo. Run /platform:update in an affected gitops repo.
The gitops secrets scan no longer flags the credentials Job’s script (#341). detect-secrets reported the create.py embedded in every rendered credentials-configmap.yaml (a cache, broker, bucket or generated component secret) as a secret, so the repo’s security check turned red. It was a false positive: no credential value is rendered. The renderer now writes create.py as a YAML literal block whose header line carries # pragma: allowlist secret; plan.json and every other value are still scanned. The script itself is unchanged, so the Job names stay the same. A baseline entry added as a workaround must be removed with the update (devbox run secrets-baseline): once the finding is gone, detect-secrets rewrites the stale entry and fails the security check.
[5.5.0] - 2026-10-31
Section titled “[5.5.0] - 2026-10-31”A gate that checked nothing fails. CI, /component:build and /component:ship read a green gate as “verified”, but a test runner that ran 0 tests, kubeconform over an empty build, a render check that compared no files or a secrets scan over an empty file list passed (#285, spec 130, ADR-074: A gate that checked nothing fails). Behaviour change: every covered gate (test, test-fast, validate, render-check, secrets-scan) in every template now counts what the tool reported and fails on 0 with <gate>: checked nothing: <0 tests ran | 0 resources | 0 files compared | 0 files to scan> - <how to fix>. A repo with 0 tests, only skipped tests, an empty build or no files to scan turns red after /platform:update; the fix is to add at least one test or make the build non-empty. There is no opt-out. Also: gitops-app validate now stops at its first failing step, Go tests run with -v (the count comes from the result lines), and a package.json test script with its own --reporter makes the web gate fail. Nothing breaks the CLI or the plugins: the release is a minor.
/platform:release. The platform was released by hand along CONTRIBUTING.md, Releases: ten steps, two of them irreversible (#296). /platform:release [--version X.Y.Z] [--dry-run] (cplat platform release) now does it: it checks the tree and the versions, moves [Unreleased] under the new heading, opens the release PR, waits for its CI, merges, tags and publishes the GitHub release. It stops twice for your yes (CONFIRM: merge, CONFIRM: tag, continued with --confirm merge|tag) and keeps no state file: it reads its progress from git and GitHub, so running it again resumes. /component:release still refuses in this repo and now names the new command.
Plugins: platform 1.4.0, component 1.5.3.
cplat feedback keeps the details readable. The details file went into the issue inside one plain code fence, so a fence in the details closed it early and broke the rest of the issue; and a secret-like key whose value said it was unset (# secretStore: (unset)) had that value redacted, or, with no value at all, the first word of the next line (#262). The fence is now longer than any backtick run in the details; unset, (unset), null, none, n/a, ~ and an empty string stay readable; only a value on the same line is redacted. Every real value under a secret-like key is still redacted.
/platform:update re-renders a cluster repo. The update re-applied the cluster template but left cluster/ (the render’s output) as it was, so a renderer change made the review branch fail its own render-check (#264). For a cluster repo the update now runs the updated scripts/render.py and commits what changed in cluster/ with the update; the report names the count. A cluster.yaml the new render rejects stops the update with the render’s message. System repos are re-rendered by the update since 5.4.0.
Deploy confirms an external Helm component. /component:ship escalated in phase deploy for a component with an external: {helm: …} entry: Argo CD reports no summary.images for its app, so the deploy check saw “runs nothing” although the Deployment ran the merged build (#251). When an app reports no images, the check now reads the pod templates of the workloads it manages, through the managed <svc>-<env>-chart Application one level down, and smoke and promotion continue as for any other component.
/system:set --operators owner|install. Handing a system’s operators to a cluster repo (environments.<env>.operators: owner, what a cluster’s check asks for) meant editing system.yaml by hand (#264). /system:set --env <env> --operators owner sets the key and renders; --operators install removes it, so the system installs its operators again.
Plugins: system 1.6.0.
/cluster:new takes the cluster’s properties. cplat cluster new wrote cluster.yaml with every default, so setting the API server, the operators or the secret store took a second edit, render and PR (#259). It now takes --server <url>, --operators <a,b> and --secret-store <name>, checks them as the cluster render does (an unknown or repeated operator, a server that is not an http(s) URL, a store without external-secrets) before anything is created, writes them into cluster.yaml, and with operators renders cluster/operators/ before the first commit. Without the flags nothing changes.
Operator applications stay Synced. operator-kyverno stayed OutOfSync / Healthy forever: the chart renders empty annotations: {} / labels: {} on its CRDs, the API server drops them, and Argo CD’s default client-side diff kept reporting the difference (#261). Every operator Application (system repos’ operators/ and cluster repos’ cluster/operators/) now carries argocd.argoproj.io/compare-options: ServerSideDiff=true, so Argo CD compares against what the server applied. Action: run /platform:update in system and cluster repos; the render adds the annotation.
cluster add checks the system before it opens a PR. cplat cluster add (and its --dry-run) opened a PR for a system that the cluster repo’s own check rejects right away, for example one whose environments would install operators themselves (#263). It now runs the cluster repo’s scripts/check.py with the system added and puts cluster.yaml back; when the check names a problem of that system (set environments.<env>.operators: owner in <repo>, an operator cluster.yaml does not list), it stops with those lines and changes nothing. Problems of other registered systems do not stop it.
Plugins: cluster 1.1.0.
An amended spec with a plan can be approved again. Amending a planned spec changes its criteria, so its plan no longer covered them; cplat spec approve refused because of the plan, and /component:plan only plans an approved spec, so the amend flow could not finish (#208). Approval now checks the spec on its own and moves a plan written for other criteria aside to plan.v<k>.md (it says so and names /component:plan <NNN> as the next step); cplat spec check no longer checks the old plan of an amended draft. /component:spec approve commits the set-aside plan with the spec.
Plugins: component 1.5.2.
cplat pr wait ignores a replaced run while the newer one is queued. A second CI run on the same commit (a label on gh pr create, a PR body edit) cancels the first; its cancelled jobs and failed ci-success made the wait red although the newer run was only queued and later passed (#244, #282). The wait now also reads the head commit’s workflow runs, queued ones included, and counts only the newest run of each workflow.
A generated password no longer drifts from the running server (#316). A cache or broker could answer WRONGPASS (cache) or an authorization error (broker) until the server restarted, because the password in its Secret was no longer the one the server had started with; a forced refresh also rewrote bucket keys and generate: values of components. This hit in-cluster caches and brokers (since 5.4.0), in-cluster buckets and the generated secrets of components (since they were introduced). Each credential is now written once: a credentials Job per owner creates each Secret if it is absent and keeps an existing one, and a server that refuses its Secret’s password restarts once (spec 128, ADR-072: A generated credential is written exactly once, by a create-only Job that cannot write it twice). An existing system has nothing to do: /platform:update and the next sync keep every existing password and key, and the change list says so per resource type.
Versions (cplat platform versions --diff v5.4.0 --changelog):
credentials-job3.13.18-alpine3.24 (new)
[5.4.0] — 2026-10-28
Section titled “[5.4.0] — 2026-10-28”Every requirement traces to its evidence. Spec 109 (spec 109). Not breaking; no plugin changes. cplat spec trace <id> --chain [--json] is a new read-only view that follows each criterion through the tracked issue, spec, tests, reviewer verdict, merged PR, release and the tags pinned in dev, staging and prod, each link ok, missing, stale, unknown or n/a. Without --chain, trace is unchanged. It never writes, exits 0 whatever the states, and offline makes at most one 10 s gh attempt and marks what it could not read unknown. cplat spec ci now prints a warning (no change to the exit code) for each test that names a withdrawn or unknown criterion.
Fix rounds get a compact failure summary, not a raw log tail. Spec 108 (spec 108), ADR-071: A failing check reaches an agent as a summary made by fixed rules, the full log stays on disk (a failing check reaches an agent as a summary made by fixed rules, the full log stays on disk). Not breaking. A new package scripts/cplat/failsum summarises a failed check with one parser per template tool format (pytest, ruff, mypy, surefire, javac, go test, vitest, eslint, tsc, kustomize, kubeconform, docker build and more; anything else is format: unknown with the last 40 lines): own-code paths, repeated entries counted once, at most 20 entries and 80 lines, secrets redacted. cplat quiet, cplat validate run, the pre-push devbox run ci and the red CI checks of cplat component ship land and cplat pr wait print it, with the full log kept on disk next to a <log>.summary.json sidecar; spec 102’s no-progress signature and recovery snapshot use the summary. Agent and brief instructions say to open the full log only when the summary is not enough. failure_summary: off in .platform-settings.yml or cplat quiet --raw restores the earlier output. A new benchmark workflow fix-rounds compares a build with summaries on and off.
Plugins: component 1.5.0, shared 1.1.0, svc 4.0.1, svc-go 0.4.1, svc-java 0.4.1, web 0.4.1.
The cache URL names its user, and CI pins its test tools. Spec 127 (spec 127). Not breaking. The url of an in-cluster cache is now redis://default:<password>@<host>:6379/0, so valkey-cli -u and client libraries take REDIS_URL as it is. A cache Secret that exists keeps its old redis://:<password>@... url (it is written once and authenticates as the same user default); new caches and new environments get the new form. /platform:update adds a url row per cache and environment to its change list. The platform’s own test tools (pytest, pytest-xdist, pyyaml, ruamel.yaml) are pinned in requirements-test.txt, compiled from requirements-test.in; CI, scripts/ci-local.sh and devbox run test-fast install only from it, scripts/check-pins.py fails on a suite command that installs them otherwise, and a monthly grouped Dependabot pip entry moves them.
Unattended runs stop wasting turns and CI rounds. Spec 126 (spec 126), ADR-070: The Stop hook holds only the run’s owning session, and only while it makes progress (the Stop hook holds only the owning session until it stops progressing). Not breaking. The Stop hook now holds only the session that started or resumed the run (the owner); other sessions are never held, and a run without an owner holds nobody and says so. The 8-stop limit is gone: two owner stops without a new commit or phase change escalate as no progress, with the last message as an excerpt and a cause chosen by a fixed rule (a matching escalation rule, else code on red evidence, else decision), so environment appears only with a matched rule. cplat component ship pr and cplat pr open check the title and, in the platform repo, the spec rule before devbox run ci and any push; --no-spec adds the no-spec label (platform repo only). devbox run ci in the platform repo runs the pr-title and spec-required checks locally through the same code as the CI jobs, and titles the platform builds are shortened to fit 72 characters and bytes. Action: resume runs that were open before the update once with /component:ship <NNN> (or /system:ship), so they get an owner.
Plugins: component 1.4.0, system 1.5.1.
The cluster-gitops validate gate checks the built manifests. devbox run validate (and quality, test, lint and the CI job quality) wrote the kustomize build to an extension-less temp file, which kubeconform skips, so the gate reported 0 resource found and passed without checking anything (#260). The build now goes to a .yaml file (#266), and the recipe fails when kubeconform checks 0 resources. Action: run /platform:update in each cluster repo; a repo with invalid manifests turns red.
Resources and environment properties upgrade safely. Spec 121 (spec 121), ADR-068: Resource and operator upgrades roll through the environments in verified steps (resource and operator upgrades roll through the environments in verified steps) and ADR-069: One versions table pins everything, and every release proves the upgrade from the previous one (one versions table pins everything, and every release proves the upgrade). Not breaking: a system.yaml without the new keys renders as before, except that engine image references gain exact tags and digests (CloudNativePG publishes no plain tags for PostgreSQL, so they move to immutable build tags). Every image and chart the platform writes or installs is a row of one versions table (cplat platform versions [--diff <ref>] [--changelog]). /resource:upgrade <name> --to <major> [--env <env>] moves one environment per pull request, needs a backup from the last 60 minutes for a database, waits for health and runs the smoke tests; staging follows dev, prod is by hand. /platform:update keeps staging and prod on their held pins and prints the versions that change per environment, and cplat platform update --verify checks the operators once per cluster. The gitops repo answers property_operators: owner | install (existing repos keep owner; new ones get install, which renders External Secrets, Kyverno and LGTM). /platform:doctor --context [ENV=]CTX lists each operator as ok, missing, older or newer with its CRD versions, and /system:status gets a Resource versions section. A new end-to-end upgrade test and a release gate (scripts/release-check.py) prove every release upgrades from the previous one.
Plugins: resource 1.6.1 (/resource:upgrade; the cache url names default), platform 1.3.0 (doctor --context, update --verify), system 1.5.0 (status).
Versions (cplat platform versions --diff v5.3.0 --changelog):
postgres 1515.19 (digest none) → 15.19 (digest f617efc0)postgres 1616.15 (digest none) → 16.15 (digest 1ec419e9)postgres 1717.11 (digest none) → 17.11 (digest ed6923f9)postgres 1818.6 (digest none) → 18.6 (digest 39add80f)valkey 88.1.11 (new)valkey 99.1.3 (new)nats 2.142.14.9 (new)nats 2.152.15.2 (new)nats2.15.1 (new)seaweedfs4.51 (digest none) → 4.51 (digest f7a49e3f)opentelemetry-collector0.162.0 (digest none) → 0.162.0 (digest 39923a8e)lgtm0.35.0 (new)velero-plugin-for-awsv1.14.4 (digest none) → v1.14.4 (digest c85a7fa8)argo-cdv3.4.12 (new)k3sv1.32.5-k3s1 (new)gateway-apiv1.2.1 (new)operator nack0.35.0 (new)operator keda2.19.0 (new)operator metrics-server3.14.0 (new)
Action: run /platform:update in each repo. Optionally answer property_operators: install after removing a hand-installed External Secrets, Kyverno or LGTM.
Instances scale on load and queue depth. Spec 120 (spec 120), ADR-066: Instances scale with a plain HPA or with KEDA, and the platform installs KEDA per cluster when an instance needs it (instances scale with an HPA or KEDA, installed per cluster) and ADR-067: Argo CD leaves the replica count of an autoscaled Deployment alone (Argo CD leaves autoscaled replica counts alone). Not breaking: a system.yaml without scale renders byte-identically. scale: {min, max, on} on a service or website component, or on one of its instances, follows one of four triggers: cpu, memory, a cache or a broker queue depth (lag). Only queue triggers may scale to zero; scale and replicas exclude each other. Argo CD ignores the replica count of a scaled workload through a RespectIgnoreDifferences rule. KEDA 2.19.0 and metrics-server 3.14.0 are installed as operators; the gitops repo answers resource_metrics: provided | install (default provided). The two “allowed:” key lists gain scale, and the e2e job limit is 90 minutes. /component:set --replicas refuses a scaled instance, /system:status shows replicas <n> (<min>–<max>), and /platform:doctor lists the KEDA and metrics rows.
Plugins: component 1.3.0, system 1.4.0, platform 1.2.0.
A message broker resource behind events APIs. Spec 119 (spec 119), ADR-065: A broker is NATS JetStream from the official chart, with declared streams and consumers and one user per component (a broker is NATS with JetStream). Not breaking: a system.yaml without a broker renders as before. type: broker declares NATS with JetStream (version, replicas, storage, or external); an events API is bound with apis.<api>.broker, and its provider and consumers get NATS_URL, NATS_USER and NATS_PASSWORD from their own Secret, plus NATS_INBOX_PREFIX and EVENTS_<API>_* as literal values. NACK is installed as an operator to create streams and consumers. An unbound events API only gets a note. Pins: nats chart 2.15.1, nats:2.14.9-alpine, nats:2.15.2-alpine, nack chart 0.35.0 with controller 0.24.0.
Plugins: resource 1.5.0 (/resource:add <name> --type broker), api 1.1.0 (/api:list shows the broker of an events API).
A cache resource, compatible with Redis. Spec 118 (spec 118), ADR-064: A cache is a platform-rendered single-node Valkey StatefulSet, without an operator; its password is generated once in the cluster (a cache is a platform-rendered Valkey StatefulSet). Not breaking: a system.yaml without a cache renders as before. type: cache declares a Redis-compatible cache (Valkey 8 or 9, memory, maxmemoryPolicy, optional persistence with storage, or external); a component that uses it gets REDIS_URL, REDIS_HOST, REDIS_PORT and REDIS_PASSWORD. No operator is installed, and cache data is not backed up. Pins: valkey/valkey:8.1.11, valkey/valkey:9.1.3.
Plugins: resource 1.4.0 (/resource:add <name> --type cache).
[5.3.0] — 2026-10-27
Section titled “[5.3.0] — 2026-10-27”Autonomy is declared per environment and supervised in the console. Spec 124, ADR-063: Autonomy is declared in system.yaml; an engine is bound by a contract and only signals can act before a merge, guide. Optional and off by default: a system without the new keys renders as before and every environment stays at observe. environments.<env>.autonomy (level, engine, autoMerge, riskGate, budget, window, undoWindow; prod at most act), a top-level autonomy: {paused: true} and components.<c>.instances.<env>.autonomy: {max} are validated by render.py, which writes applications/<system>/autonomy.json for an engine to read. /system:autonomy show|set|pause|resume|undo|approve (cplat system autonomy) changes it: lowering and pausing act at once by a signal to the engine and are recorded by a pull request, a higher level waits for the merge. The console gets System > Autonomy, a review decision in the Inbox for a fix an engine will not merge itself, engine badges and signal facts on the Board, and the notification events review, signal and done-on-its-own. cplat model show --json and cplat needs --json carry new keys.
Exception to “dev follows main”. /instance:promote <component> --to dev --version <tag> is now allowed when <tag> is a version dev pinned before (git history decides); it is the rollback and the undo for dev. Any other --to dev still fails.
Plugins: system 1.3.0 (/system:autonomy), instance 1.1.0 (dev rollback in /instance:promote).
Action. Only if you want autonomy: run /platform:update in each gitops-app repo that will declare it; create an engine token per engine and put it in CPLAT_ENGINE_TOKEN or the keychain (service sdlc-foundry, account engine:<host>) on every machine that runs /system:autonomy, and give the engine the same token; give the engine’s GitHub identity write access to the private run store repo (CPLAT_RUNS_REPO) plus issues and pull requests in the system’s repos. The engine itself is built in its own repository against the contract in the spec.
cplat pr wait and ship land wait for a queued workflow run. GitHub reports no checks for a run that is still queued, so a PR whose CI waited for a runner showed only CodeQL, and the wait called it green and tried to merge (the merge was refused). The wait now also asks for the head commit’s workflow runs and stays pending while one has not completed; a run that never starts ends in the usual timeout, unmerged.
Fixes to the platform’s own checks. Release and docs-only PRs now run the cplat tests too: the tests read the changelog, specs and guides, and release 5.2.0 left main red because they were skipped. uv run scripts/pin-actions.py, which the pin check names as the fix, now moves the templates’ pins to the platform’s own: Dependabot reads only /.github/workflows, so its Actions PRs bump the platform’s pins alone. cplat spec test-diff no longer reports a test file that a building spec’s plan task creates after the red commit. The platform repo sets worktree.baseRef: "head", so its build tasks can run in parallel worktrees.
[5.2.0] — 2026-10-27
Section titled “[5.2.0] — 2026-10-27”Databases and environments are backed up and restorable. Spec 117 (spec 117), ADR-061: Databases are backed up by the CloudNativePG Barman Cloud plugin to a bucket resource; a point-in-time restore is a new generation next to the running one (database backups with the Barman Cloud plugin, restore by generation) and ADR-062: An environment is backed up by Velero, installed once per cluster; persistent volumes by file-system backup; databases, caches and the target bucket are left out (environment backups with Velero). Not breaking: a system.yaml without backup renders as before. backup on a database instance gives scheduled base backups and continuous archiving with point-in-time restore; backup on an environment gives Velero backups of its namespaces and volumes. /system:status shows a Backups section, cplat doctor warns about an unbacked-up prod, and a failing backup older than an hour files an alert issue. Pins: plugin-barman-cloud 0.8.1, cert-manager v1.21.3, velero 12.2.1, velero-plugin-for-aws v1.14.4.
Plugins: resource 1.3.0 (/resource:backup <db> --env <env>, /resource:restore <db> --env <env> (--to <time|latest> | --switch | --discard)), system 1.2.0 (/system:restore <env> [--backup <name|latest>] [--replace]).
Action for existing clusters: after the first backup in a cluster, re-apply operators/root.yaml once (KUBE_CONTEXT=<ctx> devbox run bootstrap; local clusters: devbox run cluster-up). A cluster that already runs cert-manager or Velero (or wants to install the Barman Cloud plugin itself) sets environments.<env>.operators: owner first. Velero’s node agent is a privileged DaemonSet in the velero namespace: clusters that enforce Pod Security restricted there need an exception (or operators: owner). A cluster with a cluster repo (spec 125) lists plugin-barman-cloud, cert-manager and velero in its cluster.yaml instead.
A cluster repo hosts several systems. ADR-060: A cluster repo owns what a cluster shares and lists the systems it runs, spec 125. Not breaking: a system repo renders as before. The new shape cluster-gitops is a repo for one cluster: cluster.yaml lists the shared operators, the secret store and the systems (cluster/systems/<system>.yaml, one Argo CD application each). A system with operators: owner in every environment leaves its operators to the cluster, and a CI check names any operator or release a system still needs. The versions table pins external-secrets 2.12.0 and kyverno 3.9.1.
Plugins: new cluster 1.0.0 (/cluster:new <name>, /cluster:add <owner>/<repo>, /cluster:remove <system>).
Action for shared clusters, once per cluster: create the cluster repo and move the cluster to it as described in Shared clusters. Nothing is reinstalled. Clusters with one system need nothing.
[5.1.0] — 2026-10-27
Section titled “[5.1.0] — 2026-10-27”A local console shows the system, its process and what needs you. Spec 122, ADR-055: The local console is a stdlib web server over cplat JSON; it acts only through Claude Code, guide. /platform:ui (cplat ui) serves a browser console on 127.0.0.1 with three places: Inbox (decisions waiting for you), Board (every item by stage) and System (components by environment). Actions run as cplat headless in the local clone; the console never writes to git, GitHub or the cluster itself.
The documentation is in the console. Menu → Guide (or g) shows the console guide with its screenshots, the process with every stage, and a reference of every 5.x slash command with its usage and a copy button, read from the platform checkout the console runs from; each page’s help links to its section (AC-122.52).
cplat needs and the run list. cplat needs --json lists what waits for a human (approvals, answers, escalations, promotions). The run list (cplat runs --json) gives every run a phase and a stage.
Status and spec additions. cplat system status --json and cplat spec list --all --detail --json carry more keys for the console. Deliberate test change: the AC-113.11 status tests now compare the 5.0 keys, because spec 122 adds keys.
Draft spec PRs. /component:spec and /system:spec publish a spec as a draft pull request (cplat spec publish) and ask the owner to review; approving lands it (cplat spec land).
Notifications. ~/.config/sdlc-foundry/notify.yaml chooses per event whether GitHub, desktop, Slack or email is told, instantly or in a digest, with quiet hours. Secrets come from the environment or the OS keychain, never the file. cplat ui notify --once runs from cron or launchd.
--provides and --consumes. /component:add and /component:set declare the APIs of one component; a new API goes into apis: in the same PR.
/system:set. Changes system-wide settings such as --env (cplat system set).
Plugins: platform 1.1.0 (/platform:ui), system 1.1.0 (/system:set), component 1.2.0 (--provides, --consumes, draft spec PRs).
cplat pr wait and ship land never call a conflicting PR green. GitHub starts no pull_request workflow while a PR conflicts with its base, so only CodeQL reported and the wait said GREEN. The watch now asks whether the PR is mergeable and ends with “conflicts with its base branch” and how to fix it (#221).
Fixes to platform update --migrate. A repo without .claude/project-notes.md now gets the file, created empty, because the new CLAUDE.md imports it; an existing notes file is still never changed (#239). The “left out N new starter file(s)” line no longer lists the system.yaml the migration writes on the next line (#238).
An object storage resource: type: bucket. ADR-059: An in-cluster bucket is its own SeaweedFS, run by the platform-installed SeaweedFS operator; its keys are generated in the cluster and Argo CD never deletes it, spec 116. Not breaking: a 5.0 or 115 system.yaml renders as before. A system declares buckets under resources.<name> with type: bucket (storage, default 10Gi, and versioning, default off, per resource or instance). An in-cluster bucket is its own SeaweedFS per instance; external buckets read endpoint, bucket, region, forcePathStyle, accessKeyId and secretAccessKey from the secret store. A component that uses a bucket gets S3_* and AWS_* (prefixed with two or more buckets). Pins: SeaweedFS image chrislusf/seaweedfs:4.51 and operator chart seaweedfs-operator 0.1.45. cplat platform doctor compares the installed SeaweedFS operator.
Plugins: resource 1.2.0 (/resource:add <name> --type database|bucket [--storage S] [--versioning]).
Action. One human step per existing cluster, after the first in-cluster bucket: re-apply operators/root.yaml (KUBE_CONTEXT=<ctx> devbox run bootstrap; local clusters: devbox run cluster-up). A cluster whose owner installed the SeaweedFS operator by hand: set environments.<env>.operators: owner before operators/root.yaml is applied again. Existing repos: run /platform:update.
Resources are named, typed and may be external. ADR-057: Resources are named and typed; each type has a fixed contract; any resource instance may be external, ADR-058: Images and operators are pinned exactly per release; the platform installs the operators a cluster needs as Argo CD applications, spec 115. Not breaking: a 5.0 system.yaml renders as before. A system declares any number of resources under resources.<name> with a type (database is built; bucket, cache and broker are reserved for specs 116, 118 and 119). A database can be external (optionally with remote.path and a per-environment instances.<env>.external): the platform renders an ExternalSecret from the secret store and no cluster. A component that uses two or more resources of one type gets prefixed variables (ORDERS_DB_DATABASE_URL). cplat model show lists type and external; cplat platform doctor compares the installed operator.
Pinned database image, supported majors 15–18. The database image is pinned exactly per major. The one narrowing against 5.0: another version (for example 14) now fails the render and names the supported majors 15, 16, 17 and 18.
Operators live under operators/. The platform installs CloudNativePG through operators/ instead of leaving it to the cluster. environments.<env>.operators: owner leaves the operator to the cluster owner.
Plugins: resource 1.1.0 (/resource:add <name> --type database [--version N] [--external [--path P]], /resource:remove <name>).
Action. One human step per existing cluster: kubectl apply -f operators/root.yaml (or KUBE_CONTEXT=<ctx> devbox run bootstrap; local clusters get it from devbox run cluster-up). A cluster that already runs a hand-installed CloudNativePG: set environments.<env>.operators: owner before anyone applies it. Existing repos: run /platform:update; databases on a major other than 15–18 must move to a supported one before the render passes.
[5.0.1] — 2026-10-26
Section titled “[5.0.1] — 2026-10-26”Fixes:
cplat platform doctorin the platform repo reportsgate recipesandgate CI jobsas not applicable instead of warning and suggesting/platform:update, which refuses there (#231, AC-123.24).
[5.0.0] — 2026-10-26
Section titled “[5.0.0] — 2026-10-26”BREAKING: the platform speaks the domain model. ADR-054: The 5.0 interface: system.yaml checked by the renderer, noun plugins with tombstones, and a migration that proves itself, ADR-053: A domain model based on Backstage’s system model, with environments, guide, spec 114. A system is one file, applications/<system>/system.yaml, that the renderer checks; slash commands and cplat subcommands are grouped by noun (system, component, api, resource, instance, issue, platform). Cluster names (namespaces, Argo applications, image names) never change, so a migrated system renders the same manifests.
Plugins: new system, api, resource, instance, issue, platform 1.0.0 and component 1.1.0; svc 4.0.0, shared 1.0.0, app 2.0.0, gitops 2.0.0; web, svc-go, svc-java 0.4.0. shared keeps the cplat launcher, the hooks and the quality and security agents and no working command; the planner agent moved to system; the v1-only gitops deployment agent is gone. The last major of app and gitops holds only tombstone commands (no agents, no hooks), and app and gitops leave the marketplace in 6.0.
The platform repo declares its own shape. ADR-056: The platform repo declares its own shape: a template-less registry entry, set only in the committed settings file, spec 123. This repo declares shape: platform in .platform-settings.yml, so /component:spec, plan, build, verify and ship work here like in a component repo (svc agents, not deployable: ship ends at merge). /component:release and cplat platform update refuse in it (the platform is released through CONTRIBUTING.md, Releases), cplat platform doctor reports the platform source version, and the fleet skips it. Only platform can be declared; a shape: key in the personal settings file, a component shape or a declaration next to a template .copier-answers.yml is an error.
Fixes:
- A bare
offinbudget:(.platform-settings.ymlor the user settings file) is read as off; YAML parsed it asfalse, which was refused. cplat pr openandcplat component ship prin a git worktree no longer commit the PR body: it moves into the worktree’s own git dir, and a.git/…--body-fileresolves there (#227).
Old → new. Every 4.7 slash command stays as a tombstone: it runs nothing and prints where the command went. A 4.7 cplat name prints `cplat <old>` is `cplat <new>` in 5.0 to stderr and exits 2.
| 4.7 slash command | 5.0 |
|---|---|
/svc:build |
/component:build |
/svc:fix-bug |
/component:fix |
/svc:plan |
/component:plan |
/svc:quick-task |
/component:fix |
/svc:release |
/component:release |
/svc:ship |
/component:ship |
/svc:spec |
/component:spec |
/svc:specs |
/component:spec |
/svc:verify |
/component:verify |
/app:build |
/system:ship |
/app:plan |
/system:plan |
/app:ship |
/system:ship |
/app:spec |
/system:spec |
/app:specs |
/system:spec |
/gitops:addon |
/resource:add and /resource:remove; observability is set under environments.<env>.observability in system.yaml |
/gitops:compose |
/component:add (also set, remove) |
/gitops:promote |
/instance:promote (now --to <env>) |
/gitops:secret |
/instance:secret (list: /instance:secret list) |
/shared:check-quality |
/component:check |
/shared:doctor |
/platform:doctor |
/shared:fleet |
/platform:fleet |
/shared:new-app |
/system:new |
/shared:new-service |
/component:new |
/shared:report-issue |
/issue:report |
/shared:status |
/system:status |
/shared:triage |
/issue:triage |
/shared:update-service |
/platform:update |
| 4.7 cplat subcommand | 5.0 |
|---|---|
cplat new-app |
cplat system new |
cplat status |
cplat system status |
cplat app-ship |
cplat system ship |
cplat new-service |
cplat component new (--type and --lang replace --web, --library, --gitops, --app and shape ids) |
cplat compose |
cplat component add, set, remove |
cplat ship |
cplat component ship |
cplat build |
cplat component build |
cplat addon |
cplat resource add, remove |
cplat promote |
cplat instance promote (<component> --to <env>) |
cplat secret |
cplat instance secret |
cplat smoke |
cplat instance verify |
cplat triage |
cplat issue triage |
cplat doctor |
cplat platform doctor |
cplat update-service |
cplat platform update |
cplat fleet |
cplat platform fleet |
cplat runs |
cplat platform runs |
cplat retro |
cplat platform retro |
New: cplat api list. Unchanged plumbing: spec, pr, headless, budget, quiet, validate, deps, shape, feedback, adopt, alerts, model (cplat model show --json carries schema: 2).
| 4.x file or key | 5.0 |
|---|---|
applications/<system>/app.yaml and services.yaml |
one applications/<system>/system.yaml (comments are not carried over) |
| directory name | system |
gateway, secretStore, policies.registries |
top-level gateway, secretStore, registries |
hosts.<env>, urls.<env> |
environments.<env>.host, .url ({service} is {component}, {app} is {system}) |
policies absent or false |
policy: "off" on every environment |
policies.mode |
environments.<env>.policy, lowercased |
promotion.staging: auto or manual |
environments.staging.promote: after-dev-smoke or manual |
promotion.prod: manual |
dropped (prod is always manual) |
addons.observability |
environments.<env>.observability on every environment |
addons.postgres |
resources.postgres with type: database (per-environment values under instances.<env>) |
| service entry | components.<name> |
shape |
type and lang |
environments of a service |
instances keys |
per-service replicas map and promotion.staging |
instances.<env>.replicas and instances.<env>.promote |
cplat promote <from> <to> |
cplat instance promote … --to <env> |
| “addon”, “product”, “service” in docs and prompts | resource, system, component |
Migration steps (a repo that is not migrated keeps working only through the tombstones, which tell you what to run):
- Run
/plugin marketplace update, then enablesystem,component,api,resource,instance,issueandplatform; the migration switches the repo’s plugin settings for you. - In every repo run
cplat platform update --migrate, or from one place run/platform:fleet updateover the whole fleet. In a gitops-app it convertsapp.yamlandservices.yamlintosystem.yaml, proves that the render is unchanged (or says exactly which key or file differs), and opens a PR; the fleet merges a proved one on green CI and reports the repos that failed. - A repo the converter cannot convert (an unknown key, a per-environment
versionthat differs) stops with the key named; fix that key by hand and run it again. - Replace old command names in your own scripts, CI and notes with the table above.
[4.8.0] — 2026-10-26
Section titled “[4.8.0] — 2026-10-26”A domain model core in cplat, run state per worktree, and gates for the platform repo itself.
Domain model and tests (112, 113):
- Feat (113): a domain model core in cplat. ADR-053: A domain model based on Backstage’s system model, with environments, guide One module now answers “this component in that environment” for
status,promote,smoke, the staging promotion, the alert poll,secretand the/app:shipgate. Newcplat model show [--json]prints a product in the model’s nouns (cplat only, no slash command). No command, flag, output format or file schema changed, so there is no action for existing repos. The JSON carriesschema: 1and may change in 5.0. - Perf (112): the cplat test job is faster without being weaker. Tests that only need a rendered repo share one render per process and get private copies (
new_service,shared_renders; ADR-052), and CI runstests/cplatin 4 round-robin shards (--shard I/N) with acplat shards cover the suitejob that fails when a test runs in no shard or in two;ci-successneeds it. Measured locally: the full suite went from 149 s to 90 s wall on 12 cores.
Run records and escalations (087, 088, 090):
- Feat (087, #207): the run records say why a
pr waitended. guide Apr-waitrecord has apr_waitsection: the result (green,merged,red,budget-refused,timeout,no-checks,closed,merge-failed,error) and the failing or pending check names.cplat runs reportand the weekly retro showpr-wait results:and the top 5 red checks, and count red CI asred-ciinstead offailed(the record keepsoutcome: failed). The public retro issue shows checks ascheck <n>. Older records count asunrecorded. - Feat (088, #207): escalations without a cause are classified. guide Fixed rules match the reason of an escalation that names no allowed cause: git approval waits and a dirty working tree become
environment, a spec/test conflict and a build left open for review becomedecision. The record getscause_source(given,rule,none); a stated cause always wins and no rule yieldsplatform. The ship stop gate giving up and headlessno progressnow recordenvironment.cplat runs reportshowsUnclassified escalations: <n> of <m> (<p>%)with the reasons still unknown (--jsonkeyunclassified), and the weekly retro shows the unknown share week over week. Older records are classified when read. - Fix (090): a failing retro proposal step shows why. When
claudeexits non-zero,cplat retro proposeprints the last 20 lines of its output to the job log.claude exited with 1alone could not be diagnosed: the output stayed inpropose.logon the ephemeral runner. The posted issue keeps the short reason.
Parallel sessions and the platform repo:
- Fix: run state is per worktree.
cplat-ship.json, the app-ship state, thecplat runs beginpointer, the recovery snapshot and pending replans now live in the worktree’s own git dir instead of the dir every worktree shares, so one session’s ship run no longer blocks another session’s Stop hook, and a build in one worktree cannot attach to another’s run. A worktree no longer sees another checkout’s run; a run started inside a worktree before this fix must be resumed from the main checkout or restarted. The main checkout’s.gitis unchanged. The gitops merge slot and the validation evidence cache stay shared (ADR-051). - Fix:
cplat pr waitignores runs a newer run superseded. When several runs of one workflow exist for the PR’s head commit (a label added to a fresh PR starts a second run that cancels the first), only the newest run’s checks count, so the cancelledci-successof the older run no longer ends the wait red. - Chore: devbox gates in the platform repo.
devbox run test-fast,test,quality,ciandlint-fixnow exist here, so the/svc:shipand/svc:buildgates and the ship Stop hook find their scripts. No action for existing repos.
Action for existing repos:
- Run
/plugin marketplace update(shared 0.17.0; svc, gitops, app, web, svc-go, svc-java unchanged). - Nothing else is required: no command, flag, output format or file schema changed.
cplat fleet updatebrings repos to 4.8.0 when wanted.
[4.7.0] — 2026-10-26
Section titled “[4.7.0] — 2026-10-26”Runs stay within a budget, fix rounds run only what a change can affect, and prod runs exactly what staging tested.
- Cost and loops (102, 103): one budget per run tree (on by default: 180 min, 60 agent invocations, 6 CI reruns, $15 per ship), a stop after two equivalent failures, a recovery snapshot; fix rounds validate only what a change can affect while the final gate and PR CI stay full.
- Deploys you can trust (105, 106): the dev smoke records the image digest the cluster runs and never counts without it; staging promotion and prod promotion compare registry digests; release tags re-tag the tested
sha-<40>image instead of rebuilding. - Generated repos (104): strict lockfile installs, one metrics middleware with a bounded
routelabel. - Live usage (101, optional): Claude Code’s own OpenTelemetry metrics for headless and benchmark sessions, local backends only unless opted in, a Grafana dashboard.
- Measured on the LAN cluster: a full
/svc:ship(plan → build → PR → merge → pin → Argo healthy) costs about $1.20–1.60 per run; quick-task $0.15, build $1.16–1.26 (estimates at list prices).
Action for existing repos:
- Run
/plugin marketplace update(svc 3.6.0, shared 0.16.0, gitops 1.6.0; app, web, svc-go, svc-java unchanged). - Before
/shared:update-service: in a repo whose lock file is out of date, runuv lock(Python) orpnpm install(web) once, since images now install strictly. - Run
/shared:update-servicein every service repo (orcplat fleet update). Until it lands, release tags rebuild the image, its digest differs from staging, andcplat promote … staging prodrefuses without--accept-digest-mismatch "<reason>". - Dashboards that used the metric label
endpointmust useroute. - Set the product’s kube context once (
export CPLAT_KUBE_CONTEXT=<context>orkube_context:in the settings files) so unattended ships check Argo, smoke and the image digest. - Optional:
CPLAT_OTLP_ENDPOINTortelemetry:settings for live usage in Grafana;--budget KEY=VALUE|offto change a run’s limits.
- Feat (101): run usage is exported live to OpenTelemetry (optional). guide
cplat headlessandcplat runs benchmarkturn on Claude Code’s native OTLP export whenCPLAT_OTLP_ENDPOINTortelemetry: {endpoint}is set, or the gitops-app hasobservability: {ui: lgtm}and a kube context is configured (telemetry: {context}/CPLAT_KUBE_CONTEXT; never guessed). Remote endpoints needallow_remote; prompt and tool content are always off; resource attributes carry the run id, kind, repo and platform version. The run record getsusage.otel(OTel totals vs transcript totals); reports are unchanged. The lgtm stack gets a Claude Code usage dashboard (rerundevbox run cluster-up). - Feat (102): runs stay within a budget and stop repeating failures. ADR-043 — One budget per run tree, kept in a local ledger and checked at safe points, guide
- Behaviour change: every unattended run tree has one budget, on by default: per ship 180 min, 60 agent invocations, 6 CI reruns, $15 estimated cost, multiplied by the ships a root covers.
--budget KEY=VALUE(oroff) oncplat headless,ship start,app-ship start,runs begin;budget:in the settings files. A stop escalates with causedecisionand is resumable; usage continues across resumes. - New shared-plugin
PreToolUsehook onTask|Agentcounts and, when the budget is used up, refuses subagents inside a recorded run. - Two equivalent failures of a stage in a row without progress stop with
no progress: <stage>; every escalation writes a local recovery snapshot;cplat budget resumelists the stages still verified for HEAD.
- Behaviour change: every unattended run tree has one budget, on by default: per ship 180 min, 60 agent invocations, 6 CI reruns, $15 estimated cost, multiplied by the ships a root covers.
- Feat (103): validation runs what a change can affect. ADR-044 — Change-aware validation in the inner loop only, decided by rules in code, guide
- Behaviour change: build and ship fix rounds use
cplat validate run(rulesfull,mandatory:<category>,docs-only,mapped,no rule; local evidence reuse for identical inputs). The final gate and PR CI still run everything; skips the final gate contradicts count as misses incplat runs report. Opt out with--full,validation: fullorCPLAT_VALIDATION=full. shapes.ymlgainsvalidation: {selector, mandatory};cplat runs benchmark <wf> --compare-validationruns a task with and without selection.
- Behaviour change: build and ship fix rounds use
- Generated repos build strictly and record every request (104). The
service-pythonDockerfile installs withuv sync --lockedandweb-nextjswithpnpm install --frozen-lockfile: no fallback that re-resolves silently, and the lock file is required. Both Dockerfiles are template-owned, so/shared:update-servicebrings the strict install. Migration: a repo with an out-of-date lock must runuv lock(python) orpnpm install(web) and commit the lock before/shared:update-service, or its image build fails.service-pythonrecords every request in oneMetricsMiddleware: the metric labelendpointis renamedroute(the matched path template,unmatchedfor no route), so dashboards and alerts that usedendpointmust follow; unhandled errors count as status 500 and inapplication_errors_total;/metricsis not recorded. With observability off,metrics.py, its test andprometheus-clientare left out. The template smoke CI jobs check that a stale lock is rejected. - Feat (105): a deploy counts only after its checks pass. guide
- Behaviour change: the dev smoke records the image digest the cluster runs (
cluster.running_digest); without a readable digest, or without--context, smoke isnot-run(image digest unknown) and nothing is promoted. Staging is pinned only while the registry digest of the tag (registry.digest) still equals it; otherwise ship escalates with causedecision(digest changed since smoke). Anot-runsmoke printsNOT VERIFIED. - Red smokes are classed
environment(URL unreachable or 502/503/504, every test failing) orapplication: environment failures are tried up to 3 times, 30 s apart, then escalate with causeenvironmentand no issue. A replay re-smokes unless the record holds a pass for the same digest. The prod evidence summary shows the checked digest and flagsdigest differs from what staging runs. - New “Deploy gate” table in the unattended-delivery guide; a test fails when a row names a missing test.
- Behaviour change: the dev smoke records the image digest the cluster runs (
- Feat (106): prod runs the exact image staging tested. guide
- Behaviour change:
cplat promote … staging prodcompares the registry digests of the stagingsha-<40>tag and the release tag for every service before writing anything, and refuses on a mismatch, a missing tag, a failed lookup or a staging tag that is not asha-<40>build.--dry-runshows both digests;--accept-digest-mismatch "<reason>"accepts a mismatch only;--no-verifyprintsimage identity not verified.registry.digestspages through all package versions. - Migration: run
/shared:update-servicein every service repo. The CI ofservice-python,service-java,service-goandweb-nextjsno longer rebuilds onv*.*.*tags; a newrelease-imagejob re-tags the testedsha-<40>image asX.Y.Z,X.YandX(and fails if main has not published it, so wait for CI on main before tagging). Repos that are not updated keep rebuilding releases, andpromotewill refuse those releases as a mismatch.
- Behaviour change:
- Feat (107, part 1): benchmark arms.
cplat runs benchmark <wf> --arm <name>applies a named local change to the sample repo before the run (testmon, pytest-impact, semgrep, trivy, codebase-memory inscripts/cplat/benchmark/arms/). The reset after every run removes it. The record names the arm (baselinewithout one), andcplat runs report --benchmarksshows it in the row key.--recallruns the full suite after the session and fails the record when the arm deselected a failing test. Arm files are written verbatim, so no local path reaches the public sample repo. The trials and decision records follow in part 2. - Fix (067):
cplat ship startswitches to the feature branch itself. Before the merge it createsfeature/<spec>from the current HEAD, or switches to the local or remote one, and prints which. A headless coordinator improvisedgit checkout -B …, which the template allowlist rightly refuses because it resets a branch, and a ship benchmark escalated in phase plan.ship.mdnow says never to create, reset or switch branches by hand. - Fix (090): the weekly retro workflow is valid.
cplat-retro.ymlsetRETRO_DIRfrom${{ runner.temp }}in the jobenv:, where GitHub does not allow therunnercontext, so GitHub rejected the file: the retro never ran and every push of a run record showed a failed run in the store repo. The steps use$RUNNER_TEMP/retronow, and CI runs actionlint overscripts/cplat/assets/workflows/. Store repos set up before this fix: replace.github/workflows/cplat-retro.ymlthere with the new asset (cplat runs initleaves an existing file alone). - Fix (091):
cplat ship deployuses the configured kube context. Headless coordinators rarely pass--context, so a ship against a real cluster skipped Argo, smoke and the digest check (found in the ship benchmark on the LAN cluster). Without--contextor a product link, deploy now takesCPLAT_KUBE_CONTEXT, thenkube_context:in.platform-settings.yml, then the user settings file, and prints which one. It never guesses from kubectl. - Fix (099):
cplat runs report --benchmarksseparates coordinator models and failed runs. Each row is one workflow, platform version and coordinator model (unknownfor records from before spec 100); the median covers done runs only, and runs that did not finish are counted with their own median. The JSON key gains(<model>)and each rowcoordinator_model,doneandnot_done. - Fix: platform updates no longer flag every template change as a local customisation. The spec 086 baseline (the template at the repo’s previous version) is archived from the checkout
cplatruns from, and the marketplace clone is shallow and has no tags. The baseline was silently missing, socplat fleet updateand/shared:update-servicelisted the template’s own changes between versions as overwritten customisations and left every PR open. A missing release tag is now fetched from origin first. - Fix: a fresh gitops-app repo passes its own secrets scan. Three template lines (
CLAUDE.md,README.md,scripts/render.py) tripped detect-secrets’ keyword detector; they carrypragma: allowlist secretnow, and a test renders every template and runs itssecrets-scan. Repos updated from before 4.6.0 still rundevbox run secrets-baselineonce (their baseline has no filters, see 4.6.0). - Fix:
cplat triage applychanges only labels that differ. Removing a label the repo lacks (--remove triagebeforesetup-labels) no longer fails the whole edit; labels the issue already has, or lacks, are reported as “already as asked” and left out of the undo hint; adding a label missing in the repo stops with a pointer tocplat triage setup-labels.triage listnames missing labels (missing_labelsin--json). - Chore (099): the benchmark sample is pinned to
ika100/todo-api@9cba320(its 4.6.0 skeleton and the Dependabot bump) instead of resetting them away; every workflow’stask_versiongoes up by one, so its records say which sample revision they ran on. - Docs: the SDLC map for 4.6.0 (How it works). A swimlane map of the delivery flow (you, agents,
cplat, GitHub/Argo, with dev smoke and auto-staging inside the unattended band) and a lane for the feedback loops, plus a table of the loops. The docs site sync now serves images fromdocs/img/. - Docs: optional live view with dot-agent-deck (guide, #145). Setup checked against dot-agent-deck v0.48.1: what it shows and controls, what stays with the run store and the gates, the hooks it writes into
~/.claude/settings.json, local data and remote-host security, limitations. Nothing in the platform depends on it.
[4.6.0] — 2026-10-25
Section titled “[4.6.0] — 2026-10-25”Runs cost a third less, ship checks the spec in dev and promotes to staging, and a whole product ships across its repos.
- Leaner runs (100): headless coordinators run on Sonnet (
--model,coordinator_model),cplat pr opencommits, pushes and opens the PR in one step, agents start from a compact brief,cplat quietkeeps green output short. Measured: quick-task $0.48 → $0.15, build $1.94 → $1.26 (estimates at list prices). - Dev and staging (091, 093):
cplat shipruns the spec’s smoke tests against dev and, withpromotion: {staging: auto}, promotes a green build to staging and smoke-tests it there; prod stays manual with an evidence summary. - Whole products (097):
/app:shipships one product spec across its repos in dependency order. - Feedback loops (090, 092): a weekly retro turns the run store into spec proposals; firing alerts become deduplicated issues.
- Fixes: headless sessions can edit files and commit in any flag order; update-service refreshes devbox.lock and keeps a repo’s secrets baseline;
cplat pr waitjudges only the PR’s current head; the benchmark checks folder trust and its result.
Action for existing repos:
- Run
/plugin marketplace update(svc 3.5.0, shared 0.15.0, app 1.1.0, gitops 1.5.0, svc-go/svc-java/web 0.3.0). - Run
/shared:update-servicein every repo (orcplat fleet update), gitops-apps first. It brings the dev smoke recipes,pin-staging.ymland the widergit commit *allowlist. - Optional: add
promotion: {staging: auto}to a gitops-app’sapp.yamlfor automatic staging; setcoordinator_modelif a workflow should keep Opus. - Run store:
export CPLAT_RUNS_REPO=<owner/name>(a private repo,cplat runs init); the weekly retro there needsCLAUDE_CODE_OAUTH_TOKEN,RETRO_GH_TOKENand a self-hosted runner.
- Feat (100): leaner runs — cheaper coordinator, fewer turns, smaller context. ADR-036 — The coordinator runs on Sonnet; project and user settings choose otherwise, ADR-037 — cplat commands that bundle steps never widen the session’s permissions
- Behaviour change: headless sessions start the coordinator on Sonnet (
cplat headless --model sonnet|opus|haiku;coordinator_modelin.platform-settings.ymlor~/.config/sdlc-foundry/settings.yml, also per workflow). Architect, reviewer and product-manager keep their own model. cplat pr opencommits (-s), pushes and opens the PR in one step (idempotent; ends at a ready PR whengh pr createis not allowed). quick-task, fix-bug and build use it; PR bodies go to.pr-body.md(cplat moves them out of the work tree; Claude Code refuses edits under.git/).cplat quiet <script>: one line on success, the tail and the log path on failure. Agent briefs carry the criteria, task, contract, tests and commands; agents read spec/plan/design only for what the brief lacks. The Python templates’test-fastis-q --tb=line.- Rare branches of build, ship and quick-task live in
plugins/svc/references/, read when the branch is taken. cplat runs benchmark … --model M --plugins <worktree>/pluginsmeasures unreleased plugins (and theircplat).- Measured (benchmark on
ika100/todo-api, estimated list prices): quick-task $0.48 → $0.15, build $1.94 → $1.26 (median of two), every run done with a PR and green gates. Most of it comes from the Sonnet coordinator andcplat pr open. Details: docs/specs/100-leaner-runs-cheaper-coordinator-fewer/benchmarks.md. - Found on the way:
build.mdtold the coordinator that push/PR need confirmation; testers wrote files through shell heredocs (now Write/Edit only).
- Behaviour change: headless sessions start the coordinator on Sonnet (
- Fix (079):
cplat pr waitjudges only the PR’s current head commit. Right after a push it reported the previous commit’s red (or green with--merge); checks of other commits are ignored, a new head restarts the wait, and--mergepins--match-head-commit. - Feat (097):
/app:shipships a whole product across repos. ADR-042 — A product ship coordinates one repo ship per component, gated per repo, with serialized gitops mergescplat app-ship(state in the gitops-app’s git dir, never committed) coordinates onecplat shipper component repo: the spec/plan PR is merged on green first, repos run in plan order (at most--max, default 3) once their dependencies passed their gate (exposed service: Synced/Healthy and passing smoke; internal service: Synced/Healthy; library: merged PR), and the gitops-app’s PRs merge one at a time through a merge slot.cplat ship start --product DIRlinks a repo ship to the product run. A repo that escalates stops its dependents only. Oneproduct-shiprun record is written at the run end.- New command
/app:ship <spec> [--max N] [--context C]; a second Stop hook entry keeps the session going until the run is done or escalated./app:buildis unchanged. It refuses without a kube context for the product’s cluster.
- Feat (093): a green dev smoke promotes staging. ADR-041 — Staging follows a green dev smoke; prod stays a manual promotion with evidence With
promotion: {staging: auto}inapp.yaml(or on a service inservices.yaml),cplat ship deploydispatches the gitops-app’s newpin-staging.yml, which opens a self-merging PR, then waits for Argo and runs the staging smoke (red:smoke-failureissue, causecode, no rollback).cplat promote … staging prodprints an evidence summary from the run store first, with a WARNING when the release has no green staging smoke. Prod stays manual. Migration: existing gitops-apps stay off; run/shared:update-serviceto getpin-staging.yml, then addpromotion: {staging: auto}toapp.yaml. New gitops-apps have it on. - Fix: templates allow
git commit *. Listing flag orders (-m,-s -m,-q -m…) kept missing the next one (-am,-a -s -m), and each stopped a headless build. Commits stay local;git commit --amendstill asks, and pushes keep their own rules. - Feat (090): a weekly retro proposes the next specs. ADR-039 — The weekly retro: a deterministic report in code, agent proposals checked against it
cplat retro [--week W] [--json] [--out DIR]builds one report of the last complete week from the run store, the auto-filed platform issues and the agent metrics of spec 089, each figure next to the previous week. By hand it prints and writes nothing.cplat runs initadds a workflow to the run store repo that runs it Mondays 06:00 UTC: report, then headless Claude proposes at most 5 specs and 3 agent prompt changes, thencplat retro postchecks every proposal against the report’s evidence (an unsupported one is refused and counted) and posts.- The posted
retroissue inika100/sdlc-foundryhas counts only (no repo, spec or store names, reasons asreason 1); the issue of an earlier week is commented on and closed. The full report is committed underretros/in the store. - Action: set the store repo secrets
CLAUDE_CODE_OAUTH_TOKEN(fromclaude setup-token) andRETRO_GH_TOKEN(fine-grained: Issues read/write on the platform repo, Contents read/write on the store), give the store a self-hosted runner (CI_RUNNER), rungh auth refresh -h github.com -s workflow, thencplat runs init <owner/name>again.cplat doctorwarns about whatever is missing.
- Feature (091): ship checks the spec in dev with smoke tests. A criterion ending in
(smoke)is checked in dev after deploy: every deployable template hasdevbox run smoke(SMOKE_BASE_URL, one output contract), web repos get the standard Playwright scaffold (needs_e2eis ignored), andcplat smoke runruns it by hand.cplat ship deployruns smoke against the gitops-app’s dev URL (one retry after 30 s, 600 s limit); red smoke escalates with causecode, files asmoke-failureissue in the service repo and resumes smoke-only; the run record gets asmokesection (ADR-040 — Smoke tests run against a deployed environment through one recipe and one output contract). Migration: run/shared:update-service, thencplat triage setup-labelsif thesmoke-failurelabel is missing. - Fix (094):
cplat spec task-doneno longer rewrites plan.md. It setsdone: trueon the task’s own line(s) and keeps the rest of the file byte for byte (no block-YAML re-serialisation, no rewrapped summary). - Fix (095): a good dependency bump is not reverted for an unrelated red check. When the bump clears every dependency finding but the security gate stays red for another reason (bandit, secrets),
cplat deps patchkeeps the bump, prints it and exits 4 so the coder takes over, instead of reverting and blaming it. Unrated fixable findings were already bumped (severity does not select, AC-095.12). - Fix (088): redaction edge cases. A run of 40+ repeated characters is only kept as padding when it is not glued to other word characters (
ghp_AAAA...is redacted again), and a private name is replaced as a whole word (todono longer matches insidetodo-apiormytodo). The gh error mapping now has a test. - Fix (087):
cplat runs reporthas an upper bound. Records ended after the window’suntil(future-dated) no longer count. - Fix: skeleton updates keep the repo’s secrets baseline.
.secrets.baseline.jsonrecords a repo’s own false positives;/shared:update-servicereplaced it with the template’s, so the secrets scan failed again after every update. It is now repo-owned (_skip_if_exists) and only added when a repo has none. - Fix: template allowlists cover headless commits and plugin reads.
git commit -q -mwas missing (the other flag orders were there), andRead(~/.claude/plugins/**)lets agents read the plugins’ reference files (for example the spec-format plan and design formats) outside the repo. Each stopped a headless/svc:plan+/svc:buildin the 099 benchmark. A test now checks every template. - Fix (099): the build benchmark plans first.
/svc:buildneeds aplan.md, socplat runs benchmark buildruns/svc:plan 900and then/svc:build 900as two phases of one run (task_version 3); a failed step stops the run andresult_checknames it. - Fix:
cplat update-servicerefreshesdevbox.lock. After mergingdevbox.jsonit runsdevbox install, so the lock matches and the next devbox run no longer dirties the tree (a headless/svc:quick-taskstopped on that). Without devbox, or when the install fails, the report names the step. - Fix: templates drop the
Bash(kubectl --context * get|describe|logs *)rules. The wildcard before the subcommand also approved any options placed there;kubectl get|describe|logs *already cover the read-only calls (with--contextafter the subcommand)./shared:update-serviceremoves the old rules from each repo.
[4.5.0] — 2026-10-25
Section titled “[4.5.0] — 2026-10-25”Every run is recorded with its token usage and cost, platform failures become issues, and unattended sessions can finally edit files.
- Run store and feedback loops (087–089): each ship, build, headless and PR-wait run is recorded in a private run store; platform failures become deduplicated issues; agent quality is measured per agent, shape and model.
- Token usage and cost (099):
cplat runs usage, usage and gates in every run record, cost per delivered run incplat runs report, andcplat runs benchmarkagainstika100/todo-apifor a repeatable baseline. - More autonomy (094–096): a merge conflict re-plans, fixable CVEs are patched by the run,
cplat fleet updaterolls platform updates out to every repo. - Safer repos (085, 086, 098): a broken spec names itself and blocks nothing else, repo gates survive skeleton updates, gitops CI checks Components against the service branch’s CRDs.
- Fixes: headless sessions run with
acceptEdits(indontAskthey could edit nothing);/svc:quick-task,/svc:fix-bugand/svc:releaseno longer stop before the PR.
Action for existing repos:
- Run
/plugin marketplace update(svc 3.4.0, shared 0.14.0, app 1.0.2). - Run
/shared:update-servicein every repo (orcplat fleet update), gitops-apps first. - Optional:
cplat runs init <owner/name>with a private repo to keep run records; until then they are buffered locally. - Claude Code 2.1.132 or later is needed for usage per run (
$CLAUDE_CODE_SESSION_ID).
- Fix:
/svc:quick-task,/svc:fix-bugand/svc:releaseno longer claim that push andgh pr createneed confirmation. Both are allowlisted since 4.4.0; the stale text made a headless quick-task stop after green tests without committing (found by the 099 benchmark). - Fix (070): headless sessions can edit files.
cplat headlessrunsclaude -p --permission-mode acceptEditsinstead ofdontAsk. No allowlist grantsEditorWrite, so every unattended session (headless,/svc:shipvia the Stop hook,cplat runs benchmark) could plan but never change a file. Edits stay inside the repo; every other tool is still limited to the committed allowlist and nothing prompts. AC-070.1 is replaced by AC-070.5. - Fix (099): the benchmark checks trust and its result. One fixed clone per workflow under
<state>/benchmark/<workflow>, refused until the folder is trusted in Claude Code; a run isdoneonly with a new open PR (quick-task, build) or merged PR (ship); the reset leaves PRs, branches and tags that existed before the run alone. - Feat (099): token usage and cost are measured per run. ADR-035 — Token usage is measured from the local session transcripts
- A run record gets a
usagesection (totals and breakdown by phase, role and model), read from the local Claude Code session transcripts. Needs Claude Code 2.1.132 or later (CLAUDE_CODE_SESSION_ID). Costs are estimates from the price table in the repo; output tokens are shown as reported, so output cost is a lower bound. cplat runs usage <run_id>/--session <id>[--json] prints the breakdown.cplat runs gate <acceptance|security> <passed|failed>records the gate results that/svc:buildstates; a ship or build record carriesgates.cplat runs reportadds ausageview per kind: delivered runs with total and median, next to failed, escalated, abandoned and gate-failed runs.cplat runs benchmark <quick-task|build|ship> [--reset-first]runs a fixed task headless against the sample repoika100/todo-api;cplat runs report --benchmarkscompares workflows per platform version.
- A run record gets a
- Feat (094): a merge conflict re-plans instead of escalating.
cplat build mergeaborts a conflicting merge and sets the task aside;cplat build replan-commitaccepts the architect’s amendment only when it changesfiles/depends_on/parallel_safeof open tasks.- A task is re-planned once and a build at most 3 times, then it escalates. Conflicts are never resolved by hand.
- The Final Report says when the plan was amended.
- Feature (088): platform failures become deduplicated issues. ADR-033 — Failures become auto-filed issues, deduplicated by a signature line
cplat ship escalate --cause platformand a crash of anycplatcommand inside a recorded run file the failure inika100/sdlc-foundry. Causescode,decisionandenvironmentare only recorded; a missing or unknown cause becomesunknown.- Each issue carries one
Signature: platform:<16 hex>line and the labelauto-filed; a repeat adds a +1 comment to the oldest open issue, and a repeat after close files a new issue linking the old one (nothing afterwontfix). Repo, spec and branch names are removed. - At most 10 new issues per 24 h per machine; filings that fail or hit the cap stay pending and are retried at
cplat runs flush. CPLAT_AUTO_ISSUES=offsends nothing and prints the draft;CPLAT_FEEDBACK_REPO=<owner/name>files elsewhere.- Action: run
cplat triage setup-labelsonce to createauto-filed.
- Feature (092): firing alerts become issues in the owning repo. ADR-038 — Alerts reach GitHub through a scheduled poll on the gitops-app’s self-hosted runner
.github/workflows/alerts.ymlin a gitops-app runscplat alerts pollevery 15 minutes at the repo’s pinned platform version, only on the self-hosted runner (CI_RUNNER);gh workflow run alerts.yml -f dry_run=trueshows what would happen.- Rules go under the
observabilityaddon in app.yaml (rules,alerts,queryURL,querySecret,alertmanagerURL;devbox run qualityvalidates them);ServiceDownis built in. - Issues follow the 088 convention with an
env:<env>label in the service’srepo(else the gitops repo): a comment when it fires again after a resolve, a comment and close when it resolves while untriaged, a reopen within 7 days, nothing afterwontfix. - At most 10 new issues per poll; the rest become one summary issue.
- Three failed polls in a row file an “alert poll is failing” issue.
- Action: set the secret
ALERTS_TOKEN(fine-grained, Issues: read and write on the service repos);cplat doctorwarns when it is missing. Run/shared:update-servicein gitops-app repos.
- Feat (089): agent quality is measured per agent, shape and model.
- A build record gets
agentsandreplanssections (fix rounds, red CI, review findings, refused test diffs, out-of-plan files, merge conflicts, tester misses), recorded bycplat ship land,cplat spec test-diffand the newcplat runs agentcalls in the build prompts. cplat runs report --agents [--by-version] [--agent NAME] [--shape SHAPE] [--json]ranks agents by rework and cites an example run for the top causes. A metric a run never reached shows asn/a, not 0.- Recording never stops a build: a failed or malformed call only marks the record’s agent metrics incomplete.
- A build record gets
- Feature (096): platform updates roll out to every repo.
cplat fleet update//shared:fleetbrings every stamped platform repo of your account to the installed release, one repo at a time, one PR each.- A PR is squash-merged only when the update is routine: nothing the repo added was overwritten (spec 086’s
lostis empty), the major version is unchanged, there is no conflict and CI is green. Every other PR stays open with the labelplatform-update: needs reviewand a comment giving the reason. --dry-runshows what would happen,--only <owner/name>limits the run, and each run leaves afleetrecord in the run store.
- A PR is squash-merged only when the update is routine: nothing the repo added was overwritten (spec 086’s
- Fix (085): a broken spec names its file, line and fix and no longer blocks other specs (#129)
- Repo gates survive skeleton updates (086). Gates go in the repo-owned
ci-extrarecipe, whichdevbox run ciand aci-extraCI check run; the update keeps your.claude/settings.jsonpermission entries,.claude/project-notes.mdholds project guidance, and the update report lists every customisation it overwrote (lostin--json).- Action for existing repos: run
/shared:update-service. Gates you had incimove toci-extraautomatically.
- Action for existing repos: run
- Feature (098): gitops CI checks Components against the service branch’s CRDs. (#110)
cplat ship gitops pradds oneService-ref: <owner>/<repo>@<branch>line to every gitops PR it opens or reuses.- On pull requests the gitops-app CI reads the CRDs of that branch and validates the Components under
applications/against them, instead of the public catalog. Without a marker nothing changes. cplat doctorwarns when a gitops-app lacks theSERVICE_REPOS_READ_TOKENsecret.- Action: add the secret
SERVICE_REPOS_READ_TOKEN(fine-grained, Contents: read on the service repos) to every gitops-app, then run/shared:update-servicethere.
- Feat (087): every run is recorded in a private run store. ADR-032 — Every run is recorded in a private run store
- Each ship, build,
pr waitandcplat headlessrun leaves one JSON record: phases, CI rounds, reruns, escalation cause, outcome. Secrets are redacted. cplat runs init <owner/name>creates the private repo; thenexport CPLAT_RUNS_REPO=<owner/name>in your shell profile.cplat runs flushwrites the buffer,cplat runs report [--json]shows durations, outcomes and causes.cplat doctorhas arun storecheck: a public store fails, a missing, unreachable or backlogged one warns.- Recording never fails a run: without a store, records wait in a local buffer (1000) and one warning line says so.
- Each ship, build,
- Feature (095): fixable dependency CVEs are patched by the run. ADR-034 — Fixable dependency findings are patched by the run
- Every shape has the recipes
deps-findingsanddeps-bump(/shared:update-serviceadds them).cplat deps findings|patch|reportbumps each fixable package when the security gate is red, re-runs the gates, and commits or reverts exactly. - At most 2 patch rounds; a finding with no fix, or a bump that breaks a gate, escalates. The PR body gets a Dependency patches table (a “major bump” is marked) and the run record a
patchessection.
- Every shape has the recipes
- Fix (079): a registry rate limit is transient.
cplat pr waitandcplat ship landrerun a job whose log saysinvalid response status 429once before it counts.
[4.4.0] — 2026-10-22
Section titled “[4.4.0] — 2026-10-22”Deploys report what really runs, every PR wait is one tested command, and builds and CI do each thing once.
cplat ship deployfails at once on a red main CI, a failed pin-dev run or a missingGITOPS_TOKEN. It counts Argo only once dev runs the merged image.- Image tags are the full commit sha (ADR-030);
sha-<7>still works for one release. cplat pr waitwaits for any PR and reruns stuck or transiently failed jobs once.- Leaner flow:
/svc:buildruns the CI gate once, andcplat headlessstops after two idle sessions. - Real gates: gate tools are pinned,
cplat doctorchecks the gates, and sessions claim the issues they work on. - Visible drift:
/shared:statusshows each repo’s platform version. - Faster platform CI: platform tests run in about 30 s.
Action for existing repos:
- Run
/plugin marketplace update(svc 3.3.0, shared 0.13.0, gitops 1.4.2, svc-go/svc-java/web 0.2.6). - Run
/shared:update-servicein every repo. Service repos then dispatchsha-<40>. Update gitops-apps first, so their pin-dev accepts the full sha and names its run. - Run
cplat doctoronce per repo: the new gates section names anything to fix.
- Fix (077):
cplat ship deployreports what really runs. (#113, #114, #115)- It watches the service’s CI on main and the gitops-app’s pin-dev run, found by its new run name
pin-dev <service> <tag>. - A red main CI or a failed pin-dev run fails it at once, with the log tail. A stuck pin-dev run is rerun once.
- No pin-dev run 5 min after main’s CI passed is an error that names
GITOPS_TOKEN. - Pin PRs carry no checks, so spec 072’s AC-072.5 could never fire; it is withdrawn.
- Argo counts only once
<service>-devruns the merged image (status.summary.images). The apps named by gitops tasks count once they reached the merged gitops commit. cplat ship startandcplat doctorrefuse a linked repo withoutGITOPS_TOKEN, and the template’s pin-dev job now warns instead of a notice.
- It watches the service’s CI on main and the gitops-app’s pin-dev run, found by its new run name
- Change (078): image tags are the full commit sha (ADR-030 — Image tags are the full commit sha). (#116)
- The template CI pushes
sha-<40>, plussha-<7>for one more release, and asks pin-dev forsha-<40>. pin-dev.ymlandpin.pyaccept both formats; the pin branch keeps the short sha./gitops:promotepinssha-<40>, orsha-<7>for images built before.
- The template CI pushes
- Feat (079):
cplat pr wait <pr> [-R owner/repo] [--merge]waits for any PR with the ship watch, merges on green, and replaces hand-written polling loops. A job that failed on a transient error (artifact download, resolve failure, HTTP 502/503/504, connection reset, TLS timeout) is rerun once before it counts;ship landdoes the same. (#117) - Feat (080): leaner builds and unattended runs. (#118, #119)
/svc:buildPhase 3 runsdevbox run lint-fixanddevbox run cionce and spawns only the agent that owns a red gate: the tester only for internals without unit tests, security only for a changed Dockerfile.cplat headlessstops after two sessions in a row that change nothing, and escalates the run.
- Feat (081): gates that can fail. (#120, #123, #126)
- The templates pin every gate tool: kustomize, kubeconform, ruff, mypy, pip-audit, golangci-lint, govulncheck, maven.
bump_template_pins.pybumps them (minor/patch) in its weekly PR. cplat doctorgets a gates section: the secrets baseline has its filters, scanners are pinned,ciandsecrets-baselineexist,cichains the CI workflow’s gates, and CI jobs run the template gates.cplat adoptlists the template gates the repo’s CI lacks.
- The templates pin every gate tool: kustomize, kubeconform, ruff, mypy, pip-audit, golangci-lint, govulncheck, maven.
- Feat (082): sessions do not collide. (#121, #122)
cplat spec new --tracksandcplat ship startclaim each tracked issue: anin-progresslabel, the user as assignee, and a comment naming the branch. An issue claimed for another branch stops them (--take-overmoves the claim); an escalation releases it.- The platform CI requires a spec (or the
no-speclabel) for a PR that closes an issue and changesscripts/,plugins/ortemplates/.
- Feat (083):
/shared:statusshows each service repo’s platform version, marked(behind)when it is older than the installed platform. (#125) - CI (084): the platform’s own CI takes minutes. (#124, #127)
pytest tests/cplatruns in parallel (-n auto): 163 s → 29 s locally.- The smoke-test matrices no longer cancel healthy siblings (
fail-fast: false). - The Java smoke jobs retry downloads.
[4.3.0] — 2026-10-22
Section titled “[4.3.0] — 2026-10-22”Ship runs lose minutes, not an hour, to CI, and they carry their gitops changes.
cplat ship landstops at the first red check and reruns a stale or stuck run once.devbox run ciruns CI’s gates locally, andship prrefuses to push while it is red.- CI runs once per change (push only on
mainand tags). - The secrets scan answers the same everywhere: pinned scanners, the standard filters, no gibberish filter.
- Plans carry gitops tasks, which ship opens and merges around the deploy.
- The templates ask for fewer permissions on the routine steps.
Action for existing repos:
- Run
/plugin marketplace update(svc 3.2.0, shared 0.12.1). - Run
/shared:update-servicein every repo. - Run
devbox run secrets-baseline, audit what it adds, and commit the baseline. It is project-owned, so the update does not replace it. - Repos with extra CI jobs add them to their
ciscript.
- Templates: fewer permission prompts. The generated
.claude/settings.jsonallows the routine, reversible steps the SDLC flows take:cplat(it guards its own outward steps: it pushes feature branches only and merges only on green CI), signed-off commits (git commit -s -m, which the DCO requires and the oldgit commit -m *rule never matched),git pull --ff-only, removing agent worktree branches,gh pr create/checks/diff/comment,gh run rerun/cancel/watch,gh workflow run,gh issue comment, CI log and check-run reads,kubectl --context <ctx> get/describe/logs, and localdocker build/run --rm/rm/rmi. Still asked: force push, push to main, reset/clean/rebase/amend, deleting other branches,kubectl delete/rollout, manual merges, releases,docker push, closing issues. Action:/shared:update-service. devbox run ci: the CI gate locally (#105), svc 3.2.0. Every template has aciscript with the gates its CI workflow runs (quality+test+security; gitops-apps:validate+security), andshapes.pyrequires it./svc:buildPhase 3 ends with onedevbox run cion the staged tree, and/svc:shipruns it beforeship prand after every CI fix. A red check is then found before the push, not after a CI round on the runners. Action:/shared:update-service; repos with extra CI jobs (e.g.chart-lint) add them to theirciscript.- Templates: CI runs once per change (#104).
ci.ymlin every template triggerspushonly onmain(andv*.*.*tags). Feature, fix and other branches run CI throughpull_requestalone; before, every commit on an open PR ran each job twice, which queued PR jobs behind the duplicates on small self-hosted runner sets. In gitops-apps, pin-dev keeps running CI throughworkflow_call. Action:/shared:update-service. - CI: template pin bumps no longer get stranded.
bump-template-pinsopens its pull request with a GitHub App token (repository variableBUMP_APP_CLIENT_ID, secretBUMP_APP_PRIVATE_KEY;BUMP_TOKENstill works), so CI runs on it, and the job now fails when it cannot open the pull request instead of passing with a warning. Two weekly bumps had been pushed without a pull request. - Docs: the kro spike is kept.
docs/spikes/kro.mdis now on main and linked from ADR-020; rendered manifests stay the only addon backend. - Feat (072):
cplat ship landreports a red check at once and heals stuck checks.- It stops waiting at the first failed check and prints its log.
- It prints one line per check as each one concludes.
- A stale check (still in progress after its run completed) is rerun once. So is a run with only queued jobs for
--queue-timeout(300 s) while nothing else of the PR runs. A second time is an error that names the runner set. ship deployapplies the same rules to the open pin PR. (#102)
- Fix (073): the secrets scan gives the same answer locally and in CI.
- The templates pin
detect-secrets@1.5.0andtrivy@0.74.0. - The template baselines list the standard filters. Before,
filters_used: []turned them all off, so# pragma: allowlist secrethad no effect. They no longer carry the gibberish filter, which only some machines can apply. - New recipe
devbox run secrets-baselinerefreshes the baseline with fixed flags, keeping audits. It regenerates a baseline without the standard filters (or a missing one) from scratch first. secrets-scanpasses the same--disable-filter, so a hook that rewrites the baseline (line numbers moved) does not add the gibberish filter back.- The Python templates call the devbox binary instead of
uv run. - Action: run
/shared:update-service, thendevbox run secrets-baseline. The baseline is project-owned. Audit what it adds: hits the gibberish filter used to hide on some machines show up once. Then commit it. (#103)
- The templates pin
- Docs (074): a persistent Docker volume is the build cache on self-hosted runners.
docs/self-hosted-runners.mdrecommends one dind/var/lib/dockervolume per runner set and explains why there is no registry cache (it uploads every layer on each main build). The trigger change is #107 above; spec 074 records both. (#104) - Feat (075):
cplat ship prenforcesdevbox run ci. It runs the recipe before pushing and refuses while it is red, printing the tail; spec 075 records this together with #108. (#105) - Feat (076): ship carries a spec’s gitops changes.
- Plans can have gitops tasks (
repo:,order: before-merge | after-release,files,apps) andhuman_steps;cplat spec checkvalidates them. cplat ship gitops prepare|pr <task>opens each task as a draft PR during the build.ship landmerges thebefore-mergePRs before the service PR.ship deploymerges theafter-releasePRs once dev runs the pin, then waits for every app they name. A SyncError stops the deploy.- The run ends with the human steps as a checklist.
- The architect plans CRD field removals as expand/contract.
- Validating Components against the service branch’s CRD in gitops CI is the follow-up #110. (#106)
- Plans can have gitops tasks (
[4.2.1] — 2026-10-22
Section titled “[4.2.1] — 2026-10-22”External Helm charts keep their release name. Taking over a chart that already ran no longer fails with spec.selector: field is immutable. Action: /shared:update-service in gitops-apps with a helm external entry. No plugin changes.
- Fix (069): external Helm charts keep their release name. The chart Application sets
helm.releaseNameto the service name (external.helm.releaseNameoverrides it). Before, Argo named the release after the Application (<svc>-<env>-chart), which changes the chart’s selector labels, so taking over a chart that already ran failed withspec.selector: field is immutable. Action:/shared:update-servicein gitops-apps with ahelmexternal entry, thenuv run scripts/render.py.
[4.2.0] — 2026-10-22
Section titled “[4.2.0] — 2026-10-22”From an approved spec to dev, unattended, on GitHub Free. Approving a spec is the only step left for you:
/svc:ship <NNN>plans, builds test-first, opens the PR, merges it once CI is green, and waits untildevruns it.cplat headlesskeeps starting fresh sessions until the run is done.- CI gates every merge in code, so branch protection and auto-merge are not needed.
- Also new:
- an
externalentry type for services the generator cannot express (CronJobs, volumes, Helm charts); cplat adoptfor repos that were not created from a template;- self-hosted CI runners set through repository variables.
- an
Action for existing products:
- Run
/plugin marketplace update. - Run
/shared:update-servicein the gitops-app (pin-dev merge job, external entries) and in every service (CI runner variables). - Make sure Actions may open PRs in the gitops-app:
gh api -X PUT repos/<org>/<gitops>/actions/permissions/workflow -F can_approve_pull_request_reviews=true -f default_workflow_permissions=write. - Open each repo interactively once before its first
cplat headlessrun.
Plugins: svc 3.1.0, shared 0.12.0.
Unattended delivery on GitHub Free (ADR-028: Unattended delivery without branch protection, guide, from a review of three lan repos). Approve a spec, then /svc:ship <NNN> plans, builds, opens the PR, merges it after green CI and waits until dev runs it, without branch protection or auto-merge. Plugins: svc 3.1.0, shared 0.12.0.
-
065 — merges wait for green CI without branch protection. The gitops-app’s
pin-devrunsci.yml(now also a reusable workflow with arefinput) on the pin branch and merges the pin PR only when it passed; a red CI comments the run and leaves the PR open. Where protection refuses the direct merge it falls back to auto-merge./shared:new-servicesays what still guards merges on the free plan. Action:/shared:update-servicein each gitops-app. -
066 — unattended runs stop only when done. A
Stophook in the shared plugin keeps an unfinished/svc:shiprun going (next step, untraced criteria, red tests) until it is done, escalated (cplat ship escalate) or blocked 8 times. -
067 —
/svc:ship <spec> [--no-merge]. Plan → build →cplat ship pr→cplat ship land(merge only when every reported check passed; red → log tails → fix loop, at most 3 rounds) →cplat ship deploy(the merged pin PR of the merge commit, optionally ArgoSynced/Healthy). Resumable from the state in the git directory. -
068 — done means verified.
cplat spec set-status <id> doneneedsverification.mdwith**Result:** pass(product specs excepted);--unverified --reasonrecords an exception;/svc:specsshows/svc:verifyfor done specs without evidence. -
069 — external services (ADR-029: External services in services.yaml). A services.yaml entry with
external: {kustomize: <path>}orexternal: {helm: {repoURL, path, revision, imageTag}}is wired into the environments and pinned like any service, so pin-dev,/gitops:promoteand/shared:statuscover services with CronJobs, volumes or charts. Action:/shared:update-servicein each gitops-app (render.py, pin.py). -
070 —
cplat headless "<prompt>". Freshclaude -p --permission-mode dontAsksessions until a ship run is done or escalated,--quiet HH:MM-HH:MM, a log,summary.logand a desktop notification. -
071 —
cplat adopt --type <shape>. Brings a repo that was not created from a template onto the platform in one commit without overwriting its files. -
Fix:
/shared:update-serviceworks in a git worktree (it refused a.gitfile). -
064 — self-hosted CI runners without editing workflows (guide). Every template job runs on
${{ vars.CI_RUNNER || 'ubuntu-latest' }}, the devbox install step runs only on GitHub-hosted runners (self-hosted images ship devbox), and servicedockerjobs take their architecture matrix fromvars.CI_DOCKER_MATRIXwhen it is set. Nothing changes for repositories without the variables. To move a repository to self-hosted runners:/shared:update-service, thengh variable set CI_RUNNER --body <runner label>(andCI_DOCKER_MATRIXfor service shapes). On self-hosted runners thedockerjob no longer reads or writes the GitHub Actions cache (AC-064.5): exporting every layer over a home uplink cost 4–6 min per build.
[4.1.0] — 2026-10-09
Section titled “[4.1.0] — 2026-10-09”Second end-to-end run, fixed. A second feature on the existing todo product with v4.0.0 (run log) found four high issues; this release fixes them (specs 060–063):
- Updates keep your project’s content. The README and your own
devbox.jsonrecipes are no longer dropped. - Plans finished before v4 pass the checks again.
devfollowsmain. Every merge opens a pin PR tosha-<7>in the gitops-app, and it merges itself after CI (ADR-027: dev follows main through pin PRs).- No more false alarms from
cplat spec test-diffon plan files.
Action for existing products: after /plugin marketplace update, run /shared:update-service in the gitops-app and in every service, then set the GITOPS_TOKEN secret in each service repo, using the command the update prints. Until then dev stays on its last pin, and the service’s CI prints a notice saying why.
Plugins: svc 3.0.1, app 1.0.1, gitops 1.4.1, shared 0.11.1, web / svc-java / svc-go 0.2.5.
-
Fixes from end-to-end run 2 (run log, specs 060–063):
- 060 — updates keep project content.
README.mdis project-owned in every template (_skip_if_exists, checked byshapes.py); new repos still get it./shared:update-servicemergesdevbox.jsoninstead of overwriting it: the template’s recipes, packages and env vars win, the project’s own are kept without reformatting, and the report lists kept entries and every replaced recipe with its old line. - 061 — completed plans pass. The gitops-list and contract rules of specs 045 and 053 apply to
draftandin_progressplans;completedandabandonedones get one warning line. Errors name the exact heading line to add. - 062 — dev follows main (ADR-027: dev follows main through pin PRs). Service CI gets a
pin-devjob that asks the gitops-app (repository_dispatch, secretGITOPS_TOKEN) to pindevtosha-<7>. The gitops-app’s newpin-devworkflow andscripts/pin.pyopen a pin PR that merges itself after CI. New gitops repos get auto-merge, new services print the token step,/shared:update-servicenames it for existing ones, and/shared:statusshows open pin PRs. Action for existing products: update the gitops-app and every service, then setGITOPS_TOKENin each service repo; until thendevstays on its last pin and CI prints a notice. - 063 —
cplat spec test-diffonly looks at test files (the shape’stest_globs, neverdocs/or*.md), soplan.mdchanges fromtask-doneno longer fail it;/svc:buildforbids treating a failure as a false alarm.
- 060 — updates keep project content.
-
Field report: end-to-end run 2 (run log): a second feature (“update a notice” → edit a todo’s title) on the existing todo product with the released v4.0.0 — spec ambiguity caught as a question, both repos built test-first and verified, deployed and checked live; ~68 min active, $12.10. 17 findings, 4 high: v4’s
plan.pyrejects completed pre-v4 plans, skeleton updates drop projectdevbox.jsonrecipes (like the README, spec 060),devnever deploys newlatestimages, andcplat spec test-diffflagsplan.md.
[4.0.0] — 2026-10-08
Section titled “[4.0.0] — 2026-10-08”Spec-driven development. A feature is now built from an approved spec (ADR-026: Feature specs drive the build): /svc:spec writes docs/specs/<NNN>-<slug>/spec.md with numbered acceptance criteria and asks you its open questions; /svc:plan plans the approved spec; /svc:build writes failing tests from the criteria first, builds until they pass, has a reviewer verify every criterion and opens the PR. Products work the same way across repos with /app:spec, /app:plan and /app:build. Every check is the tested cplat spec; every generated repo checks its specs in CI.
Breaking: /svc:plan-feature, /svc:build-feature, /app:build-feature, /app:run-plan and /app:plans are replaced — the migration table is in the svc 3.0.0 and app 1.0.0 entry below. After /plugin marketplace update, run /shared:update-service in each repo, and /svc:specs migrate where docs/backlog.md still holds STORY-NNN stories.
Plugins: svc 3.0.0, app 1.0.0, shared 0.11.0, gitops 1.4.0, web / svc-java / svc-go 0.2.4.
Verified end to end: a todo product (gitops-app, Python API with Postgres, Next.js web; public repos) went from an empty folder to a verified deployment in 60.5 min; the 17 issues that run found are fixed in this release (run log). Also in this release: /shared:new-app, /shared:triage, the cplat launcher, branch protection for new repos, and secret scans that actually scan in every template.
-
Docs for the release;
/gitops:compose set(gitops 1.4.0): the slash command now offersset <service> [--env K=V] [--expose [HOST]] [--uses ADDON] [--replicas N](until now only/app:buildcould callcplat compose set). README, HOW-IT-WORKS, ADOPTING, USER-JOURNEY and AGENTS.md describe branch protection of new repos, thespecsCI check,cplat spec test-diffand resume,worktree.baseRef: "head", the gitops operations of product plans and the free local port; the AGENTS.md plan example shows the current format. The website’s Ship a product scenario starts with/shared:new-appand the spec-first product flow, and the end-to-end run log is published under Reference. Leftover “prefix” wording from thecplatlauncher change removed from three commands. -
Fixes from the todo end-to-end run, waves 4–5 (specs 051, 052): when
gh pr createis not allowed (headless, untrusted workspace, declined),/svc:build,/svc:quick-task,/svc:fix-bugand/app:buildpush the branch, write.git/PR_BODY.mdand end with the exactgh pr createcommand, one per repo for/app:build(051). A repo spec sliced from a product plan lists under Provided by the product the env variables, addons (with their env names from the addon contract) and exposure the gitops entry wires for it, and/svc:buildasks only for wiring that is missing (052). All 17 issues of the run are fixed (specs 043–059done). -
Fixes from the todo end-to-end run, wave 3 (specs 048, 050): new
cplat spec test-diff <red-commit> [files]tells formatting from real changes in acceptance tests (Python by AST with sorted imports, other languages by whitespace-free text, criterion ids must stay);/svc:buildruns it after everylint-fixand stops only on a real change, so lint no longer blocks a build and tests are never quietly weakened (048)./app:planpushes the spec branch and opens its PR, and/app:buildwarns when the plan onorigin/maindiffers from the local one (050). -
Fixes from the todo end-to-end run, wave 2 (specs 046, 047, 053, 054):
/app:buildruns its repo agents as foreground calls, so headless runs no longer lose them after 600 s, anddocs/ADOPTING.mdhas an Unattended runs section (046)./svc:buildresumes an interrupted build: stale staged files are unstaged, the leftover work is committed aswip(<task>): interruptedand redone, and the PR asks for a squash merge (047).plan-checkrequires### Errorsand### Timeoutsin the contract of every multi-repo plan, so repo specs stop asking the same questions (053)./shared:new-serviceand/shared:new-appprotectmainwith the shape’s CI checks (newci_checksper shape inshapes.yml, validated against the workflow job names; no review requirement; a warning with the manual command where the plan does not allow protection) (054). -
Fixes from the todo end-to-end run, wave 1 (specs 043–045, 049, 055–059; run log):
- Templates: service-python’s
tracing.py/test_tracing.pypass the template’s own ruff rules (generated repos were red onmain); platform CI now lints, type-checks and tests the rendered Python templates (043). Python templates excludedocs/from ruff, so spec code examples never fail CI (049). Every template setsworktree.baseRef: "head"so parallel coders start from the feature branch;shapes.py checkrequires it (044). Template workflows use the platform’s action pins, enforced bypin-actions.py --check, and one Dependabot group updates all of them (055).cluster-upmoves to the next free port when 8088 is taken (056).spec-checksays exactly what happened (057). - Security: the secrets baselines of gitops-app, service-go, service-java and web-nextjs enabled no detect-secrets plugins (the scan checked nothing), library-python only 4; all templates now use the full default set (059). The security agent groups unfixable base-image CVEs into one line (059).
- Multi-repo: new
cplat compose set <service> [--env K=V] [--expose [HOST]] [--uses ADDON] [--replicas N]changes a composed service; the gitops-app entry of a product plan carries structuredgitops:operations validated byplan-check, and/app:buildruns them itself and marks the PR merge first (045, ADR-011 amended). - Specs: criteria stay environment-neutral (056); the
spec-formatskill is hidden from the/menu (058);cplat spec traceno longer counts__pycache__files as tests (found while verifying).
- Templates: service-python’s
-
End-to-end run of the spec-driven platform (run log): a todo product (gitops-app, Python API, Next.js web; public repos) went from an empty folder to a verified deployment on a local cluster in 60.5 min with PRs #77–#81: product spec with answered questions, per-repo spec slices, failing acceptance tests first, 18/18 and 25/25 criteria verified (the reviewer caught one real gap), resume after interruptions, Postgres persistence checked live. 17 issues logged with workarounds; the blocking ones: the service-python template fails its own lint (platform CI does not lint Python templates), parallel worktrees start from
maininstead of the feature branch, and/app:buildhas no route for the gitops-app repo entry the planner adds. -
cplaton the PATH; the platform runs its own spec workflow (ADR-026: Feature specs drive the build, spec 042; shared 0.11.0, gitops 1.3.4): the shared plugin shipsbin/cplat, so the ~300-character fetch-and-clone line is gone from all 24 commands.cplatruns the platform script from the installed marketplace checkout, i.e. the version your plugins came from, without network;CPLAT_PLATFORM(local checkout) andCPLAT_REF(a tag, used by--ref) override it, and only a missing checkout falls back to fetchingmain./shared:doctorreports which copy ran. claude.ai and Cowork do not install plugins withbin/; the platform targets the CLI and desktop app. This repo’s backlog was migrated withcplat spec migrateintodocs/specs/001…042(numbers kept, links rewritten,STORY-036superseded); platform changes now start with/svc:spec.migratemaps free-text legacy statuses (done (superseded …),partial,planned). -
Spec contract in every template (ADR-026: Feature specs drive the build, STORY-041): every template seeds
docs/specs/README.md(formats, lifecycle, rules) and listsdocs/specs/**as project-owned. A new warn-onlyspecsworkflow anddevbox run spec-checkrunscripts/spec-check.sh, which fetches the platform at the repo’s own version (ref:in.platform-version, a release tag is cached) and runs the newcplat spec ci: every spec and plan valid, every criterion of a building or done spec named by a test, as GitHub annotations;SPEC_CHECK_STRICT=1turns warnings into a failure (planned default in the next major).shapes.py checkrequires the files, the recipe and the/svc:spec(gitops-app:/app:spec) rule. Existing repos get the workflow, script and recipe with/shared:update-service;docs/specs/appears with their first spec. -
BREAKING — svc 3.0.0 and app 1.0.0: spec → plan → build → verify (ADR-026: Feature specs drive the build, STORY-038, STORY-039, STORY-040; app 1.0.0, shared 0.10.0, web/svc-java/svc-go 0.2.4). A feature is a folder
docs/specs/<NNN>-<slug>/and is built only from an approved spec:/svc:spec <desc>— the product-manager writesspec.md(acceptance criteriaAC-<NNN>.<n>, non-goals, open questions); the command asks you each open question, folds the answers in and asks for approval./svc:spec approve <id>,/svc:spec --amend <id> <change>,/svc:spec --from-plan <plan> <repo-id>(multi-repo)./svc:plan <id>— the architect writesdesign.md(contract) andplan.md(tasks thatcovercriteria); refused for unapproved specs;cplat spec checkvalidates coverage, cycles, parallel file overlap and spec drift./svc:build <id>— the tester writes failing, criterion-tagged acceptance tests first; coders (parallel worktrees) work until they pass and never weaken them; quality ‖ tester ‖ security; the new read-only reviewer checks every criterion and writesverification.md; image; PR listing every criterion. Tasks are marked done as they merge, so a re-run resumes./svc:verify [<id>](reviewer only, no code changes) and/svc:specs [--all|index|migrate](status with the next command, backlog table, legacy migration).- New
svc:spec-formatskill holds every format; product-manager (opus, shape-agnostic) returns open questions instead of guessing; architect, testers (acceptance mode) and coders updated in all four code plugins./svc:quick-taskand/svc:fix-bugstop when a change would contradict a criterion. - app 1.0.0:
/app:specwrites the product spec in the gitops-app repo;/app:planhas the planner assign every product criterion to a repo (acs:per repo,spec:in the plan, ADR-011 amended;plan-checkrefuses an unassigned criterion) with the contract between them;/app:buildbuilds every ready repo in parallel, each through its own spec slice;/app:specstracks progress.cplat spec new --from-plan <plan> <repo>writes that slice deterministically (product problem, assigned criteria verbatim, contract,parent:)./shared:triagefolds features into specs (Tracked as spec <id>). Bootstrap next steps, templates’CLAUDE.mdand the seededdocs/backlog.md(now a generated spec index) follow. docs/AGENTS.mdfixed (product-manager model, gitops commands that have no agent) and tested against the agent files.
Migration
Before (svc 2.x) Now (svc 3.0) /svc:plan-feature <desc>/svc:spec <desc>→ answer, approve →/svc:plan <id>/svc:build-feature --plan docs/plan/<slug>.md/svc:build <id>/svc:build-feature <desc>(one step)/svc:spec→/svc:plan→/svc:build(no one-step path: a build needs an approved spec)/svc:build-feature --no-pm <precise request>/svc:spec <precise request>(few or no questions), approve, plan, build/svc:build-feature --from-plan <plan> <repo>/svc:spec --from-plan <plan> <repo>→/svc:plan→/svc:build/app:build-feature <desc>/app:spec <desc>→ answer, approve →/app:plan <id>/app:run-plan <slug>/app:build <id>/app:plans list|show|start|done|abandon/app:specs [--all|show|done|abandon](starthappens in/app:build)multi-repo plan with free-text argumentsper repospec:+acs:per repo (old plans stay valid)STORY-NNNindocs/backlog.md, plans indocs/plan/docs/specs/<NNN>-<slug>/;/svc:specs migrateconverts existing stories (numbers kept)Update the plugins (
/plugin marketplace update), then in each repo run/shared:update-servicefor the newCLAUDE.mdrule. Repos with stories indocs/backlog.mdrun/svc:specs migrate; the project-owned backlog is never overwritten by the update. -
cplat spec(ADR-026: Feature specs drive the build, STORY-037): the deterministic core for spec-driven development. Feature specs live indocs/specs/<NNN>-<slug>/(spec.md,plan.md, …) with acceptance criteriaAC-<NNN>.<n>.new,check [--require STATUS](criteria ids, shape, plan coverage, cycles, parallel file overlap, drift viaspec_hash),approve(refused with open questions),set-status,task-done,hash,trace(every criterion named by a test, via the newtest_globsper shape inshapes.yml),index(table indocs/backlog.md),list,migrate(legacySTORY-NNNstories → spec folders, dry run by default). No command uses it yet; the renamed/svc:spec|plan|build|verifycommands follow in svc 3.0 (STORY-038 to STORY-042). -
/shared:triage(ADR-025: Issue triage, STORY-035; shared 0.9.0, svc 2.2.1,cplat triage): reads new GitHub issues, proposes a class for each (bug,small-change,feature,question,duplicate,wontfix,needs-info), asks you at most three questions per issue and, with your OK, the reporter, then routes: features becomeSTORY-NNNwithTracks: #N(and aTracked as STORY-NNNcomment), bugs go to/svc:fix-bug, small changes to/svc:quick-task. Labels and comments only after confirmation; issues are never closed; issue text is redacted, fenced and treated as untrusted. Every template ships issue forms (triagelabel, required fields);shapes.py checkrequires them. Existing repos pick the forms up only by copying them; the command works without. The product-manager agent’s triage section now points to the command. -
Spec-driven bootstrap (ADR-024: Spec-driven bootstrap, STORY-034; svc 2.2.0, shared 0.8.1): creating a service, library, web app or product now leads into planning. The Next steps of
/shared:new-serviceand/shared:new-appstart with/svc:plan-feature "<description>"(gitops-app and products:/app:build-feature). New/svc:build-feature --plan <path>builds a reviewed plan without planning again (validates the YAML block, the shape and the story ids). Stories areSTORY-NNN, plans liststories:, the PR names them. Every template seedsdocs/backlog.mdanddocs/plan/(project-owned) and a### Spec firstsection inCLAUDE.md;shapes.py checkrequires both. Existing repos get theCLAUDE.mdrule with/shared:update-service. -
/shared:new-app <app.yml>(shared plugin 0.8.0,cplat new-app, STORY-033): creates the gitops-app repo and every component repo of a product from a manifest (app, optionalorg/visibility,componentswithname,description,shape, optionaldata), then opens onecompose addpull request for the deployable ones. Each repo goes through the same code as/shared:new-service. Creation order is computed (gitops-app, libraries, services, web), validation andgh repo viewchecks run before anything is created (also in--dry-run), a failure midway lists the repos that exist and--resumecontinues; nothing is rolled back. Replaces the 6new-service+ 4composecommands of the end-to-end scenario.
[3.2.0] — 2026-10-07
Section titled “[3.2.0] — 2026-10-07”Addresses issue #56 (“Improve performance”) and the leftovers of the project-management sample.
-
Work starts right away.
new-servicenext steps,/shared:new-service,/svc:build-featureand the user journey now say that development starts on a feature branch immediately; nothing waits for the bootstrap CI onmain(#56, idea 3). -
Parallel multi-repo execution. New
/app:run-plan <slug>(app plugin 0.2.0, ADR-023: Parallel execution of multi-repo plans): builds every repo whose dependencies are done in parallel, one agent per repo, ends at open PRs, merges only on your word, never pins images.plan.py readycomputes the wave; the planner writes a## Contractsection so a backend and its UI can start together (#56, idea 1). -
Template pins stay current. Weekly
bump-template-pinsworkflow andscripts/bump_template_pins.py: rendersservice-java,web-nextjsandservice-go, bumps the rendered manifest (minor/patch), copies literal unique line changes back into the Jinja source and re-renders to verify. Needs aBUMP_TOKENsecret for CI to start on the bot’s PR (#56, idea 2). -
k9s in the gitops project:
k9sin the devbox anddevbox run cluster-ui(clear error with the fix when the cluster is not running). -
Java services wait for their database at startup (Hikari
initialization-fail-timeout) instead of crash-looping while PostgreSQL initialises. -
k9s in the gitops project:
k9sis part of the gitops-app devbox anddevbox run cluster-uiopens it on the local cluster in the<app>-devnamespace (clear error with the fix when the cluster is not running).cluster-upmentions it when it finishes. Existing projects get it with/shared:update-service.
[3.1.0] — 2026-10-07
Section titled “[3.1.0] — 2026-10-07”Found and fixed while building the project-management sample (pm-gitops, pm-backend, pm-web-ui) end to end. New options and flags only; nothing breaking.
-
CI re-checks a pull request when its title is edited (all templates and the platform itself): the
pr-titlecheck ran only on open/push/reopen, so correcting a rejected title never turned it green; a title edit now re-runs CI in its own concurrency group, so it cannot cancel a running build. -
Worked example “project management app” on the docs site: Spring Boot + Next.js + PostgreSQL built and run end to end with the platform’s commands (also a findings list).
Database support in the Java template (found while building the project-management sample)
Section titled “Database support in the Java template (found while building the project-management sample)”- service-java
needs_database(default false;/shared:new-service ... --type service-java --data needs_database=true): Spring Data JPA, Bean Validation, Flyway and the PostgreSQL driver;application.ymlreads the platform’sPG*variables (the postgres addon injects them; defaults matchdevbox run db-up) and the password never travels inside a URL; Hibernate only validates (ddl-auto: validate), the schema belongs to Flyway (src/main/resources/db/migration); readiness includes the database; Testcontainers PostgreSQL for the generated tests (Docker needed);db-uprecipe, env-var docs and CLAUDE.md section. The CI smoke job has a database variant that starts a real PostgreSQL next to the image. Before, a Java service could not use the postgres addon without hand-written wiring.
UX fixes found while building the project-management sample
Section titled “UX fixes found while building the project-management sample”/shared:new-service --public(or--visibility public): create the GitHub repository public instead of the hard-coded private one; the preview says PUBLIC or PRIVATE before anything is created./shared:new-service --data KEY=VALUE: set template options at creation (for exampleneeds_observability=false); the keys are validated against the template’s owncopier.ymland an unknown one lists the valid options. Before, options could only be changed afterwards withupdate-service --data./gitops:compose add <svc> --secret-ref NAME: expose an existing Secret (for example the one generated for another service) to a service.secretRefswas previously impossible to set from the CLI.devbox run cluster-upchecks the host ports first: a busyLOCAL_HTTP_PORT(orREGISTRY_PORT) stops the script before anything is created, names the program that holds the port and suggests a free one;LOCAL_HTTP_PORT=autopicks the first free port from 8088.- Plugins: shared 0.7.3, gitops 1.3.3.
[3.0.3] — 2026-10-07
Section titled “[3.0.3] — 2026-10-07”- The image publish failed on arm64 in every generated service and web repo that has the scan step (since 2.1.0). The per-architecture jobs scan the digest they just pushed with Trivy, which resolves a digest to the host platform (linux/amd64) by default. On the arm64 job it found no matching image (“no child with platform linux/amd64 in index”), failed, and the multi-arch manifest and the
latest/sha-*/ version tags were never published. Both Trivy steps now setTRIVY_PLATFORM: linux/${{ matrix.arch }}. Pull-request runs never reach this step (it only runs when publishing), so it was invisible to the smoke tests; it surfaced when the platform’s todo test application was migrated. A static test now asserts the setting in all four templates. Existing repos: run/shared:update-serviceand merge, then check that the nextmainbuild publishes the manifest.
[3.0.2] — 2026-10-07
Section titled “[3.0.2] — 2026-10-07”- web-nextjs:
package.jsonis now project-owned (_skip_if_exists), likepom.xml,go.modandpyproject.tomlin the other templates. Until now/shared:update-serviceoverwrote it: it silently reverted dependency versions (for example a Dependabot bump of TypeScript, which then failed CI with a frozen-lockfile mismatch) and removed dependencies the project had added. If you updated a web repo with an earlier release, checkgit diff HEAD~1 -- package.jsonon the update branch before merging and restore your dependencies (git checkout HEAD~1 -- package.json pnpm-lock.yaml). A test now asserts that every template protects its dependency manifest. Found while migrating the platform’s own todo web app.
[3.0.1] — 2026-10-07
Section titled “[3.0.1] — 2026-10-07”/shared:update-serviceno longer adds starter files to project-owned paths. The templates markapp/**,tests/**,e2e/**,docs/adr/**,public/**and a few docs as_skip_if_exists(never overwritten), but copier still creates files that are missing there. Since 2.2.0 an update of an existing web app therefore added the new starter UI (app/globals.css,app/_components/*,app/not-found.tsx,app/error.tsx,app/icon.svg) and atests/layout.test.tsxthat asserts a layout the app does not have, which would fail its CI. Updates now remove new files in those paths and say so in the report. If you already ran an update on a web repo with 2.2.0 or 3.0.0, delete those files if you do not use the starter UI (git rm -r app/_components app/globals.css app/not-found.tsx app/error.tsx app/icon.svg tests/layout.test.tsx). Found while migrating the platform’s own test application to the new marketplace.
[3.0.0] — 2026-10-07
Section titled “[3.0.0] — 2026-10-07”The project is now sdlc-foundry (formerly claude-platform), an agentic SDLC platform. This is a breaking release because the plugin marketplace id changed; nothing else in how you work changes (slash commands keep their names).
Breaking changes and migration
Section titled “Breaking changes and migration”| What | Before | Now |
|---|---|---|
| Repository | ika100/claude-platform (GitHub redirects it) |
ika100/sdlc-foundry |
| Plugin marketplace id | ika100-claude |
sdlc-foundry, so plugins install as shared@sdlc-foundry |
| Documentation site | ika100.github.io/claude-platform (gone) |
https://ika100.github.io/sdlc-foundry/ |
| Commits | no sign-off | DCO: git commit -s (see CONTRIBUTING) |
To migrate (once per machine, in Claude Code):
/plugin marketplace remove ika100-claude/plugin marketplace add ika100/sdlc-foundry/plugin install shared@sdlc-foundry # and svc, gitops, web, svc-java, svc-go, app as you use themthen restart Claude Code, and in every repository generated from the templates run /shared:update-service (it rewrites .claude/settings.json and _src_path in .copier-answers.yml; review the branch it creates). /shared:doctor detects plugins still installed under the old marketplace and prints these steps.
Renamed to sdlc-foundry (breaking), DCO sign-off
Section titled “Renamed to sdlc-foundry (breaking), DCO sign-off”- The project is now
sdlc-foundry(formerly claude-platform): repositoryika100/sdlc-foundry, documentation at https://ika100.github.io/sdlc-foundry/, and the plugin marketplace idsdlc-foundry(wasika100-claude), so plugins install asshared@sdlc-foundry. The old name contained a third-party trademark and described only part of the scope: this is an agentic SDLC platform. GitHub redirects the old repository URL; the old Pages URL is gone. Slash commands do not change (/svc,/gitops,/shared,/app,/web,/svc-java,/svc-go). - Migration:
/plugin marketplace remove ika100-claude,/plugin marketplace add ika100/sdlc-foundry,/plugin install <name>@sdlc-foundryfor each plugin, restart Claude Code; in every generated repo run/shared:update-service(the template-owned.claude/settings.jsonand the_src_pathin.copier-answers.ymlare rewritten; a regression test covers a repository generated before the rename)./shared:doctordetects plugins still installed under the old marketplace and prints these steps. - DCO: every commit of a pull request needs a
Signed-off-bytrailer (git commit -s), enforced by theDCO sign-offCI job (scripts/check-dco.py, bots exempt). See CONTRIBUTING. - Dependabot’s grouped PR titles exceed the title limit, so the
pr-titlecheck skips Dependabot PRs. A naming guard test keeps the old names out of the tree. Plugins: shared 0.7.2, svc 2.1.2, gitops 1.3.2, web, svc-java and svc-go 0.2.3, app 0.1.4.
[2.2.0] — 2026-10-07
Section titled “[2.2.0] — 2026-10-07”The platform is now an open-source project with a documentation site: https://ika100.github.io/claude-platform/ (vision, problem statement, client value, usage scenarios, user documentation). It also gets a feedback loop for problems found in the field, a designed starter UI for new web services, and a leaner CI. No breaking changes.
Upgrading from 2.1.x
Section titled “Upgrading from 2.1.x”- Plugins:
claude plugin marketplace update ika100-claude, thenclaude plugin update <name>@ika100-claudefor each installed plugin, then restart Claude Code (a running session keeps the old prompts). You get/shared:report-issueand the escalation line in every agent and command (shared 0.7.1, svc 2.1.1, gitops 1.3.1, web, svc-java and svc-go 0.2.2, app 0.1.3). - Generated repos: run
/shared:update-servicefor pull requests with the new skeleton:concurrencythat cancels superseded PR runs, monthly grouped Dependabot, theorg.opencontainers.image.sourcelabel in Dockerfiles (links the GHCR package to the repository),git init -b mainfor new repos, and for web repos the test-setup cleanup. Existing web apps keep their own UI (app/**is project-owned); new web services get the designed starter UI. - Forks and contributors:
mainis protected and the single required status isci-success; seeCONTRIBUTING.md(new) and runscripts/ci-local.shfor the cheap checks.
Web template: a designed starter UI
Section titled “Web template: a designed starter UI”- web-nextjs now ships a real design instead of a bare heading: plain-CSS design tokens (
app/globals.css), automatic light/dark themes, fluid typography, a sticky header with brand mark, a hero with gradient headline and actions, a responsive “what’s included” card grid, numbered getting-started steps, a branded 404 and error boundary, a favicon,themeColor/viewportmetadata, a skip link, visible focus styles and reduced-motion support. No new dependencies, no network fonts (Docker builds stay reproducible). - Components live in
app/_components/; tests cover every component (15 tests, 100% line coverage) and the Playwright smoke test now checks landmarks, the 404 page and a 375px viewport without horizontal scroll.vitest.setup.tscleans up after each test (without it, repeatedrender()calls accumulate). - Existing services are unaffected (
app/**is project-owned); new web services get the design. - e2e: log checks no longer fail intermittently under
pipefail(kubectl logs | grep -qcould die of SIGPIPE).
Documentation site
Section titled “Documentation site”- GitHub Pages site at https://ika100.github.io/claude-platform/ (Astro Starlight, no analytics, no third-party requests, full-text search): vision, problem statement, client value by role with measured evidence and honest limits, seven usage scenarios, get started, concepts, and the user documentation. Guides, ADRs, the changelog and community pages are synced from the repository’s Markdown; the command, shape, configuration and CLI reference pages are generated from the code, so they cannot drift. Built and link-checked in CI (
sitejob, part ofci-success), published bypages.yml; Dependabot covers its npm dependencies. docs/USER-JOURNEY.mdrefreshed: a chapter for secrets, Postgres, observability and guard rails,doctor/status/report-issue, and honest limits that match the tested-script design.
Open source
Section titled “Open source”- Public repository under the Apache License 2.0 (
LICENSE,NOTICE), with a contributing guide, Code of Conduct (Contributor Covenant 2.1), security policy (private vulnerability reporting), support guide, CODEOWNERS and a PR template. The three todo test repositories are public too. - Legal hygiene:
THIRD_PARTY_NOTICES.mdandLICENSES/(Apache-2.0, CC-BY-4.0, ISC); the web template’s icon paths are adapted from Lucide, soicons.tsxnow carries Lucide’s ISC notice (which travels into every generated web app); trademark/affiliation and no-warranty disclaimers, a privacy and no-telemetry statement and an AI-assistance disclosure in the README; the repository is REUSE 3.3 compliant (REUSE.toml,reuse lintin CI) so every file has machine-readable license and copyright data. - Repository protection:
mainrequires a pull request and theci-successstatus, linear history, no force-push or deletion;v*tags are immutable; secret scanning with push protection, Dependabot alerts and security updates, CodeQL, and read-only Actions tokens with approval for outside contributors’ workflows. - A pre-publication secret scan (gitleaks over the full history of all four repositories, detect-secrets, issue/PR text and run logs) found no credentials.
- The
/shared:report-issuecommand now warns that issues on this repository are public (shared 0.7.1).
CI usage cut, link check, local CI
Section titled “CI usage cut, link check, local CI”- Platform CI only runs what a change needs: a
changesjob (plaingit diff, no third-party action) gates the template smoke jobs and the cplat tests; docs-only pull requests run the validation jobs only (about 2 minutes instead of about 25). Superseded runs of the same PR are cancelled. A finalci-successjob aggregates everything and is the single required status for branch protection (skipped jobs count as passed; matrix jobs would otherwise block merges forever). The e2e workflow is path-filtered, nightly and cancels superseded PR runs. - Dependabot is monthly and grouped (Actions together; minor and patch together per language ecosystem) in the platform repository and in every template, so a month of updates is a handful of PRs instead of 16; security updates still arrive immediately.
- Generated repos cancel superseded PR runs (
concurrencyin everyci.yml). scripts/check-links.py(relative Markdown links, in CI) andscripts/ci-local.sh/devbox run ci-local(the cheap CI checks locally;--renderalso renders every template).
Feedback loop and fixes for the old issues
Section titled “Feedback loop and fixes for the old issues”/shared:report-issue(cplat feedback): when a step fails because a platform template, script or command misbehaves, every agent and command now tells Claude to stop, summarize and offer to file an issue. The draft carries platform/plugin versions, shape, OS and tool availability plus the error tail, with tokens, passwords, e-mail addresses and home-directory names removed; it is shown to you and filed throughghonly after your OK (a prefilled browser link withoutgh). An unexpectedcplatcrash prints the same pointer. Issue templates (bug_report,feedback) and labels added; README documents it.- Old issues triaged: #3 (invalid
team: "@org"label), #7 (commonLabels) and #6 (GHCR pull path for local k3d) are obsolete since v2 (no service-level manifests;cluster-upcreates the pull secret,doctorchecksread:packages). #4: copier tasks nowgit init -b main(the CLI already renamed tomain). #5: generated Dockerfiles carryorg.opencontainers.image.source(links the GHCR package to the repo, so CI can push) and ADOPTING has a Troubleshooting section. #8:/svc:quick-taskPR body no longer lists an unchecked security box; it says what was not run. - web-nextjs
dependabot.ymlignores major bumps of thenodeDocker image and@types/node(ADR-005: the Node LTS major is bumped deliberately; the bot proposed Node 25 and@types/node26 against a Node 24 image). - Plugins: shared 0.7.0 (new command), svc 2.1.1, gitops 1.3.1, web/svc-java/svc-go 0.2.2, app 0.1.3 (escalation line in every agent and command).
[2.1.0] — 2026-10-07
Section titled “[2.1.0] — 2026-10-07”Everything the GitOps repo needs around your services, still declared in one place and rendered by render.py: secrets, a Postgres addon, OpenTelemetry observability, Kyverno guard rails, and a hardened CI supply chain. No breaking changes.
Upgrading from 2.0.x
Section titled “Upgrading from 2.0.x”- Generated repos: run
/shared:update-service(gitops-app: newrender.py,local-cluster.sh,check-policies.sh; Python services: standard OpenTelemetry variables; all service/web repos: SHA-pinned actions, scan + SBOM, Dependabot). Nothing changes behaviour until you use a feature. - New plugin commands (restart Claude Code after
claude plugin update):/gitops:secret,/gitops:addon;/gitops:composegains--generate,--secret,--uses. - Cluster prerequisites only for features you turn on: External Secrets Operator (
secrets:), CloudNativePG (addons.postgres), Kyverno (policies:);devbox run cluster-upinstalls them locally, real clusters need them installed by their owner. OpenTelemetry needs no operator. - Local credentials:
cluster-upacceptsREPO_TOKENandPULL_TOKENfor least-privilege cluster credentials.
Kyverno guard rails (Phase G, slice 6, ADR-022)
Section titled “Kyverno guard rails (Phase G, slice 6, ADR-022)”policies:inapp.yaml(opt-in) renders a namespaced CELNamespacedValidatingPolicyper environment with the platform’s conventions (numeric non-root, read-only filesystem, no escalation, dropped capabilities, no privileged/host access, requests + memory limit,part-oflabel, allowed registries, no:latestoutside dev). Audit in dev/staging and Enforce in prod by default; per environmentAudit | Enforce | Off.- Checked before merge:
devbox run validate/ CI evaluate the rendered overlays and addons offline with the Kyverno CLI (scripts/check-policies.sh); the CI downloads the CLI with a pinned checksum. cluster-upinstalls Kyverno whenpolicies:is declared (WITH_KYVERNOoverrides);doctorchecks it. e2e runs the whole stack underEnforceand proves a violating Deployment is denied.
Observability: OpenTelemetry base, optional UI stack (Phase G, slice 5, ADR-021)
Section titled “Observability: OpenTelemetry base, optional UI stack (Phase G, slice 5, ADR-021)”addons.observabilityrenders an OpenTelemetry Collector per environment (OTLP in, a Prometheus scrape job per service with ametrics:path, optional OTLP/HTTP export viaexportTo+headersSecret) and injects the standardOTEL_*variables into every service. No operator, no CRDs; works with any OTLP backend.ui: lgtm(optional):cluster-upinstalls Grafana, Tempo, Loki and Prometheus as a shared dev stack athttp://grafana.localhost:8088. Dev/demo only.shapes.ymlruntime gainsmetrics;compose addwrites it intoservices.yaml. service-python now configures tracing from the standard OTel variables (legacyOTLP_ENDPOINTstill works) and ships tracing tests; run/shared:update-servicein Python services./gitops:addon add observability [--ui lgtm | --export-to URL];doctorandstatusknow the addon. Config addons are pruned, databases never (separate ApplicationSets).
Addons: Postgres (Phase G, slice 4, ADR-020)
Section titled “Addons: Postgres (Phase G, slice 4, ADR-020)”addons:inapp.yaml+uses: [postgres]on a service: a CloudNativePGClusterper environment (sizing as scalars or per-env maps) andDATABASE_URL/PG*injected from the operator’s Secret (never in git). The contract, not the implementation, is what services see./gitops:addon add|remove|list(cplat addon),compose add --uses postgres;doctorchecks the operator,statusshows the addon’s Argo health. Removing an addon never deletes the data (prune: false).cluster-upinstalls CloudNativePG whenaddons.postgresis declared (WITH_CNPG=0/1overrides). e2e proves a real connection with the generated credentials.- gitops plugin 1.2.0.
Supply chain (Phase G, slice 3, ADR-019)
Section titled “Supply chain (Phase G, slice 3, ADR-019)”- All GitHub Actions pinned by commit SHA (91 references, platform and templates) with
scripts/pin-actions.py;--checkruns in platform CI. Every generated repo (incl. gitops-app and library-python) gets Dependabot for Actions, which keeps the pins current. - Least-privilege workflows: top-level
permissions: contents: readeverywhere;packages: writeonly on the image jobs. - Image scan + SBOM in the service/web CI: Trivy scans each pushed architecture (fixable HIGH/CRITICAL; blocks
v*.*.*releases, reports on main) and a CycloneDX SBOM is kept for 90 days. cluster-upcredential split:REPO_TOKEN(Contents: read) for ArgoCD andPULL_TOKEN(read:packages) for GHCR instead of one broad token.
Secrets with External Secrets Operator (Phase G, slice 2, ADR-018)
Section titled “Secrets with External Secrets Operator (Phase G, slice 2, ADR-018)”secrets:inservices.yaml:generate: [KEY…]has ESO create random values in the cluster once per environment (DB passwords, signing keys);remote: {keys: […]}reads from a SecretStore (app.yamlsecretStore). Rendered asExternalSecret(+Passwordgenerator) and wired into the pod viaenvFrom. Secret values never enter git; secret-looking keys with a value inenv:are rejected.devbox run cluster-upinstalls ESO (k3sHelmChart, chart 2.12.0) and aplatform-secretsstore over thesecrets-storenamespace;WITH_ESO=0skips it.cplat compose add … --generate NAME=KEY,KEY --secret NAME=KEYand/gitops:secret set|list(cplat secret); values come from a hidden prompt or stdin. e2e test covers generated, stable and remote secrets.- gitops plugin 1.1.0.
Token efficiency and consistency (Phase G, slice 1)
Section titled “Token efficiency and consistency (Phase G, slice 1)”- Always-on cost −28%: the 41 agent/command descriptions (loaded into every session) are tightened from 10.8 KB to 6.3 KB, ≈ −1.1k tokens of the ≈ 3.95k always-on total, keeping what routing needs (what it does, when to use it, usage line).
shapes.py checknow enforces valid front matter and a 260-character cap, and fixes seven descriptions that were not valid YAML (unquoted:). - Model tiering:
product-managerruns on sonnet (structured story writing);architectand theappplanner stay on opus (decomposition and design decisions). /svc:build-feature --no-pmskips the product-manager phase for precise requests.
[2.0.0] — 2026-10-07
Section titled “[2.0.0] — 2026-10-07”v2: the GitOps repo owns every Kubernetes manifest; services ship an image. Driven by the end-to-end todo-app test (see ADR-017: The GitOps repo owns every Kubernetes manifest; services ship an image and Measured baselines). Plugin versions: svc 2.0.0, shared 0.6.0, gitops 1.0.0, web 0.2.0, svc-java 0.2.0, svc-go 0.2.0, app 0.1.1, shapes unchanged.
Breaking changes — migration: ADOPTING → Migrating from v1 to v2
Section titled “Breaking changes — migration: ADOPTING → Migrating from v1 to v2”- Service templates ship no
k8s/, nok3d/kubectl/k9s, nodeploy/deploy-check, noowner_label.shapes.py checkforbidsk8s/in them. - gitops-app generates manifests (
deployment.yaml,service.yaml,httproute.yaml,kustomization.yaml) from a richerservices.yamlentry (port, probes, user, volumes, env, secretRefs, replicas, resources, expose, environments) — no remote Kustomize bases. v1 entries (path:) are rejected with a pointer to the migration. - New services start in
devonly;/gitops:promoteaddsstaging, thenprod. Promotion pins an image tag only (stagingsha-<7>, prodX.Y.Z), verified in GHCR. /gitops:compose//gitops:promoteare thin wrappers over tested scripts; their agents are removed. The v1 platform-wide promote mode is gone (it needed service-owned overlays).- The per-shape
deploymentagents own the image only;/svc:build-featurePhase 5 is “Container image”.
- Gateway API exposure:
expose:→HTTPRoute;app.yamlholds gateway and per-env hostname templates (dev default{service}.{app}-dev.localhost).cluster-upinstalls the Traefik Gateway provider (the Gateway API CRDs come with k3s’ Traefik chart) and enables Traefik’s Gateway provider on k3s, and prints the URLs. Spike-verified on k3s v1.32.5/Traefik 3.3.6;*.localhostresolves everywhere (localtest.me / nip.io / lvh.me do not behind rebind-protecting DNS). cplat compose/cplat promotewith 21 more tests (fakegh, real renders);compose add --from-k8sseeds an entry from a v1 service;update-service --migrateremovesk8s/.shapes.ymlruntime:defaults per deployable shape (validated byshapes.py check).docs/adr/017-gitops-owns-manifests.md; CI renders a fixture product and runs the label check,kubectl kustomizeand kubeconform on the generated manifests; offlinevalidatein generated gitops repos.
End-to-end test and UX
Section titled “End-to-end test and UX”tests/e2e/run.sh+.github/workflows/e2e.yml(nightly, on relevant PRs, manual): renders a gitops-app, a Java service and a web app from the real templates, builds the images, starts the platform’s ownlocal-cluster.sh(Gateway only, local registry), applies the generated manifests and probes the services through the Gateway; asserts non-root numeric users and read-only filesystems. Its first run caught a regression before release: on a fresh cluster the Gateway API CRDs applied bycluster-upraced with k3s’ owntraefik-crdHelm chart (which installs them) —cluster-upnow only enables the provider.local-cluster.sh:WITH_ARGO=0(Gateway + namespaces only) andREGISTRY_PORT(local registry) modes./shared:status(cplat status): one table per product — pinned tag per environment, CI state of each service’smain, ArgoCD sync/health (--context).docs/HOW-IT-WORKS.md: diagrams of bootstrap, build, compose/render, promote and exposure, and which test guards what.
Faster / more stable CI
Section titled “Faster / more stable CI”- Native multi-arch image builds: the
dockerjob is a matrix (amd64 onubuntu-latest, arm64 onubuntu-24.04-arm, verified available for private repos) that pushes by digest;docker-publishmerges the digests into one manifest with the usual tags. PRs build amd64 only. Measured: web ≈ 9 → ≈ 3 min, Java ≈ 3 → ≈ 2 min (docs/BASELINES.md). - Docs-only pushes (
**.md,docs/**,.claude/**) skip CI on branches (PRs and tags always run);.github/dependabot.ymlin every service template (language ecosystem, Actions, Docker). actionlintruns over the platform’s workflows and every generated repo’s workflows in CI (it caught a corrupted expression on its first run).update-servicepicks a unique branch name when one with today’s date exists.
Removed
Section titled “Removed”SERVICE_REPOS_TOKEN and Argo credentials for service repos (Argo reads only the gitops repo), ?ref= handling, service-side overlays/ and PrometheusRule files (alerting is a gitops-side follow-up).
Added in the 2.0.0 cycle (phase A)
Section titled “Added in the 2.0.0 cycle (phase A)”Added (v2 work, phase A)
Section titled “Added (v2 work, phase A)”scripts/cplat/cplat.py— deterministic, tested implementation of the commands:new-service,update-service,doctor,shape; each prints what I will do (--dry-run) and what happened / next / how to undo. 29 tests intests/cplat(CI jobplatform-tests), including regression tests for defects found in the e2e run (module_name for non-Python shapes, stable_src_path, update keeps project files)./shared:doctor— preflight: tools,ghscopes, Docker, kube context (warns on non-local), plugin versions (with the “restart Claude Code” hint), repo platform version..platform-versionstamp in generated repos;update-servicereports the changelog range and lists overwritten skeleton files.docs/BASELINES.md: the numbers v2 is measured against.
Changed
Section titled “Changed”/shared:new-service(11 KB → 1.8 KB) and/shared:update-service(4.8 KB → 1.4 KB) are thin wrappers: preview with--dry-run, run, relay the report.- Fixed a latent defect: eleven commands/agents referenced repo-relative fragment files (
plugins/svc/fragments/shape-dispatch.md, …) that do not exist in a consumer repo (and cross-plugin paths are not reachable), so the model could not read them. They now callcplat shape, which prints the shape and the agent routing as JSON; the two fragment files are removed.
[1.1.1] — 2026-10-06
Section titled “[1.1.1] — 2026-10-06”Fixes found by running the platform end to end on a real todo app (Java API + Next.js UI + gitops-app on a local k3d/ArgoCD cluster): 12 defects, several of which made every generated deployable service undeployable. Plugin versions: shared 0.5.1, gitops 0.3.1. Existing repos: run /shared:update-service (skeleton fixes: CI, Dockerfiles, devbox), and see the migration notes in the entries below for project-owned files (k8s/base/deployment.yaml label value, pom.xml patched Tomcat/Jackson for Java).
- gitops-app:
devbox run cluster-up/cluster-down(scripts/local-cluster.sh): a local k3d cluster with ArgoCD, repo credentials for private GitHub repos, pre-created<app>-<env>namespaces with a GHCR pull secret, and the root Application. Everything the e2e test had to do by hand.
Changed
Section titled “Changed”- gitops-app:
devbox run bootstrapnow requiresKUBE_CONTEXT=<context>and prints it; before, it applied to whichever kube context was current.
- web-nextjs: bootstrap failed (
devbox run -- pnpm install→SyntaxError) becausepackageManager: pnpm@12.9.1was newer than the pnpm devbox provides (11.x). Both pins are nowpnpm@11.22.0andshapes.py checkenforces that they match (ADR-003 amendment). - All deployable templates: Dockerfiles ran as a named user (
nonroot,app), and Kubernetes cannot verifyrunAsNonRoot: truefor a name, so pods stayed inCreateContainerConfigError(“image has non-numeric user (nonroot)”). All images now use a numericUSER;shapes.py checkenforces it. Existing repos get this through/shared:update-service(the Dockerfile is a skeleton file). - service-java: the image never started:
ADDcreated the OpenTelemetry agent jar as-rw-------root, so the non-root runtime user got “Error opening zip file or JAR” (ADD --chmod=0644). CI smoke jobs now build the Java, Go and web images and start them with a read-only root filesystem and probe them. - All deployable templates: images were
linux/amd64only, so Apple-silicon k3d/Docker nodes failed with “no match for platform in manifest”. Thedockerjob now publisheslinux/amd64,linux/arm64(QEMU) onmain/tags; PR builds stay amd64. The Go and Java builder stages run on the native platform (--platform=$BUILDPLATFORM; Go cross-compiles withGOOS/GOARCH, the jar is portable) so only web and Python pay for emulation. - gitops
promote(app mode): staging pins used?ref=sha-<short>which kustomize cannot fetch (only a full commit SHA is a valid ref), and prod pins usednewTag: vX.Y.Zalthough CI publishes semver images asX.Y.Z. The agent and command now specify the ref and the image tag separately. - All deployable templates (since v1.0):
team: "@owner"in the Deployment/PrometheusRule labels is not a valid Kubernetes label value, so no generated service could ever be applied (Argo:Deployment ... is invalid: metadata.labels: Invalid value: "@ika100"). Labels now use the computedowner_label(@my-org/team→my-org-team); CI renders the templates and validates every label (scripts/check-k8s-labels.py). Existing repos:k8s/base/deployment.yamlandk8s/monitoring/alerts.yamlare project-owned — changeteam: "@x"toteam: "x"by hand. - All templates: the generated CI workflow did not trigger on pushes to
main, so merged code was never built and nolatest/sha-*image was pushed (the GitOps flow depends on them).mainis now in the push triggers andshapes.py checkenforces it. - All deployable templates:
k8s/base/deployment.yamlis now project-owned (_skip_if_exists). It is the file every app edits (env vars such asAPI_URL, resources), and/shared:update-servicesilently reverted those edits.shapes.py checkenforces it. Existing repos: nothing to do, the next update simply stops touching the file. - gitops-app: CI could not
kustomize buildoverlays whose remote bases are private service repos (GITHUB_TOKENis repo-scoped). The quality job now uses an optionalSERVICE_REPOS_TOKENsecret for the full check and otherwise runs the newvalidate-staticrecipe with a warning. - service-java:
devbox run auditaborted in CI whenNVD_API_KEYwas unset (empty key → “Invalid API Key”); it now falls back totrivy fs(aftermvn dependency:resolve, with--offline-scan, because trivy otherwise fetches poms from Maven Central and gets HTTP 429 on shared CI runners). A fresh project also failed that audit, sopom.xmloverrides the vulnerable BOM-managed Tomcat/Jackson versions and.trivyignorerecords the one CVE with no fix in Spring 6.x (ADR-009 amendment). - gitops-app: the bootstrap did not run
devbox install, sodevbox.lockwas left untracked after the firstdevbox run.
[1.1.0] — 2026-10-06
Section titled “[1.1.0] — 2026-10-06”Multi-shape platform. Implements the platform vision (Design history: the original vision PRD, ADR-001…016): six repo shapes, shape-aware orchestration, a multi-repo planner, and a documented add-a-shape contract. Existing Python repos are unaffected (their templates are unchanged apart from doc comments); no breaking changes. See User journey for the end-to-end walk-through.
Plugin versions: svc 1.1.0, shared 0.5.0, gitops 0.3.0, web 0.1.0, svc-java 0.1.0, svc-go 0.1.0, app 0.1.0.
shapes.yml— machine-readable shape registry (all six shapes, allstatus: stable).scripts/detect-shape.sh— shape detection from.copier-answers.ymlwith sniffing fallback (ADR-008), plusscripts/test-detect-shape.shfixtures.scripts/shapes.py— validatesshapes.ymland checks it against templates, plugins and the PRD §4 table.plugins/shared/fragments/shape-detection.md— canonical detection procedure for commands and agents.scripts/shapes.py checknow enforces the add-a-shape contract (PRD §4.1): canonical devbox recipes, CLAUDE.md, plugin enablement in the template settings, plugin agents, detection registration. Documented in the newdocs/templates.md.- CI job
validate-shapes;devbox run shapes-checkanddevbox run test-detect(included indevbox run smoke).
Plugins
Section titled “Plugins”-
shared 0.5.0 (docs/UX pass): new
/shared:update-service [--ref] [--data k=v]—copier updatecannot work on repos bootstrapped by/shared:new-service(the template has no git history of its own), so the command re-applies the template with the repo’s recorded answers on a review branch and lists overwritten skeleton files./shared:new-servicenow records a stable_src_path, no longer passes the Python-stylemodule_nameto non-Python templates (it had turned the Go module path intomy_saas), and runs its clone+copier steps in one shell. All docs that pointed atcopier updatenow point at/shared:update-service; ADOPTING has a shape table, an end-to-end product flow, and a corrected migration guide (/shared:check-quality, not/svc:check-quality). -
shared 0.4.0:
/shared:new-servicegains--type <shape>(aliases--library,--web,--gitops); template, topic and--pythonhandling are read fromshapes.yml. With no flag it still produces aservice-pythonrepo. Requesting aplannedshape fails with a clear message. -
svc 1.1.0 (phase 2): every orchestration command resolves
$SHAPEand routes coder/tester/deployment/observability/release to$SHAPE_PLUGIN:<role>via the newplugins/svc/fragments/shape-dispatch.md. The architect plan metadata gains a requiredshape:field (missing →service-pythonwith a deprecation notice)./svc:build-featuregains--from-plan <path> [<repo-id>](ADR-011) and skips deployment for non-deployable shapes./svc:releasedispatches to the shape’s release agent./shared:check-qualityand the quality/security agents are shape-neutral. -
gitops 0.3.0 (phase 3): new
composeagent and/gitops:compose add|remove <service...>;/gitops:promoteworks ingitops-apprepos, from a service repo via.platform-app.yml, with batch/--alland one PR per invocation. -
web 0.1.0 (new, phase 4):
coder,tester,deployment,observability,releaseagents for Next.js repos, plus a SessionStart hook that runsdevbox run install. ThesvcSessionStart hook now only runsuv syncwhenpyproject.tomlexists. -
app 0.1.0 (new, phase 5):
/app:build-feature <desc>(PM stories per repo →planneragent → validated topo-sorted multi-repo plan; plan-only, ADR-007) and/app:plans list|show|start|done|abandon(ADR-011 lifecycle). -
svc:
/svc:build-feature --from-planno longer edits the plan file (it lives in the gitops-app repo); progress is recorded with/app:plans. -
svc-go 0.1.0 and svc-java 0.1.0 (new, phase 6):
coder,tester,deployment,observability,releaseagents for Go and Spring Boot repos; releases follow ADR-012 (pom.xmlviaversions:set; Go has no version file — the git tag is the version).
Templates
Section titled “Templates”- service-go (new, phase 6): chi + slog, golangci-lint v2,
govulncheck, optional Prometheus/metrics, distroless static image with ldflags version, pre-resolvedgo.mod/go.sum. Verified by runningdevbox run test/quality/auditon rendered projects (both observability variants). - service-java (new, phase 6): Spring Boot 3.5 / JDK 21 / Maven, Spotless (google-java-format) + Checkstyle, JUnit 5 + JaCoCo 80% gate, Actuator probes, optional Micrometer Prometheus + OTel Java agent, distroless java21 image. Verified by running
devbox run lint/test/qualityon rendered projects and booting the jar. - ADR-009 / ADR-010 amendments record where the implementation differs from the ADR text.
- gitops-app generates a root Argo Application (
bootstrap/<app>-root.yaml, app-of-apps) and a human-onlydevbox run bootstraprecipe, so after onekubectl applyper cluster all changes deploy through merged PRs;validatealso schema-checks it with kubeconform (ADR-014 amendment). - gitops-app gains
scripts/plan.py(plan validation + lifecycle),devbox run plan-check(part ofvalidate), and enables theappplugin. - web-nextjs (new, phase 4): Next.js 16 App Router, TypeScript strict, pnpm (
packageManagerpinned,pnpm-workspace.yamlwithallowBuilds), Node 24 LTS pinned inpackage.json/devbox.json/Dockerfile, Vitest + Testing Library (80% gate), optional Playwright (needs_e2e), optional/api/metrics+ OpenTelemetry (needs_observability),/api/health+/api/ready, standalone-output multi-stage non-root Dockerfile, Kustomize base/overlays, CI. The rendered project is verified in CI (pnpm install, lint, typecheck, test, build). - gitops-app (new, phase 3): product-scoped GitOps repo —
applications/<app>/{services.yaml,applicationset.yaml,overlays/<env>/<service>/}generated byscripts/render.py, devbox recipes (render,validate= render-check + kustomize + kubeconform,security), CI, CLAUDE.md. See the ADR-013/014 amendments. /shared:new-servicegains--app <org>/<repo>(writes.platform-app.yml).
Documentation
Section titled “Documentation”docs/templates.md(new): template authoring guide with the pitfalls found while building the Go, Java and web shapes. README, ARCHITECTURE, ADOPTING (web and gitops-app migration sections), AGENTS updated; PRD marked implemented.
Migration
Section titled “Migration”- Run
/plugin marketplace update. Nocopier updateneeded for existing Python repos (their templates are unchanged). - New repos:
/shared:new-service <name> --web | --gitops | --type service-java | --type service-go.
[1.0.1] — 2026-05-22
Section titled “[1.0.1] — 2026-05-22”CI-only patch. No plugin or template behavior changes.
- CI smoke-test no longer fails on
devbox: not found. The copier templates’ bootstrap_tasks(which invokedevbox install) are now skipped via--skip-tasksin.github/workflows/ci.yml; CI verifies template rendering only. The full render-and-bootstrap path remains covered bydevbox run smokelocally. Broken since v0.2.1.
[1.0.0] — 2026-05-19
Section titled “[1.0.0] — 2026-05-19”Phase 3 of the agentic efficiency overhaul. First stable major. Orchestration commands restructured for parallelism and to skip wasted work; per-command preludes deduplicated.
Breaking changes
Section titled “Breaking changes”/svc:build-featurephase numbering changed. The old Phase 3a (intermediate quality gate), Phase 4 (tester), Phase 4a (security) are merged into a single Phase 4 — parallel QA fan-out that runs quality + tester + security in one orchestrator turn. Phase 5 stays “deployment”; old Phases 6/7 are unchanged. Any consumer scripts that grep transcripts forPhase 4a completeneed updating./svc:fix-bugphase numbering changed. Old “Phase 5 — Commit and open PR” is now “Phase 4 — Commit and open PR” after the redundant intermediate phase was dropped in v0.1.4./svc:quick-taskphase numbering changed. Old “Phase 4 — Commit and open PR” is now “Phase 3 — Commit and open PR”.
Plugins
Section titled “Plugins”- svc 1.0.0:
- Scope classifier (
/svc:build-featurePhase 0a): the orchestrator now classifies the request before running PM + Architect. Typos / docs / one-file tweaks are routed to/svc:quick-task; bug reports are routed to/svc:fix-bug; standard features get a “confirm full pipeline?” prompt. Saves PM + Architect on requests that don’t need them. - Parallel QA fan-out (
/svc:build-featurePhase 4,/svc:quick-taskStep 2): quality + tester + security (build-feature) or quality + tester (quick-task) now spawn in a single orchestrator turn, mirroring/shared:check-quality. Wall-clock is the longest leg, not the sum. - Pre-computed subagent context: every orchestrator now computes
$PROJECT_MAP(top-level dirs) and$TOUCHED_FILES(git diff --name-only $BASE_REF..HEAD) up-front and prepends them as<project-map>/<touched-files>blocks to every subagent prompt. Each agent saves 2–5 exploratory Glob/Grep calls. - Phase prelude fragment: new
plugins/svc/fragments/phase-prelude.mddocuments the canonical pre-Phase-1 workflow (git status check, branch creation, base-ref capture, context pre-compute). The three orchestration commands now reference it instead of carrying drifted copies.
- Scope classifier (
Migration
Section titled “Migration”- Run
/plugin marketplace updateto pull svc 1.0.0. - Existing service repos do not need a
copier updatefor this release — only plugin contracts changed, no template files. - If you watch for specific phase headers in agent output (CI greps, dashboards), update them per the breaking changes above.
[0.4.0] — 2026-05-19
Section titled “[0.4.0] — 2026-05-19”Phase 2 of the agentic efficiency overhaul: boilerplate that the svc agents used to regenerate on every invocation is now part of the service-python Copier template. Agents become verify-and-extend instead of generate-from-scratch. Non-breaking for consumers — run copier update to pull the new template files.
Templates
Section titled “Templates”- service-python: new pre-rendered observability files under
src/{{ module_name }}/:tracing.py— OpenTelemetry OTLP exporter wired viaOTLP_ENDPOINTenv var.main.pynow exposes/metrics(Prometheusgenerate_latest) and conditionally callsconfigure_tracing().k8s/monitoring/alerts.yaml— default PrometheusRule withHighErrorRate,HighLatencyP95,PodRestarting.docs/env-vars.md— documentsLOG_LEVEL,OTLP_ENDPOINT,METRICS_PORT, andDATABASE_URLwhen applicable.
- service-python: new pre-rendered Alembic scaffolding (gated by
needs_migrations):alembic.iniwithscript_location = migrations.migrations/env.pythat requiresDATABASE_URLfrom env and leaves a TODO for wiringtarget_metadata.migrations/script.py.makowith typedupgrade()/downgrade()signatures.
- service-python copier.yml: new
_excludeblock skipsalembic.iniandmigrations/whenneeds_migrations=false._skip_if_existsextended withdocs/env-vars.mdandk8s/monitoring/**so they’re project-owned after bootstrap.
Plugins
Section titled “Plugins”- svc 0.2.0: three agents slimmed against the template now owning the boilerplate:
observability.md: 158 → 45 lines. Verifies the templated logging/metrics/tracing files exist, then focuses on service-specific instrumentation and alert tuning.deployment.md: 100 → 61 lines. References the template’sDockerfile+.github/workflows/ci.ymlinstead of restating the YAML; verifies CI integrity and the dry-run gate.migrations.md: 139 → 77 lines. Skips thealembic initboilerplate (template handles it); focuses on wiringtarget_metadata, generating + verifying migrations, and maintaining the runbook.
[0.3.0] — 2026-05-19
Section titled “[0.3.0] — 2026-05-19”Devbox awareness extended to every plugin and the platform repo itself. No breaking changes — repos without devbox.json see the new hooks as a silent no-op.
Plugins
Section titled “Plugins”- shared 0.3.0: new
hooks/hooks.jsonwith aSessionStarthook. Checksdevboxis onPATH(prints install URL if missing, exits 0 — non-blocking) and runsdevbox installif adevbox.jsonis present. Makes any consumer repo that uses thesharedplugin (library repos, GitOps repos, the platform repo) devbox-aware out of the box. - gitops 0.2.0: same new
hooks/hooks.jsonasshared. GitOps repos that add adevbox.json(forkubectl,argocd,kustomize) get automatic sync; older GitOps repos see a no-op. - svc 0.1.3: existing
SessionStarthook hardened with the samecommand -v devboxhost-check, plus a[ -f devbox.json ]guard so it no-ops cleanly in non-devbox directories.
Platform repo
Section titled “Platform repo”- New root
devbox.json: pinsjq,python@3.13,uv,git,gh, andinit_hook-installscopier(invoked at runtime viauv tool run --from copier copier ...so it’s reachable from inside the devbox PATH). New scripts:validate(jq-checks marketplace + plugin manifests + hook files),smoke-service,smoke-library,smoke(runs all three). Smoke recipes run individualdevbox run lint+devbox run typecheckinstead of the chaineddevbox run qualityrecipe — devbox 0.17.2 has a wrapper bug that mis-evaluates the chained recipe even though both children succeed; tracked separately. CLAUDE.mdsmoke-test section rewritten to usedevbox run validate/smoke-service/smoke-libraryinstead of host-installedjq+copier. Only host requirement isdevboxitself.docs/AGENTS.mddocuments the new SessionStart hooks under the golden-rule section.
[0.2.1] — 2026-05-19
Section titled “[0.2.1] — 2026-05-19”Bugfix release. Resolves the two issues raised after the /shared:new-service UX overhaul (#1, #2).
Templates
Section titled “Templates”- service-python + library-python (#1):
_tasksincopier.ymlnow runsdevbox installanddevbox run -- uv sync --all-extrasto materializedevbox.lockanduv.lock, then makes an initialchore: bootstrapcommit. A freshcopier copy …now leavesgit statusclean instead of two lockfiles untracked. RequiresdevboxonPATHat template-render time (already a prerequisite for all other recipes in the template). - service-python (#2): tightened the
dockerjob’s login andbuild-push-actionconditions fromgithub.event_name != 'pull_request'togithub.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')). Feature-branch pushes now build only, matching the policy documented in the template’sCLAUDE.md(and avoiding the 403 from a brand-new repo’sGITHUB_TOKENlackingpackages: write).
Plugins
Section titled “Plugins”- shared 0.2.1:
/shared:new-servicePhase 3 rewritten for the new copier-side commit. Instead ofgit add -A && git commit, it nowgit commit --amend --reset-authors the bootstrap commit produced by_tasksso the user owns it with the richer/shared:new-servicecommit message. Falls back to the legacyadd && commitpath if_tasksdidn’t produce a commit (older templates).
[0.2.0] — 2026-05-19
Section titled “[0.2.0] — 2026-05-19”UX overhaul for /shared:new-service based on dogfooding feedback. Non-breaking — old flag form (--description "...") still works.
Plugins
Section titled “Plugins”- shared 0.2.0:
/shared:new-servicerewritten for fewer interactions:- Natural-language args. First token = project name, the rest = description.
/shared:new-service hello-world a simple rest apinow scaffolds end-to-end with zero further prompts. Old--description "..."flag still accepted. - Auto-preflight. Silently runs
uv tool install copierifcopieris missing. Ifghis missing, automatically falls back to “skip GitHub steps” mode and emits the exactgh repo create/gh repo editcommands in the final report (instead of aborting). - Auto-detect
GITHUB_ORG. Triesgh api user -q .login, falls back to the copier template’sika100default. No prompt. - No confirmation table. Resolved inputs go on one compact line; bootstrap proceeds immediately. The user can correct after the fact — everything is local and reversible until Phase 4.
- Empty/stub directory auto-clean. If
./<name>is empty or contains only.claude/, removes it with one warning line. Non-empty directories still abort. - Single-round prompt when truly missing. If both name and description are absent, asks once via plain text (
<name> <description>on one line) — noAskUserQuestion, no batched dialog. - Handles missing git identity. Falls back to one-off
-c user.name=... -c user.email=...on the bootstrap commit so fresh hosts without global git config don’t error out.
- Natural-language args. First token = project name, the rest = description.
Templates
Section titled “Templates”No template changes in this release.
[0.1.1] — 2026-05-19
Section titled “[0.1.1] — 2026-05-19”Bugfix release. All five issues found during the hello-world dogfood are resolved.
Plugins
Section titled “Plugins”- shared 0.1.1:
/shared:new-servicenow shallow-clones the platform repo before invoking Copier instead of using the unsupportedgh:<owner>/<repo>/<subdir>URL form. Also passes--trustbecause the templates declare_tasks.
Templates
Section titled “Templates”-
service-python:
- CI: added
workflow_dispatch:trigger so freshly-bootstrapped repos can run their first validation without needing a PR. - devbox.json: dropped
--frozenfrom the init_hook so fresh repos canuv syncand generateuv.lockon first session. - pyproject.toml: added
ruff,mypy,pip-auditto dev deps souv run <tool>uses venv-installed versions (the devbox-Nix versions can’t see project deps like pydantic). Excludedtests/from mypy and disableddisallow_untyped_decorators(FastAPI decorators lack stubs). - Python source files: pre-formatted to ruff style — added blank line between module docstring and first import. Generated trees now pass
ruff format --checkon first bootstrap. - Dockerfile + .dockerignore: README.md is now included in the docker build context (hatchling needs it to satisfy
readme = "README.md"in pyproject.toml).
- CI: added
-
library-python:
- Same
workflow_dispatch:trigger, dropped--frozen, and the same dev-dep + mypy additions as service-python.
- Same
Validated by
Section titled “Validated by”ika100/hello-world— green CI on all 6 jobs after applying these fixes locally. Bumping the platform to 0.1.1 means a fresh/shared:new-serviceinvocation produces a green-on-first-PR repo.
[0.1.0] — 2026-05-19
Section titled “[0.1.0] — 2026-05-19”Plugins
Section titled “Plugins”- svc 0.1.0: initial release. 8 agents (product-manager, architect, coder, tester, migrations, observability, release, deployment) + 5 commands (plan-feature, build-feature, quick-task, fix-bug, release).
- gitops 0.1.0: initial release. 2 agents (deployment, promote) + 1 command (promote). ArgoCD ApplicationSet conventions documented.
- shared 0.1.0: initial release. 2 agents (quality, security) + 2 commands (check-quality, new-service).
Templates
Section titled “Templates”- service-python: initial. Full Python service skeleton — devbox, CI (quality/test/security/pr-title/docker), Dockerfile, k8s base + overlays, FastAPI scaffolding, optional observability/migrations toggles.
- library-python: initial. Slim Python library skeleton — devbox, CI (quality/test/security/pr-title), no Docker, no k8s.
- AGENTS.md lifted from money-maker; orchestration model documented.
- ADOPTING.md: green-field + existing-repo migration paths.
- ARCHITECTURE.md: design rationale.