Skip to content

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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-job 3.13.18-alpine3.24 (new)

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 15 15.19 (digest none) → 15.19 (digest f617efc0)
  • postgres 16 16.15 (digest none) → 16.15 (digest 1ec419e9)
  • postgres 17 17.11 (digest none) → 17.11 (digest ed6923f9)
  • postgres 18 18.6 (digest none) → 18.6 (digest 39add80f)
  • valkey 8 8.1.11 (new)
  • valkey 9 9.1.3 (new)
  • nats 2.14 2.14.9 (new)
  • nats 2.15 2.15.2 (new)
  • nats 2.15.1 (new)
  • seaweedfs 4.51 (digest none) → 4.51 (digest f7a49e3f)
  • opentelemetry-collector 0.162.0 (digest none) → 0.162.0 (digest 39923a8e)
  • lgtm 0.35.0 (new)
  • velero-plugin-for-aws v1.14.4 (digest none) → v1.14.4 (digest c85a7fa8)
  • argo-cd v3.4.12 (new)
  • k3s v1.32.5-k3s1 (new)
  • gateway-api v1.2.1 (new)
  • operator nack 0.35.0 (new)
  • operator keda 2.19.0 (new)
  • operator metrics-server 3.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).

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.

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.

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.

Fixes:

  • cplat platform doctor in the platform repo reports gate recipes and gate CI jobs as not applicable instead of warning and suggesting /platform:update, which refuses there (#231, AC-123.24).

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 off in budget: (.platform-settings.yml or the user settings file) is read as off; YAML parsed it as false, which was refused.
  • cplat pr open and cplat component ship pr in a git worktree no longer commit the PR body: it moves into the worktree’s own git dir, and a .git/… --body-file resolves 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):

  1. Run /plugin marketplace update, then enable system, component, api, resource, instance, issue and platform; the migration switches the repo’s plugin settings for you.
  2. In every repo run cplat platform update --migrate, or from one place run /platform:fleet update over the whole fleet. In a gitops-app it converts app.yaml and services.yaml into system.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.
  3. A repo the converter cannot convert (an unknown key, a per-environment version that differs) stops with the key named; fix that key by hand and run it again.
  4. Replace old command names in your own scripts, CI and notes with the table above.

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, secret and the /app:ship gate. New cplat 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 carries schema: 1 and 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 runs tests/cplat in 4 round-robin shards (--shard I/N) with a cplat shards cover the suite job that fails when a test runs in no shard or in two; ci-success needs 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 wait ended. guide A pr-wait record has a pr_wait section: the result (green, merged, red, budget-refused, timeout, no-checks, closed, merge-failed, error) and the failing or pending check names. cplat runs report and the weekly retro show pr-wait results: and the top 5 red checks, and count red CI as red-ci instead of failed (the record keeps outcome: failed). The public retro issue shows checks as check <n>. Older records count as unrecorded.
  • 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 become decision. The record gets cause_source (given, rule, none); a stated cause always wins and no rule yields platform. The ship stop gate giving up and headless no progress now record environment. cplat runs report shows Unclassified escalations: <n> of <m> (<p>%) with the reasons still unknown (--json key unclassified), 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 claude exits non-zero, cplat retro propose prints the last 20 lines of its output to the job log. claude exited with 1 alone could not be diagnosed: the output stayed in propose.log on 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, the cplat runs begin pointer, 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 .git is unchanged. The gitops merge slot and the validation evidence cache stay shared (ADR-051).
  • Fix: cplat pr wait ignores 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 cancelled ci-success of the older run no longer ends the wait red.
  • Chore: devbox gates in the platform repo. devbox run test-fast, test, quality, ci and lint-fix now exist here, so the /svc:ship and /svc:build gates and the ship Stop hook find their scripts. No action for existing repos.

Action for existing repos:

  1. Run /plugin marketplace update (shared 0.17.0; svc, gitops, app, web, svc-go, svc-java unchanged).
  2. Nothing else is required: no command, flag, output format or file schema changed.
  3. cplat fleet update brings repos to 4.8.0 when wanted.

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 route label.
  • 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:

  1. Run /plugin marketplace update (svc 3.6.0, shared 0.16.0, gitops 1.6.0; app, web, svc-go, svc-java unchanged).
  2. Before /shared:update-service: in a repo whose lock file is out of date, run uv lock (Python) or pnpm install (web) once, since images now install strictly.
  3. Run /shared:update-service in every service repo (or cplat fleet update). Until it lands, release tags rebuild the image, its digest differs from staging, and cplat promote … staging prod refuses without --accept-digest-mismatch "<reason>".
  4. Dashboards that used the metric label endpoint must use route.
  5. Set the product’s kube context once (export CPLAT_KUBE_CONTEXT=<context> or kube_context: in the settings files) so unattended ships check Argo, smoke and the image digest.
  6. Optional: CPLAT_OTLP_ENDPOINT or telemetry: settings for live usage in Grafana; --budget KEY=VALUE|off to change a run’s limits.
  • Feat (101): run usage is exported live to OpenTelemetry (optional). guide cplat headless and cplat runs benchmark turn on Claude Code’s native OTLP export when CPLAT_OTLP_ENDPOINT or telemetry: {endpoint} is set, or the gitops-app has observability: {ui: lgtm} and a kube context is configured (telemetry: {context} / CPLAT_KUBE_CONTEXT; never guessed). Remote endpoints need allow_remote; prompt and tool content are always off; resource attributes carry the run id, kind, repo and platform version. The run record gets usage.otel (OTel totals vs transcript totals); reports are unchanged. The lgtm stack gets a Claude Code usage dashboard (rerun devbox 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 (or off) on cplat headless, ship start, app-ship start, runs begin; budget: in the settings files. A stop escalates with cause decision and is resumable; usage continues across resumes.
    • New shared-plugin PreToolUse hook on Task|Agent counts 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 resume lists the stages still verified for HEAD.
  • 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 (rules full, 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 in cplat runs report. Opt out with --full, validation: full or CPLAT_VALIDATION=full.
    • shapes.yml gains validation: {selector, mandatory}; cplat runs benchmark <wf> --compare-validation runs a task with and without selection.
  • Generated repos build strictly and record every request (104). The service-python Dockerfile installs with uv sync --locked and web-nextjs with pnpm install --frozen-lockfile: no fallback that re-resolves silently, and the lock file is required. Both Dockerfiles are template-owned, so /shared:update-service brings the strict install. Migration: a repo with an out-of-date lock must run uv lock (python) or pnpm install (web) and commit the lock before /shared:update-service, or its image build fails. service-python records every request in one MetricsMiddleware: the metric label endpoint is renamed route (the matched path template, unmatched for no route), so dashboards and alerts that used endpoint must follow; unhandled errors count as status 500 and in application_errors_total; /metrics is not recorded. With observability off, metrics.py, its test and prometheus-client are 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 is not-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 cause decision (digest changed since smoke). A not-run smoke prints NOT VERIFIED.
    • Red smokes are classed environment (URL unreachable or 502/503/504, every test failing) or application: environment failures are tried up to 3 times, 30 s apart, then escalate with cause environment and 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 flags digest differs from what staging runs.
    • New “Deploy gate” table in the unattended-delivery guide; a test fails when a row names a missing test.
  • Feat (106): prod runs the exact image staging tested. guide
    • Behaviour change: cplat promote … staging prod compares the registry digests of the staging sha-<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 a sha-<40> build. --dry-run shows both digests; --accept-digest-mismatch "<reason>" accepts a mismatch only; --no-verify prints image identity not verified. registry.digests pages through all package versions.
    • Migration: run /shared:update-service in every service repo. The CI of service-python, service-java, service-go and web-nextjs no longer rebuilds on v*.*.* tags; a new release-image job re-tags the tested sha-<40> image as X.Y.Z, X.Y and X (and fails if main has not published it, so wait for CI on main before tagging). Repos that are not updated keep rebuilding releases, and promote will refuse those releases as a mismatch.
  • 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 in scripts/cplat/benchmark/arms/). The reset after every run removes it. The record names the arm (baseline without one), and cplat runs report --benchmarks shows it in the row key. --recall runs 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 start switches to the feature branch itself. Before the merge it creates feature/<spec> from the current HEAD, or switches to the local or remote one, and prints which. A headless coordinator improvised git checkout -B …, which the template allowlist rightly refuses because it resets a branch, and a ship benchmark escalated in phase plan. ship.md now says never to create, reset or switch branches by hand.
  • Fix (090): the weekly retro workflow is valid. cplat-retro.yml set RETRO_DIR from ${{ runner.temp }} in the job env:, where GitHub does not allow the runner context, 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/retro now, and CI runs actionlint over scripts/cplat/assets/workflows/. Store repos set up before this fix: replace .github/workflows/cplat-retro.yml there with the new asset (cplat runs init leaves an existing file alone).
  • Fix (091): cplat ship deploy uses 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 --context or a product link, deploy now takes CPLAT_KUBE_CONTEXT, then kube_context: in .platform-settings.yml, then the user settings file, and prints which one. It never guesses from kubectl.
  • Fix (099): cplat runs report --benchmarks separates coordinator models and failed runs. Each row is one workflow, platform version and coordinator model (unknown for 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 row coordinator_model, done and not_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 cplat runs from, and the marketplace clone is shallow and has no tags. The baseline was silently missing, so cplat fleet update and /shared:update-service listed 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 carry pragma: allowlist secret now, and a test renders every template and runs its secrets-scan. Repos updated from before 4.6.0 still run devbox run secrets-baseline once (their baseline has no filters, see 4.6.0).
  • Fix: cplat triage apply changes only labels that differ. Removing a label the repo lacks (--remove triage before setup-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 to cplat triage setup-labels. triage list names missing labels (missing_labels in --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’s task_version goes 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 from docs/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.

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 open commits, pushes and opens the PR in one step, agents start from a compact brief, cplat quiet keeps 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 ship runs the spec’s smoke tests against dev and, with promotion: {staging: auto}, promotes a green build to staging and smoke-tests it there; prod stays manual with an evidence summary.
  • Whole products (097): /app:ship ships 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 wait judges only the PR’s current head; the benchmark checks folder trust and its result.

Action for existing repos:

  1. 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).
  2. Run /shared:update-service in every repo (or cplat fleet update), gitops-apps first. It brings the dev smoke recipes, pin-staging.yml and the wider git commit * allowlist.
  3. Optional: add promotion: {staging: auto} to a gitops-app’s app.yaml for automatic staging; set coordinator_model if a workflow should keep Opus.
  4. Run store: export CPLAT_RUNS_REPO=<owner/name> (a private repo, cplat runs init); the weekly retro there needs CLAUDE_CODE_OAUTH_TOKEN, RETRO_GH_TOKEN and 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_model in .platform-settings.yml or ~/.config/sdlc-foundry/settings.yml, also per workflow). Architect, reviewer and product-manager keep their own model.
    • cplat pr open commits (-s), pushes and opens the PR in one step (idempotent; ends at a ready PR when gh pr create is 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-fast is -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>/plugins measures unreleased plugins (and their cplat).
    • 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 and cplat pr open. Details: docs/specs/100-leaner-runs-cheaper-coordinator-fewer/benchmarks.md.
    • Found on the way: build.md told the coordinator that push/PR need confirmation; testers wrote files through shell heredocs (now Write/Edit only).
  • Fix (079): cplat pr wait judges 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 --merge pins --match-head-commit.
  • Feat (097): /app:ship ships a whole product across repos. ADR-042 — A product ship coordinates one repo ship per component, gated per repo, with serialized gitops merges
    • cplat app-ship (state in the gitops-app’s git dir, never committed) coordinates one cplat ship per 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 DIR links a repo ship to the product run. A repo that escalates stops its dependents only. One product-ship run 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:build is 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} in app.yaml (or on a service in services.yaml), cplat ship deploy dispatches the gitops-app’s new pin-staging.yml, which opens a self-merging PR, then waits for Argo and runs the staging smoke (red: smoke-failure issue, cause code, no rollback). cplat promote … staging prod prints 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-service to get pin-staging.yml, then add promotion: {staging: auto} to app.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 --amend still 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 init adds 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, then cplat retro post checks every proposal against the report’s evidence (an unsupported one is refused and counted) and posts.
    • The posted retro issue in ika100/sdlc-foundry has counts only (no repo, spec or store names, reasons as reason 1); the issue of an earlier week is commented on and closed. The full report is committed under retros/ in the store.
    • Action: set the store repo secrets CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token) and RETRO_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), run gh auth refresh -h github.com -s workflow, then cplat runs init <owner/name> again. cplat doctor warns 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 has devbox run smoke (SMOKE_BASE_URL, one output contract), web repos get the standard Playwright scaffold (needs_e2e is ignored), and cplat smoke run runs it by hand. cplat ship deploy runs smoke against the gitops-app’s dev URL (one retry after 30 s, 600 s limit); red smoke escalates with cause code, files a smoke-failure issue in the service repo and resumes smoke-only; the run record gets a smoke section (ADR-040 — Smoke tests run against a deployed environment through one recipe and one output contract). Migration: run /shared:update-service, then cplat triage setup-labels if the smoke-failure label is missing.
  • Fix (094): cplat spec task-done no longer rewrites plan.md. It sets done: true on 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 patch keeps 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 (todo no longer matches inside todo-api or mytodo). The gh error mapping now has a test.
  • Fix (087): cplat runs report has an upper bound. Records ended after the window’s until (future-dated) no longer count.
  • Fix: skeleton updates keep the repo’s secrets baseline. .secrets.baseline.json records a repo’s own false positives; /shared:update-service replaced 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 -m was missing (the other flag orders were there), and Read(~/.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:build in the 099 benchmark. A test now checks every template.
  • Fix (099): the build benchmark plans first. /svc:build needs a plan.md, so cplat runs benchmark build runs /svc:plan 900 and then /svc:build 900 as two phases of one run (task_version 3); a failed step stops the run and result_check names it.
  • Fix: cplat update-service refreshes devbox.lock. After merging devbox.json it runs devbox install, so the lock matches and the next devbox run no longer dirties the tree (a headless /svc:quick-task stopped 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 --context after the subcommand). /shared:update-service removes the old rules from each repo.

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 in cplat runs report, and cplat runs benchmark against ika100/todo-api for a repeatable baseline.
  • More autonomy (094–096): a merge conflict re-plans, fixable CVEs are patched by the run, cplat fleet update rolls 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 (in dontAsk they could edit nothing); /svc:quick-task, /svc:fix-bug and /svc:release no longer stop before the PR.

Action for existing repos:

  1. Run /plugin marketplace update (svc 3.4.0, shared 0.14.0, app 1.0.2).
  2. Run /shared:update-service in every repo (or cplat fleet update), gitops-apps first.
  3. Optional: cplat runs init <owner/name> with a private repo to keep run records; until then they are buffered locally.
  4. Claude Code 2.1.132 or later is needed for usage per run ($CLAUDE_CODE_SESSION_ID).
  • Fix: /svc:quick-task, /svc:fix-bug and /svc:release no longer claim that push and gh pr create need 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 headless runs claude -p --permission-mode acceptEdits instead of dontAsk. No allowlist grants Edit or Write, so every unattended session (headless, /svc:ship via 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 is done only 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 usage section (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:build states; a ship or build record carries gates.
    • cplat runs report adds a usage view 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 repo ika100/todo-api; cplat runs report --benchmarks compares workflows per platform version.
  • Feat (094): a merge conflict re-plans instead of escalating.
    • cplat build merge aborts a conflicting merge and sets the task aside; cplat build replan-commit accepts the architect’s amendment only when it changes files / depends_on / parallel_safe of 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 platform and a crash of any cplat command inside a recorded run file the failure in ika100/sdlc-foundry. Causes code, decision and environment are only recorded; a missing or unknown cause becomes unknown.
    • Each issue carries one Signature: platform:<16 hex> line and the label auto-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 after wontfix). 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=off sends nothing and prints the draft; CPLAT_FEEDBACK_REPO=<owner/name> files elsewhere.
    • Action: run cplat triage setup-labels once to create auto-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.yml in a gitops-app runs cplat alerts poll every 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=true shows what would happen.
    • Rules go under the observability addon in app.yaml (rules, alerts, queryURL, querySecret, alertmanagerURL; devbox run quality validates them); ServiceDown is built in.
    • Issues follow the 088 convention with an env:<env> label in the service’s repo (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 after wontfix.
    • 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 doctor warns when it is missing. Run /shared:update-service in gitops-app repos.
  • Feat (089): agent quality is measured per agent, shape and model.
    • A build record gets agents and replans sections (fix rounds, red CI, review findings, refused test diffs, out-of-plan files, merge conflicts, tester misses), recorded by cplat ship land, cplat spec test-diff and the new cplat runs agent calls 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 as n/a, not 0.
    • Recording never stops a build: a failed or malformed call only marks the record’s agent metrics incomplete.
  • Feature (096): platform updates roll out to every repo. cplat fleet update / /shared:fleet brings 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 lost is empty), the major version is unchanged, there is no conflict and CI is green. Every other PR stays open with the label platform-update: needs review and a comment giving the reason.
    • --dry-run shows what would happen, --only <owner/name> limits the run, and each run leaves a fleet record in the run store.
  • 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-extra recipe, which devbox run ci and a ci-extra CI check run; the update keeps your .claude/settings.json permission entries, .claude/project-notes.md holds project guidance, and the update report lists every customisation it overwrote (lost in --json).
    • Action for existing repos: run /shared:update-service. Gates you had in ci move to ci-extra automatically.
  • Feature (098): gitops CI checks Components against the service branch’s CRDs. (#110)
    • cplat ship gitops pr adds one Service-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 doctor warns when a gitops-app lacks the SERVICE_REPOS_READ_TOKEN secret.
    • Action: add the secret SERVICE_REPOS_READ_TOKEN (fine-grained, Contents: read on the service repos) to every gitops-app, then run /shared:update-service there.
  • 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 wait and cplat headless run leaves one JSON record: phases, CI rounds, reruns, escalation cause, outcome. Secrets are redacted.
    • cplat runs init <owner/name> creates the private repo; then export CPLAT_RUNS_REPO=<owner/name> in your shell profile. cplat runs flush writes the buffer, cplat runs report [--json] shows durations, outcomes and causes.
    • cplat doctor has a run store check: 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.
  • 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-findings and deps-bump (/shared:update-service adds them). cplat deps findings|patch|report bumps 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 patches section.
  • Fix (079): a registry rate limit is transient. cplat pr wait and cplat ship land rerun a job whose log says invalid response status 429 once before it counts.

Deploys report what really runs, every PR wait is one tested command, and builds and CI do each thing once.

  • cplat ship deploy fails at once on a red main CI, a failed pin-dev run or a missing GITOPS_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 wait waits for any PR and reruns stuck or transiently failed jobs once.
  • Leaner flow: /svc:build runs the CI gate once, and cplat headless stops after two idle sessions.
  • Real gates: gate tools are pinned, cplat doctor checks the gates, and sessions claim the issues they work on.
  • Visible drift: /shared:status shows each repo’s platform version.
  • Faster platform CI: platform tests run in about 30 s.

Action for existing repos:

  1. Run /plugin marketplace update (svc 3.3.0, shared 0.13.0, gitops 1.4.2, svc-go/svc-java/web 0.2.6).
  2. Run /shared:update-service in every repo. Service repos then dispatch sha-<40>. Update gitops-apps first, so their pin-dev accepts the full sha and names its run.
  3. Run cplat doctor once per repo: the new gates section names anything to fix.
  • Fix (077): cplat ship deploy reports 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>-dev runs the merged image (status.summary.images). The apps named by gitops tasks count once they reached the merged gitops commit.
    • cplat ship start and cplat doctor refuse a linked repo without GITOPS_TOKEN, and the template’s pin-dev job now warns instead of a notice.
  • 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>, plus sha-<7> for one more release, and asks pin-dev for sha-<40>.
    • pin-dev.yml and pin.py accept both formats; the pin branch keeps the short sha.
    • /gitops:promote pins sha-<40>, or sha-<7> for images built before.
  • 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 land does the same. (#117)
  • Feat (080): leaner builds and unattended runs. (#118, #119)
    • /svc:build Phase 3 runs devbox run lint-fix and devbox run ci once 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 headless stops 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.py bumps them (minor/patch) in its weekly PR.
    • cplat doctor gets a gates section: the secrets baseline has its filters, scanners are pinned, ci and secrets-baseline exist, ci chains the CI workflow’s gates, and CI jobs run the template gates.
    • cplat adopt lists the template gates the repo’s CI lacks.
  • Feat (082): sessions do not collide. (#121, #122)
    • cplat spec new --tracks and cplat ship start claim each tracked issue: an in-progress label, the user as assignee, and a comment naming the branch. An issue claimed for another branch stops them (--take-over moves the claim); an escalation releases it.
    • The platform CI requires a spec (or the no-spec label) for a PR that closes an issue and changes scripts/, plugins/ or templates/.
  • Feat (083): /shared:status shows 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/cplat runs 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.

Ship runs lose minutes, not an hour, to CI, and they carry their gitops changes.

  • cplat ship land stops at the first red check and reruns a stale or stuck run once.
  • devbox run ci runs CI’s gates locally, and ship pr refuses to push while it is red.
  • CI runs once per change (push only on main and 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:

  1. Run /plugin marketplace update (svc 3.2.0, shared 0.12.1).
  2. Run /shared:update-service in every repo.
  3. Run devbox run secrets-baseline, audit what it adds, and commit the baseline. It is project-owned, so the update does not replace it.
  4. Repos with extra CI jobs add them to their ci script.
  • Templates: fewer permission prompts. The generated .claude/settings.json allows 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 old git 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 local docker 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 a ci script with the gates its CI workflow runs (quality + test + security; gitops-apps: validate + security), and shapes.py requires it. /svc:build Phase 3 ends with one devbox run ci on the staged tree, and /svc:ship runs it before ship pr and 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 their ci script.
  • Templates: CI runs once per change (#104). ci.yml in every template triggers push only on main (and v*.*.* tags). Feature, fix and other branches run CI through pull_request alone; 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 through workflow_call. Action: /shared:update-service.
  • CI: template pin bumps no longer get stranded. bump-template-pins opens its pull request with a GitHub App token (repository variable BUMP_APP_CLIENT_ID, secret BUMP_APP_PRIVATE_KEY; BUMP_TOKEN still 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.md is now on main and linked from ADR-020; rendered manifests stay the only addon backend.
  • Feat (072): cplat ship land reports 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 deploy applies 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.0 and trivy@0.74.0.
    • The template baselines list the standard filters. Before, filters_used: [] turned them all off, so # pragma: allowlist secret had no effect. They no longer carry the gibberish filter, which only some machines can apply.
    • New recipe devbox run secrets-baseline refreshes the baseline with fixed flags, keeping audits. It regenerates a baseline without the standard filters (or a missing one) from scratch first.
    • secrets-scan passes 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, then devbox 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)
  • Docs (074): a persistent Docker volume is the build cache on self-hosted runners. docs/self-hosted-runners.md recommends one dind /var/lib/docker volume 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 pr enforces devbox 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) and human_steps; cplat spec check validates them.
    • cplat ship gitops prepare|pr <task> opens each task as a draft PR during the build.
    • ship land merges the before-merge PRs before the service PR.
    • ship deploy merges the after-release PRs 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)

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.releaseName to the service name (external.helm.releaseName overrides 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 with spec.selector: field is immutable. Action: /shared:update-service in gitops-apps with a helm external entry, then uv run scripts/render.py.

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 until dev runs it.
  • cplat headless keeps 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 external entry type for services the generator cannot express (CronJobs, volumes, Helm charts);
    • cplat adopt for repos that were not created from a template;
    • self-hosted CI runners set through repository variables.

Action for existing products:

  1. Run /plugin marketplace update.
  2. Run /shared:update-service in the gitops-app (pin-dev merge job, external entries) and in every service (CI runner variables).
  3. 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.
  4. Open each repo interactively once before its first cplat headless run.

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-dev runs ci.yml (now also a reusable workflow with a ref input) 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-service says what still guards merges on the free plan. Action: /shared:update-service in each gitops-app.

  • 066 — unattended runs stop only when done. A Stop hook in the shared plugin keeps an unfinished /svc:ship run 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 Argo Synced/Healthy). Resumable from the state in the git directory.

  • 068 — done means verified. cplat spec set-status <id> done needs verification.md with **Result:** pass (product specs excepted); --unverified --reason records an exception; /svc:specs shows /svc:verify for done specs without evidence.

  • 069 — external services (ADR-029: External services in services.yaml). A services.yaml entry with external: {kustomize: <path>} or external: {helm: {repoURL, path, revision, imageTag}} is wired into the environments and pinned like any service, so pin-dev, /gitops:promote and /shared:status cover services with CronJobs, volumes or charts. Action: /shared:update-service in each gitops-app (render.py, pin.py).

  • 070 — cplat headless "<prompt>". Fresh claude -p --permission-mode dontAsk sessions until a ship run is done or escalated, --quiet HH:MM-HH:MM, a log, summary.log and 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-service works in a git worktree (it refused a .git file).

  • 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 service docker jobs take their architecture matrix from vars.CI_DOCKER_MATRIX when it is set. Nothing changes for repositories without the variables. To move a repository to self-hosted runners: /shared:update-service, then gh variable set CI_RUNNER --body <runner label> (and CI_DOCKER_MATRIX for service shapes). On self-hosted runners the docker job 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.

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.json recipes are no longer dropped.
  • Plans finished before v4 pass the checks again.
  • dev follows main. Every merge opens a pin PR to sha-<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-diff on 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.md is project-owned in every template (_skip_if_exists, checked by shapes.py); new repos still get it. /shared:update-service merges devbox.json instead 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 draft and in_progress plans; completed and abandoned ones 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-dev job that asks the gitops-app (repository_dispatch, secret GITOPS_TOKEN) to pin dev to sha-<7>. The gitops-app’s new pin-dev workflow and scripts/pin.py open a pin PR that merges itself after CI. New gitops repos get auto-merge, new services print the token step, /shared:update-service names it for existing ones, and /shared:status shows open pin PRs. Action for existing products: update the gitops-app and every service, then set GITOPS_TOKEN in each service repo; until then dev stays on its last pin and CI prints a notice.
    • 063 — cplat spec test-diff only looks at test files (the shape’s test_globs, never docs/ or *.md), so plan.md changes from task-done no longer fail it; /svc:build forbids treating a failure as a false alarm.
  • 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.py rejects completed pre-v4 plans, skeleton updates drop project devbox.json recipes (like the README, spec 060), dev never deploys new latest images, and cplat spec test-diff flags plan.md.

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 offers set <service> [--env K=V] [--expose [HOST]] [--uses ADDON] [--replicas N] (until now only /app:build could call cplat compose set). README, HOW-IT-WORKS, ADOPTING, USER-JOURNEY and AGENTS.md describe branch protection of new repos, the specs CI check, cplat spec test-diff and 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-app and the spec-first product flow, and the end-to-end run log is published under Reference. Leftover “prefix” wording from the cplat launcher change removed from three commands.

  • Fixes from the todo end-to-end run, waves 4–5 (specs 051, 052): when gh pr create is not allowed (headless, untrusted workspace, declined), /svc:build, /svc:quick-task, /svc:fix-bug and /app:build push the branch, write .git/PR_BODY.md and end with the exact gh pr create command, 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:build asks only for wiring that is missing (052). All 17 issues of the run are fixed (specs 043–059 done).

  • 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:build runs it after every lint-fix and stops only on a real change, so lint no longer blocks a build and tests are never quietly weakened (048). /app:plan pushes the spec branch and opens its PR, and /app:build warns when the plan on origin/main differs from the local one (050).

  • Fixes from the todo end-to-end run, wave 2 (specs 046, 047, 053, 054): /app:build runs its repo agents as foreground calls, so headless runs no longer lose them after 600 s, and docs/ADOPTING.md has an Unattended runs section (046). /svc:build resumes an interrupted build: stale staged files are unstaged, the leftover work is committed as wip(<task>): interrupted and redone, and the PR asks for a squash merge (047). plan-check requires ### Errors and ### Timeouts in the contract of every multi-repo plan, so repo specs stop asking the same questions (053). /shared:new-service and /shared:new-app protect main with the shape’s CI checks (new ci_checks per shape in shapes.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.py pass the template’s own ruff rules (generated repos were red on main); platform CI now lints, type-checks and tests the rendered Python templates (043). Python templates exclude docs/ from ruff, so spec code examples never fail CI (049). Every template sets worktree.baseRef: "head" so parallel coders start from the feature branch; shapes.py check requires it (044). Template workflows use the platform’s action pins, enforced by pin-actions.py --check, and one Dependabot group updates all of them (055). cluster-up moves to the next free port when 8088 is taken (056). spec-check says 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 structured gitops: operations validated by plan-check, and /app:build runs them itself and marks the PR merge first (045, ADR-011 amended).
    • Specs: criteria stay environment-neutral (056); the spec-format skill is hidden from the / menu (058); cplat spec trace no longer counts __pycache__ files as tests (found while verifying).
  • 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 main instead of the feature branch, and /app:build has no route for the gitops-app repo entry the planner adds.

  • cplat on 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 ships bin/cplat, so the ~300-character fetch-and-clone line is gone from all 24 commands. cplat runs the platform script from the installed marketplace checkout, i.e. the version your plugins came from, without network; CPLAT_PLATFORM (local checkout) and CPLAT_REF (a tag, used by --ref) override it, and only a missing checkout falls back to fetching main. /shared:doctor reports which copy ran. claude.ai and Cowork do not install plugins with bin/; the platform targets the CLI and desktop app. This repo’s backlog was migrated with cplat spec migrate into docs/specs/001…042 (numbers kept, links rewritten, STORY-036 superseded); platform changes now start with /svc:spec. migrate maps 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 lists docs/specs/** as project-owned. A new warn-only specs workflow and devbox run spec-check run scripts/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 new cplat 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=1 turns warnings into a failure (planned default in the next major). shapes.py check requires 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 writes spec.md (acceptance criteria AC-<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 writes design.md (contract) and plan.md (tasks that cover criteria); refused for unapproved specs; cplat spec check validates 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 writes verification.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-format skill 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-task and /svc:fix-bug stop when a change would contradict a criterion.
    • app 1.0.0: /app:spec writes the product spec in the gitops-app repo; /app:plan has the planner assign every product criterion to a repo (acs: per repo, spec: in the plan, ADR-011 amended; plan-check refuses an unassigned criterion) with the contract between them; /app:build builds every ready repo in parallel, each through its own spec slice; /app:specs tracks progress. cplat spec new --from-plan <plan> <repo> writes that slice deterministically (product problem, assigned criteria verbatim, contract, parent:). /shared:triage folds features into specs (Tracked as spec <id>). Bootstrap next steps, templates’ CLAUDE.md and the seeded docs/backlog.md (now a generated spec index) follow.
    • docs/AGENTS.md fixed (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] (start happens in /app:build)
    multi-repo plan with free-text arguments per repo spec: + acs: per repo (old plans stay valid)
    STORY-NNN in docs/backlog.md, plans in docs/plan/ docs/specs/<NNN>-<slug>/; /svc:specs migrate converts existing stories (numbers kept)

    Update the plugins (/plugin marketplace update), then in each repo run /shared:update-service for the new CLAUDE.md rule. Repos with stories in docs/backlog.md run /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 in docs/specs/<NNN>-<slug>/ (spec.md, plan.md, …) with acceptance criteria AC-<NNN>.<n>. new, check [--require STATUS] (criteria ids, shape, plan coverage, cycles, parallel file overlap, drift via spec_hash), approve (refused with open questions), set-status, task-done, hash, trace (every criterion named by a test, via the new test_globs per shape in shapes.yml), index (table in docs/backlog.md), list, migrate (legacy STORY-NNN stories → spec folders, dry run by default). No command uses it yet; the renamed /svc:spec|plan|build|verify commands 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 become STORY-NNN with Tracks: #N (and a Tracked as STORY-NNN comment), 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 (triage label, required fields); shapes.py check requires 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-service and /shared:new-app start 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 are STORY-NNN, plans list stories:, the PR names them. Every template seeds docs/backlog.md and docs/plan/ (project-owned) and a ### Spec first section in CLAUDE.md; shapes.py check requires both. Existing repos get the CLAUDE.md rule 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, optional org/visibility, components with name, description, shape, optional data), then opens one compose add pull 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 and gh repo view checks run before anything is created (also in --dry-run), a failure midway lists the repos that exist and --resume continues; nothing is rolled back. Replaces the 6 new-service + 4 compose commands of the end-to-end scenario.

Addresses issue #56 (“Improve performance”) and the leftovers of the project-management sample.

  • Work starts right away. new-service next steps, /shared:new-service, /svc:build-feature and the user journey now say that development starts on a feature branch immediately; nothing waits for the bootstrap CI on main (#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 ready computes the wave; the planner writes a ## Contract section so a backend and its UI can start together (#56, idea 1).

  • Template pins stay current. Weekly bump-template-pins workflow and scripts/bump_template_pins.py: renders service-java, web-nextjs and service-go, bumps the rendered manifest (minor/patch), copies literal unique line changes back into the Jinja source and re-renders to verify. Needs a BUMP_TOKEN secret for CI to start on the bot’s PR (#56, idea 2).

  • k9s in the gitops project: k9s in the devbox and devbox 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: k9s is part of the gitops-app devbox and devbox run cluster-ui opens it on the local cluster in the <app>-dev namespace (clear error with the fix when the cluster is not running). cluster-up mentions it when it finishes. Existing projects get it with /shared:update-service.

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-title check 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.yml reads the platform’s PG* variables (the postgres addon injects them; defaults match devbox 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-up recipe, 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 example needs_observability=false); the keys are validated against the template’s own copier.yml and an unknown one lists the valid options. Before, options could only be changed afterwards with update-service --data.
  • /gitops:compose add <svc> --secret-ref NAME: expose an existing Secret (for example the one generated for another service) to a service. secretRefs was previously impossible to set from the CLI.
  • devbox run cluster-up checks the host ports first: a busy LOCAL_HTTP_PORT (or REGISTRY_PORT) stops the script before anything is created, names the program that holds the port and suggests a free one; LOCAL_HTTP_PORT=auto picks the first free port from 8088.
  • Plugins: shared 0.7.3, gitops 1.3.3.
  • 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 set TRIVY_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-service and merge, then check that the next main build publishes the manifest.
  • web-nextjs: package.json is now project-owned (_skip_if_exists), like pom.xml, go.mod and pyproject.toml in the other templates. Until now /shared:update-service overwrote 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, check git diff HEAD~1 -- package.json on 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.
  • /shared:update-service no longer adds starter files to project-owned paths. The templates mark app/**, 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 a tests/layout.test.tsx that 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.

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).

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 them

then 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): repository ika100/sdlc-foundry, documentation at https://ika100.github.io/sdlc-foundry/, and the plugin marketplace id sdlc-foundry (was ika100-claude), so plugins install as shared@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-foundry for each plugin, restart Claude Code; in every generated repo run /shared:update-service (the template-owned .claude/settings.json and the _src_path in .copier-answers.yml are rewritten; a regression test covers a repository generated before the rename). /shared:doctor detects plugins still installed under the old marketplace and prints these steps.
  • DCO: every commit of a pull request needs a Signed-off-by trailer (git commit -s), enforced by the DCO sign-off CI job (scripts/check-dco.py, bots exempt). See CONTRIBUTING.
  • Dependabot’s grouped PR titles exceed the title limit, so the pr-title check 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.

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.

  • Plugins: claude plugin marketplace update ika100-claude, then claude plugin update <name>@ika100-claude for each installed plugin, then restart Claude Code (a running session keeps the old prompts). You get /shared:report-issue and 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-service for pull requests with the new skeleton: concurrency that cancels superseded PR runs, monthly grouped Dependabot, the org.opencontainers.image.source label in Dockerfiles (links the GHCR package to the repository), git init -b main for 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: main is protected and the single required status is ci-success; see CONTRIBUTING.md (new) and run scripts/ci-local.sh for the cheap checks.
  • 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/viewport metadata, 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.ts cleans up after each test (without it, repeated render() 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 -q could die of SIGPIPE).
  • 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 (site job, part of ci-success), published by pages.yml; Dependabot covers its npm dependencies.
  • docs/USER-JOURNEY.md refreshed: a chapter for secrets, Postgres, observability and guard rails, doctor/status/report-issue, and honest limits that match the tested-script design.
  • 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.md and LICENSES/ (Apache-2.0, CC-BY-4.0, ISC); the web template’s icon paths are adapted from Lucide, so icons.tsx now 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 lint in CI) so every file has machine-readable license and copyright data.
  • Repository protection: main requires a pull request and the ci-success status, 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-issue command now warns that issues on this repository are public (shared 0.7.1).
  • Platform CI only runs what a change needs: a changes job (plain git 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 final ci-success job 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 (concurrency in every ci.yml).
  • scripts/check-links.py (relative Markdown links, in CI) and scripts/ci-local.sh / devbox run ci-local (the cheap CI checks locally; --render also 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 through gh only after your OK (a prefilled browser link without gh). An unexpected cplat crash 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-up creates the pull secret, doctor checks read:packages). #4: copier tasks now git init -b main (the CLI already renamed to main). #5: generated Dockerfiles carry org.opencontainers.image.source (links the GHCR package to the repo, so CI can push) and ADOPTING has a Troubleshooting section. #8: /svc:quick-task PR body no longer lists an unchecked security box; it says what was not run.
  • web-nextjs dependabot.yml ignores major bumps of the node Docker image and @types/node (ADR-005: the Node LTS major is bumped deliberately; the bot proposed Node 25 and @types/node 26 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).

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.

  • Generated repos: run /shared:update-service (gitops-app: new render.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:compose gains --generate, --secret, --uses.
  • Cluster prerequisites only for features you turn on: External Secrets Operator (secrets:), CloudNativePG (addons.postgres), Kyverno (policies:); devbox run cluster-up installs them locally, real clusters need them installed by their owner. OpenTelemetry needs no operator.
  • Local credentials: cluster-up accepts REPO_TOKEN and PULL_TOKEN for 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: in app.yaml (opt-in) renders a namespaced CEL NamespacedValidatingPolicy per environment with the platform’s conventions (numeric non-root, read-only filesystem, no escalation, dropped capabilities, no privileged/host access, requests + memory limit, part-of label, allowed registries, no :latest outside dev). Audit in dev/staging and Enforce in prod by default; per environment Audit | 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-up installs Kyverno when policies: is declared (WITH_KYVERNO overrides); doctor checks it. e2e runs the whole stack under Enforce and 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.observability renders an OpenTelemetry Collector per environment (OTLP in, a Prometheus scrape job per service with a metrics: path, optional OTLP/HTTP export via exportTo + headersSecret) and injects the standard OTEL_* variables into every service. No operator, no CRDs; works with any OTLP backend.
  • ui: lgtm (optional): cluster-up installs Grafana, Tempo, Loki and Prometheus as a shared dev stack at http://grafana.localhost:8088. Dev/demo only.
  • shapes.yml runtime gains metrics; compose add writes it into services.yaml. service-python now configures tracing from the standard OTel variables (legacy OTLP_ENDPOINT still works) and ships tracing tests; run /shared:update-service in Python services.
  • /gitops:addon add observability [--ui lgtm | --export-to URL]; doctor and status know 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: in app.yaml + uses: [postgres] on a service: a CloudNativePG Cluster per environment (sizing as scalars or per-env maps) and DATABASE_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; doctor checks the operator, status shows the addon’s Argo health. Removing an addon never deletes the data (prune: false).
  • cluster-up installs CloudNativePG when addons.postgres is declared (WITH_CNPG=0/1 overrides). e2e proves a real connection with the generated credentials.
  • gitops plugin 1.2.0.
  • All GitHub Actions pinned by commit SHA (91 references, platform and templates) with scripts/pin-actions.py; --check runs 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: read everywhere; packages: write only 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-up credential split: REPO_TOKEN (Contents: read) for ArgoCD and PULL_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: in services.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.yaml secretStore). Rendered as ExternalSecret (+ Password generator) and wired into the pod via envFrom. Secret values never enter git; secret-looking keys with a value in env: are rejected.
  • devbox run cluster-up installs ESO (k3s HelmChart, chart 2.12.0) and a platform-secrets store over the secrets-store namespace; WITH_ESO=0 skips it.
  • cplat compose add … --generate NAME=KEY,KEY --secret NAME=KEY and /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 check now enforces valid front matter and a 260-character cap, and fixes seven descriptions that were not valid YAML (unquoted : ).
  • Model tiering: product-manager runs on sonnet (structured story writing); architect and the app planner stay on opus (decomposition and design decisions).
  • /svc:build-feature --no-pm skips the product-manager phase for precise requests.

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.

  • Service templates ship no k8s/, no k3d/kubectl/k9s, no deploy/deploy-check, no owner_label. shapes.py check forbids k8s/ in them.
  • gitops-app generates manifests (deployment.yaml, service.yaml, httproute.yaml, kustomization.yaml) from a richer services.yaml entry (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 dev only; /gitops:promote adds staging, then prod. Promotion pins an image tag only (staging sha-<7>, prod X.Y.Z), verified in GHCR.
  • /gitops:compose / /gitops:promote are 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 deployment agents own the image only; /svc:build-feature Phase 5 is “Container image”.
  • Gateway API exposure: expose: → HTTPRoute; app.yaml holds gateway and per-env hostname templates (dev default {service}.{app}-dev.localhost). cluster-up installs 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; *.localhost resolves everywhere (localtest.me / nip.io / lvh.me do not behind rebind-protecting DNS).
  • cplat compose / cplat promote with 21 more tests (fake gh, real renders); compose add --from-k8s seeds an entry from a v1 service; update-service --migrate removes k8s/.
  • shapes.yml runtime: defaults per deployable shape (validated by shapes.py check).
  • docs/adr/017-gitops-owns-manifests.md; CI renders a fixture product and runs the label check, kubectl kustomize and kubeconform on the generated manifests; offline validate in generated gitops repos.
  • 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 own local-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 by cluster-up raced with k3s’ own traefik-crd Helm chart (which installs them) — cluster-up now only enables the provider.
  • local-cluster.sh: WITH_ARGO=0 (Gateway + namespaces only) and REGISTRY_PORT (local registry) modes.
  • /shared:status (cplat status): one table per product — pinned tag per environment, CI state of each service’s main, ArgoCD sync/health (--context).
  • docs/HOW-IT-WORKS.md: diagrams of bootstrap, build, compose/render, promote and exposure, and which test guards what.
  • Native multi-arch image builds: the docker job is a matrix (amd64 on ubuntu-latest, arm64 on ubuntu-24.04-arm, verified available for private repos) that pushes by digest; docker-publish merges 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.yml in every service template (language ecosystem, Actions, Docker).
  • actionlint runs over the platform’s workflows and every generated repo’s workflows in CI (it caught a corrupted expression on its first run). update-service picks a unique branch name when one with today’s date exists.

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).

  • 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 in tests/cplat (CI job platform-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, gh scopes, Docker, kube context (warns on non-local), plugin versions (with the “restart Claude Code” hint), repo platform version.
  • .platform-version stamp in generated repos; update-service reports the changelog range and lists overwritten skeleton files.
  • docs/BASELINES.md: the numbers v2 is measured against.
  • /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 call cplat shape, which prints the shape and the agent routing as JSON; the two fragment files are removed.

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.
  • gitops-app: devbox run bootstrap now requires KUBE_CONTEXT=<context> and prints it; before, it applied to whichever kube context was current.
  • web-nextjs: bootstrap failed (devbox run -- pnpm install → SyntaxError) because packageManager: pnpm@12.9.1 was newer than the pnpm devbox provides (11.x). Both pins are now pnpm@11.22.0 and shapes.py check enforces that they match (ADR-003 amendment).
  • All deployable templates: Dockerfiles ran as a named user (nonroot, app), and Kubernetes cannot verify runAsNonRoot: true for a name, so pods stayed in CreateContainerConfigError (“image has non-numeric user (nonroot)”). All images now use a numeric USER; shapes.py check enforces it. Existing repos get this through /shared:update-service (the Dockerfile is a skeleton file).
  • service-java: the image never started: ADD created 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/amd64 only, so Apple-silicon k3d/Docker nodes failed with “no match for platform in manifest”. The docker job now publishes linux/amd64,linux/arm64 (QEMU) on main/tags; PR builds stay amd64. The Go and Java builder stages run on the native platform (--platform=$BUILDPLATFORM; Go cross-compiles with GOOS/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 used newTag: vX.Y.Z although CI publishes semver images as X.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 computed owner_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.yaml and k8s/monitoring/alerts.yaml are project-owned — change team: "@x" to team: "x" by hand.
  • All templates: the generated CI workflow did not trigger on pushes to main, so merged code was never built and no latest/sha-* image was pushed (the GitOps flow depends on them). main is now in the push triggers and shapes.py check enforces it.
  • All deployable templates: k8s/base/deployment.yaml is now project-owned (_skip_if_exists). It is the file every app edits (env vars such as API_URL, resources), and /shared:update-service silently reverted those edits. shapes.py check enforces it. Existing repos: nothing to do, the next update simply stops touching the file.
  • gitops-app: CI could not kustomize build overlays whose remote bases are private service repos (GITHUB_TOKEN is repo-scoped). The quality job now uses an optional SERVICE_REPOS_TOKEN secret for the full check and otherwise runs the new validate-static recipe with a warning.
  • service-java: devbox run audit aborted in CI when NVD_API_KEY was unset (empty key → “Invalid API Key”); it now falls back to trivy fs (after mvn 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, so pom.xml overrides the vulnerable BOM-managed Tomcat/Jackson versions and .trivyignore records the one CVE with no fix in Spring 6.x (ADR-009 amendment).
  • gitops-app: the bootstrap did not run devbox install, so devbox.lock was left untracked after the first devbox run.

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, all status: stable).
  • scripts/detect-shape.sh — shape detection from .copier-answers.yml with sniffing fallback (ADR-008), plus scripts/test-detect-shape.sh fixtures.
  • scripts/shapes.py — validates shapes.yml and 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 check now 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 new docs/templates.md.
  • CI job validate-shapes; devbox run shapes-check and devbox run test-detect (included in devbox run smoke).
  • shared 0.5.0 (docs/UX pass): new /shared:update-service [--ref] [--data k=v] — copier update cannot 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-service now records a stable _src_path, no longer passes the Python-style module_name to non-Python templates (it had turned the Go module path into my_saas), and runs its clone+copier steps in one shell. All docs that pointed at copier update now 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-service gains --type <shape> (aliases --library, --web, --gitops); template, topic and --python handling are read from shapes.yml. With no flag it still produces a service-python repo. Requesting a planned shape fails with a clear message.

  • svc 1.1.0 (phase 2): every orchestration command resolves $SHAPE and routes coder/tester/deployment/observability/release to $SHAPE_PLUGIN:<role> via the new plugins/svc/fragments/shape-dispatch.md. The architect plan metadata gains a required shape: field (missing → service-python with a deprecation notice). /svc:build-feature gains --from-plan <path> [<repo-id>] (ADR-011) and skips deployment for non-deployable shapes. /svc:release dispatches to the shape’s release agent. /shared:check-quality and the quality/security agents are shape-neutral.

  • gitops 0.3.0 (phase 3): new compose agent and /gitops:compose add|remove <service...>; /gitops:promote works in gitops-app repos, from a service repo via .platform-app.yml, with batch/--all and one PR per invocation.

  • web 0.1.0 (new, phase 4): coder, tester, deployment, observability, release agents for Next.js repos, plus a SessionStart hook that runs devbox run install. The svc SessionStart hook now only runs uv sync when pyproject.toml exists.

  • app 0.1.0 (new, phase 5): /app:build-feature <desc> (PM stories per repo → planner agent → 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-plan no 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, release agents for Go and Spring Boot repos; releases follow ADR-012 (pom.xml via versions:set; Go has no version file — the git tag is the version).

  • service-go (new, phase 6): chi + slog, golangci-lint v2, govulncheck, optional Prometheus /metrics, distroless static image with ldflags version, pre-resolved go.mod/go.sum. Verified by running devbox run test/quality/audit on 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/quality on 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-only devbox run bootstrap recipe, so after one kubectl apply per cluster all changes deploy through merged PRs; validate also schema-checks it with kubeconform (ADR-014 amendment).
  • gitops-app gains scripts/plan.py (plan validation + lifecycle), devbox run plan-check (part of validate), and enables the app plugin.
  • web-nextjs (new, phase 4): Next.js 16 App Router, TypeScript strict, pnpm (packageManager pinned, pnpm-workspace.yaml with allowBuilds), Node 24 LTS pinned in package.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 by scripts/render.py, devbox recipes (render, validate = render-check + kustomize + kubeconform, security), CI, CLAUDE.md. See the ADR-013/014 amendments.
  • /shared:new-service gains --app <org>/<repo> (writes .platform-app.yml).
  • 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.
  • Run /plugin marketplace update. No copier update needed for existing Python repos (their templates are unchanged).
  • New repos: /shared:new-service <name> --web | --gitops | --type service-java | --type service-go.

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 invoke devbox install) are now skipped via --skip-tasks in .github/workflows/ci.yml; CI verifies template rendering only. The full render-and-bootstrap path remains covered by devbox run smoke locally. Broken since v0.2.1.

Phase 3 of the agentic efficiency overhaul. First stable major. Orchestration commands restructured for parallelism and to skip wasted work; per-command preludes deduplicated.

  • /svc:build-feature phase 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 for Phase 4a complete need updating.
  • /svc:fix-bug phase 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-task phase numbering changed. Old “Phase 4 — Commit and open PR” is now “Phase 3 — Commit and open PR”.
  • svc 1.0.0:
    • Scope classifier (/svc:build-feature Phase 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-feature Phase 4, /svc:quick-task Step 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.md documents 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.
  • Run /plugin marketplace update to pull svc 1.0.0.
  • Existing service repos do not need a copier update for 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.

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.

  • service-python: new pre-rendered observability files under src/{{ module_name }}/:
    • tracing.py — OpenTelemetry OTLP exporter wired via OTLP_ENDPOINT env var.
    • main.py now exposes /metrics (Prometheus generate_latest) and conditionally calls configure_tracing().
    • k8s/monitoring/alerts.yaml — default PrometheusRule with HighErrorRate, HighLatencyP95, PodRestarting.
    • docs/env-vars.md — documents LOG_LEVEL, OTLP_ENDPOINT, METRICS_PORT, and DATABASE_URL when applicable.
  • service-python: new pre-rendered Alembic scaffolding (gated by needs_migrations):
    • alembic.ini with script_location = migrations.
    • migrations/env.py that requires DATABASE_URL from env and leaves a TODO for wiring target_metadata.
    • migrations/script.py.mako with typed upgrade() / downgrade() signatures.
  • service-python copier.yml: new _exclude block skips alembic.ini and migrations/ when needs_migrations=false. _skip_if_exists extended with docs/env-vars.md and k8s/monitoring/** so they’re project-owned after bootstrap.
  • 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’s Dockerfile + .github/workflows/ci.yml instead of restating the YAML; verifies CI integrity and the dry-run gate.
    • migrations.md: 139 → 77 lines. Skips the alembic init boilerplate (template handles it); focuses on wiring target_metadata, generating + verifying migrations, and maintaining the runbook.

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.

  • shared 0.3.0: new hooks/hooks.json with a SessionStart hook. Checks devbox is on PATH (prints install URL if missing, exits 0 — non-blocking) and runs devbox install if a devbox.json is present. Makes any consumer repo that uses the shared plugin (library repos, GitOps repos, the platform repo) devbox-aware out of the box.
  • gitops 0.2.0: same new hooks/hooks.json as shared. GitOps repos that add a devbox.json (for kubectl, argocd, kustomize) get automatic sync; older GitOps repos see a no-op.
  • svc 0.1.3: existing SessionStart hook hardened with the same command -v devbox host-check, plus a [ -f devbox.json ] guard so it no-ops cleanly in non-devbox directories.
  • New root devbox.json: pins jq, python@3.13, uv, git, gh, and init_hook-installs copier (invoked at runtime via uv 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 individual devbox run lint + devbox run typecheck instead of the chained devbox run quality recipe — devbox 0.17.2 has a wrapper bug that mis-evaluates the chained recipe even though both children succeed; tracked separately.
  • CLAUDE.md smoke-test section rewritten to use devbox run validate / smoke-service / smoke-library instead of host-installed jq + copier. Only host requirement is devbox itself.
  • docs/AGENTS.md documents the new SessionStart hooks under the golden-rule section.

Bugfix release. Resolves the two issues raised after the /shared:new-service UX overhaul (#1, #2).

  • service-python + library-python (#1): _tasks in copier.yml now runs devbox install and devbox run -- uv sync --all-extras to materialize devbox.lock and uv.lock, then makes an initial chore: bootstrap commit. A fresh copier copy … now leaves git status clean instead of two lockfiles untracked. Requires devbox on PATH at template-render time (already a prerequisite for all other recipes in the template).
  • service-python (#2): tightened the docker job’s login and build-push-action conditions from github.event_name != 'pull_request' to github.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’s CLAUDE.md (and avoiding the 403 from a brand-new repo’s GITHUB_TOKEN lacking packages: write).
  • shared 0.2.1: /shared:new-service Phase 3 rewritten for the new copier-side commit. Instead of git add -A && git commit, it now git commit --amend --reset-authors the bootstrap commit produced by _tasks so the user owns it with the richer /shared:new-service commit message. Falls back to the legacy add && commit path if _tasks didn’t produce a commit (older templates).

UX overhaul for /shared:new-service based on dogfooding feedback. Non-breaking — old flag form (--description "...") still works.

  • shared 0.2.0: /shared:new-service rewritten for fewer interactions:
    • Natural-language args. First token = project name, the rest = description. /shared:new-service hello-world a simple rest api now scaffolds end-to-end with zero further prompts. Old --description "..." flag still accepted.
    • Auto-preflight. Silently runs uv tool install copier if copier is missing. If gh is missing, automatically falls back to “skip GitHub steps” mode and emits the exact gh repo create / gh repo edit commands in the final report (instead of aborting).
    • Auto-detect GITHUB_ORG. Tries gh api user -q .login, falls back to the copier template’s ika100 default. 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) — no AskUserQuestion, 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.

No template changes in this release.

Bugfix release. All five issues found during the hello-world dogfood are resolved.

  • shared 0.1.1: /shared:new-service now shallow-clones the platform repo before invoking Copier instead of using the unsupported gh:<owner>/<repo>/<subdir> URL form. Also passes --trust because the templates declare _tasks.
  • service-python:

    • CI: added workflow_dispatch: trigger so freshly-bootstrapped repos can run their first validation without needing a PR.
    • devbox.json: dropped --frozen from the init_hook so fresh repos can uv sync and generate uv.lock on first session.
    • pyproject.toml: added ruff, mypy, pip-audit to dev deps so uv run <tool> uses venv-installed versions (the devbox-Nix versions can’t see project deps like pydantic). Excluded tests/ from mypy and disabled disallow_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 --check on 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).
  • library-python:

    • Same workflow_dispatch: trigger, dropped --frozen, and the same dev-dep + mypy additions as service-python.
  • 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-service invocation produces a green-on-first-PR repo.
  • 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).
  • 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.