Reviewer kept flagging :full omission and stale :latest as bugs. Both are intentional per Q3 maintainer decision and the rc.1 soak design (loop-5). Strengthen inline comments to make this unmistakable to future readers.
* fix(ci): breaking-change-guard handles \${VAR:-default} compose image syntax (REV12)
The sed 's/:.*//' pattern truncated at the first colon in the
\${CCS_IMAGE:-ghcr.io/...} expression, yielding "\${CCS_IMAGE" as the
image name instead of the actual registry path. Any change to the default
image namespace was therefore undetectable.
Introduce extract_image_name() that first strips the \${VAR:-default}
wrapper with a sed -E expression, then strips only the trailing :tag
suffix using a pattern that preserves internal colons (e.g. registry:5000/
owner/repo). Applied to both OLD_RAW and NEW_RAW extraction paths.
* fix(ci): breaking-change-guard runs on ubuntu-latest to cover forked PRs (REV13)
The trusted-author gate (COLLABORATOR|MEMBER|OWNER) caused forked-PR
contributors to bypass the breaking-change check entirely. A forked
contributor could rename services.ccs or change the image namespace
without a feat!/fix! marker and the guard would never run.
This workflow is a documented exception to the self-hosted-first policy:
it performs ONLY pure YAML diff parsing (git show / awk / sed). No build,
install, or arbitrary PR-branch scripts are executed. The checkout uses
persist-credentials: false. There is no untrusted code execution, so
ubuntu-latest is safe and necessary for universal fork coverage.
Update self-hosted-runner-policy.test.ts to:
- Introduce GITHUB_HOSTED_RUNNER_EXCEPTIONS registry with required
justification comments for each entry
- Skip exception workflows in the "keeps active workflows on local runners"
and "gates pull-request workflows" assertions
- Add a new "documented exceptions use github-hosted runners" test that
verifies each exception entry actually uses a GitHub-hosted runner
(prevents stale entries accumulating without cleanup)
Reverts the loop-1 prerelease channel that was publishing npm as
vX.Y.Z-rc.N to the `rc` dist-tag instead of `latest`.
`main` is now a stable semantic-release channel again: every merge
publishes vX.Y.Z immediately to npm @latest. The rc.1 soak window
that guards Docker mutable tags is moved entirely into the Docker
publish workflow:
- `.releaserc.cjs`: remove `prerelease: 'rc'` from productionConfig,
restore `branches: ['main']` (stable). Restore full successComment
and `released` label. Keep loop-1 releaseNotesGenerator additions
(revert section, breaking change comment).
- `docker-release.yml`: every `release: published` event publishes
only the immutable `:<ver>` Docker tag. `promote-mutable-tags` job
now gates exclusively on `workflow_dispatch` with
`promote_to_latest=true` — no longer triggered automatically by
non-prerelease release events.
- `promote-release.yml`: rewritten as a dispatch wrapper that validates
the stable tag exists and is not a prerelease, verifies the immutable
Docker image is in the registry, then dispatches docker-release.yml
with `promote_to_latest=true`. Removes the `gh release edit
--prerelease=false` approach that required the rc soak to be wired
through GitHub release state.
- `docs/release-process.md`: updated to reflect the decoupled model —
npm @latest is immediate; Docker :latest requires manual promote after
soak. Documents the `why` split between npm and Docker soak windows.
REV9 — docker-release.yml smoke-test: add HEALTHY flag to the boot-and-wait
loop. Previously a timeout (status stays 'starting'/'missing' for all 12
iterations) exited the loop silently, letting port probes be the only net.
Now if the loop exits without HEALTHY=1 the step fails immediately with
container logs and state dump. network-contract.sh already has the HEALTHY
pattern — no change needed there.
REV10 — breaking-change-guard.yml: drop the OLD_KEYS != NEW_KEYS comparison.
That check treated ANY change to the service-key set as breaking — including
adding a harmless sidecar. The DNS contract only requires services.ccs to
exist; other services are irrelevant to the 'ccs' hostname on ccs-net. Keep
only the "ccs key must exist" check; remove the unused OLD_KEYS extraction.
* fix(test): tighten compose-parity image-name match to exact expected values (REV7)
Replace loose substring grep (grep -q "ccs") with exact equality checks
against declared EXPECTED_CANONICAL_IMAGE and EXPECTED_INTEGRATED_IMAGE
constants. The integrated compose builds locally as ccs-cliproxy:latest
(not ghcr.io/kaitranntt/ccs), so both expected names are explicitly
documented at the top of the assertion block.
Fixes: a drift in integrated compose to a wrong owner/registry could
slip through the old "grep -q ccs" check; now any name other than the
declared constant is a hard failure.
* feat(ci): breaking-change-guard catches services.ccs rename — public DNS contract (REV8)
Docker's service-name DNS uses the compose service KEY as the hostname.
Sibling containers on ccs-net reach CCS via http://ccs:8317; renaming
services.ccs: to anything else silently breaks that contract even when
image name, network name, and container_name are unchanged.
Add check 4 to the guard:
- Extract top-level service keys from both base and HEAD versions of
docker/compose.yaml using awk (no external YAML parser required).
- Fail if the "ccs" key is absent from HEAD.
- Fail if the sorted set of service keys differs from base.
Wraps inside the existing git cat-file guard so new-file PRs skip it.
* fix(ci): breaking-change-guard skips missing base files (CI-1)
Guard each git-show call with git cat-file -e existence check before
attempting to read docker/compose.yaml from the base branch. When the
file doesn't exist on the base (new file in this PR), the script would
crash with "fatal: path exists on disk but not in origin/dev". New
files can't cause a contract regression, so we exit early with
breaking=0.
* style: prettier reformat unrelated drift (CI-2)
Two files had minor formatting drift from earlier umbrella PRs:
- src/cliproxy/quota/quota-manager.ts
- src/management/checks/image-analysis-check.ts
No logic changes — formatter-only pass to unblock CI format:check.
* fix(test): update trusted-author gate count to 4 in ci-workflow test (CI-3)
PR #1260 added a compose-parity job to ci.yml. That job runs on
self-hosted runners using PR-provided checkout, so it legitimately
requires the trusted-author guard (same as validate, build, test).
The test expected 3 occurrences; the correct count is now 4:
validate (matrix), build, test, compose-parity.
* fix(ci): smoke-test passes compose path + image ref to network-contract (REV-1)
network-contract.sh signature is: <compose-file> [image-ref]
The smoke-test job was calling it as:
bash tests/docker/network-contract.sh "${{ steps.image.outputs.ref }}"
which placed the image ref in the compose-file position ($1), causing
the script to try docker compose -f <image-ref> which fails.
Corrected to:
bash tests/docker/network-contract.sh docker/compose.yaml "${{ steps.image.outputs.ref }}"
Also removes publish-dashboard from smoke-test.needs (REV-2): when
publish-dashboard is SKIPPED on prerelease events, GitHub Actions
propagates the skip to downstream jobs, so smoke-test and
promote-mutable-tags were silently skipped on every rc.N publish.
smoke-test only verifies the integrated image; it has no dependency
on the legacy dashboard image job.
* docs(docker): annotate /root/.ccs path in compose volume (REV-3 clarification)
The reviewer raised a concern that the compose volume mounts /root/.ccs
but the entrypoint might default to /home/node/.ccs. This is a false
positive: the integrated image uses entrypoint-integrated.sh (not
entrypoint.sh), which runs under supervisord with user=root and
explicitly mkdir -p /root/.ccs. HOME is /root inside the container.
The volume mount at /root/.ccs is correct.
Added an inline comment documenting the reasoning so future reviewers
do not confuse entrypoint.sh (legacy dashboard image) with
entrypoint-integrated.sh (integrated image).
* fix(ci): gate docs-parity pull_request job to trusted authors
docs-parity.yml runs on a self-hosted runner and checks out PR code.
The self-hosted-runner-policy test requires any such workflow to include
the trusted-author guard. The workflow was missing the guard, causing
bun test:fast to fail with 1 failure.
Allow push events (no author check needed — push is to own branch)
and trusted-contributor PRs only.
* docs: hoist Docker zero-install quickstart above npm install path
- Create docs/quickstart-snippet.md as canonical source for the
two-command flow (curl + docker compose up -d), wrapped in
<!-- quickstart-snippet-start/end --> markers
- Hoist the snippet into README.md immediately below the deprecation
banner, above all other install paths
- Rename old npm-only "## Quick Start" to "## Install on Host (npm)"
and move it below the Docker quickstart
* docs(docker): restructure README with zero-install first and migration section
- Reorder top-level sections: zero-install (canonical snippet with
markers), choosing an image, power-user ccs docker, prebuilt image,
connect your app to CLIProxy, migration, env vars, troubleshooting
- Add deprecation banner at the top pointing at the migration section
- Add ## Migration from ccs-dashboard:latest section with step-by-step
instructions covering compose down, data preservation, named volume
vs bind-mount path, and compose up with the new image
- Keep P1's Choosing an image table and P5's Connect Your App to
CLIProxy section intact, just repositioned
* test(docs): parity check for quickstart snippet across README files
Assert README.md and docker/README.md both contain the canonical
quickstart block verbatim, anchored by marker comments. Exits non-zero
and prints a diff on any drift.
* ci(docs): wire quickstart-parity test on push and PR
Runs tests/docs/quickstart-parity.sh on self-hosted runner whenever
docs/quickstart-snippet.md, README.md, docker/README.md, or the
test/workflow files themselves change. Fails fast on snippet drift.
Add "Connect your app to CLIProxy" section to docker/README.md with:
- Public contract table (network=ccs-net, service DNS=ccs, ports 8317/3000)
- Pattern A: same compose file with external network reference
- Pattern B: docker run --network ccs-net
- Troubleshooting subsection (DNS, missing network, conflict, Podman, MTU)
Add one-line link in README.md pointing to the new section.
Add CHANGELOG entry under Unreleased noting the contract as SemVer-major stable.
Add CONTRIBUTING.md note that changing services.ccs or networks.ccs-net requires major bump.
* feat(docker): parameterize Dockerfile.integrated with ARG FLAVOR=minimal|full
Add FLAVOR build arg that gates the AI CLI install layer (claude-code,
gemini-cli, grok-cli, opencode) so one Dockerfile produces both the
minimal (< 350 MB) and full (< 600 MB) integrated images.
Use BuildKit cache mount for /root/.npm to speed up repeated builds.
Part of #1251 (P1 — publish integrated images).
* feat(docker): emit startup deprecation warning in legacy ccs-dashboard entrypoint
Prepend a [WARN] line to stderr on every container start so operators
running ghcr.io/kaitranntt/ccs-dashboard:latest are notified to migrate
to ghcr.io/kaitranntt/ccs:latest. Sunset window: 2 releases. See #1251.
* test(docker): add image-size.sh budget assertion + unit test suite
image-size.sh: asserts docker image inspect .Size against a byte budget.
Usage: image-size.sh <image:tag> <max-bytes>
Budgets: minimal=350 MB (367001600), full=600 MB (629145600)
image-size-logic.test.sh: 6 mock-docker unit tests covering pass/fail
boundaries, bad arg count, and non-integer input. All 6 pass locally.
Part of #1251 (P1).
* ci(docker): extend docker-release.yml to publish ccs:latest and ccs:full
- Add publish-integrated job with matrix flavor: [minimal, full]
- minimal -> ghcr.io/kaitranntt/ccs:<ver> + :latest (when promoted)
- full -> ghcr.io/kaitranntt/ccs:full-<ver> + :full (when promoted)
- Multi-arch: linux/amd64 + linux/arm64 via buildx
- Scoped GHA cache per flavor to avoid cross-contamination
- Add promote_to_latest workflow_dispatch input (default false)
- rc.1 soak: first publish only pushes immutable version tag
- Mutable :latest/:full promoted only on release event OR explicit opt-in
- Add smoke-test job (post-publish) for each flavor
- Pulls the just-published version tag
- Asserts image size via tests/docker/image-size.sh
- Boots container, waits for healthcheck, probes :3000 and :8317
- Keep publish-dashboard job unchanged (legacy 2-release sunset)
- Updated labels to note deprecation status
All jobs run on self-hosted cliproxy runners. Part of #1251 (P1).
* docs(docker): document new image tags, add ccs-dashboard deprecation notices
docker/README.md:
- Add "Choosing an image" table (ccs:latest / ccs:full / ccs-dashboard deprecated)
- Update Quick Start section to use ccs:latest as primary example
- Add legacy image note with sunset timeline
CHANGELOG.md:
- Add Unreleased > ### Deprecated entry for ccs-dashboard:latest
pointing to migration path and #1251
README.md:
- Add one-line deprecation banner near top routing users to ccs:latest
Part of #1251 (P1).
Keep active CCS workflows on self-hosted runners, gate self-hosted PR execution to trusted authors, and scope privileged release credentials to the exact git operations that need them.
Light-theme captures from Playwright MCP against `bun run dev` with
`?mock=logs` deterministic fixture and real backend (no mock):
1. `01-after-overview-mock.png` — LOGS.STREAM marker, Live activity
title, ENTRIES/TRACES/ERRORS stat strip, populated detail panel
2. `02-after-real-backend-clean.png` — real backend with default filter
active: 0 entries because all are dashboard internals; no noise
3. `03-hide-internals-toggle.png` — advanced filters showing the new
labelled toggle with red callout
4. `04-internals-shown-with-coalesce.png` — toggle off: dashboard
internals visible but a 149-stage trace coalesces to 3 rows; detail
panel shows full requestId UUID + module (C1 fix proven)
5. `05-after-mobile.png` — 390x844: header marker + title + stat strip
wrap, expanded trace renders cleanly
Asset directory will be renamed from PLACEHOLDER_PR to actual PR number;
HEAD SHA placeholder pinned after final commit.
Refs #1138, #1141, #1142
Rename .github/pr-assets/PLACEHOLDER_PR -> .github/pr-assets/1146 and
substitute {HEAD_SHA_PLACEHOLDER} with the current HEAD so commit-pinned
raw GitHub URLs in the HTML report resolve correctly.
The pinned SHA in this commit is the parent commit; once this commit is
pushed, the PR body references will resolve via the htmlpreview.github.io
proxy against the parent SHA. Reviewers see frozen-in-time evidence.
Refs #1142, #1138
Self-contained HTML report + 7 light-theme PNG captures with DOM-overlay
red callouts (#ef4444, 3px, 8px pad). Captured via Playwright MCP against
local 'bun run dev' instance with ?mock=logs deterministic 150-entry
fixture.
Surfaces:
1. Overview - default state, callouts on Live pill + ? button
2. Trace grouping - expanded vs collapsed states
3. Live-tail paused (Resume tail button)
4. Advanced filters expanded
5. Empty state - filtered-to-nothing + active filter chip
6. Keyboard shortcuts dialog
7. Mobile responsive (390x844)
Targeted-mode evidence; the 'before' UI is captured in the parent issue's
contributor screenshot. Asset directory will be renamed from PLACEHOLDER_PR
to actual PR number; HTML and asset URLs use {HEAD_SHA_PLACEHOLDER}
tokens that the maintainer pins after final commit.
Refs #1142, #1138
- align dev-release workflow with protected-branch bypass used by release.yml
- add a workflow regression test so checkout and release auth do not drift back to github.token
- cache only Bun's package cache so CI stops restoring partial hard-linked node_modules trees
- bump the cache key to avoid reusing corrupt node_modules archives on self-hosted runners
- Add concurrency group keyed on github.ref with cancel-in-progress to stop superseded runs on rapid pushes
- Split validate matrix into: validate (typecheck/lint/format), build, test
- Build uploads dist/ artifact; test downloads instead of rebuilding (removes inline build:all duplication)
- Net: faster feedback, less runner time, DRY build step
- ci.yml: 5-job matrix (typecheck/lint/format/build/test), fail-fast off,
actions/cache on ~/.bun/install/cache + node_modules + ui/node_modules.
Target wall time ~60-90s vs prior ~3-4min.
- .husky/pre-push: add bun run build:all to feature-branch fast gate so
UI tsc -b errors are caught pre-push (~18s warm).
- CLAUDE.md (symlinked as AGENTS.md): fix validate drift (maintainability
is NOT part of validate), add CI-First Protocol mandating gh pr checks
--watch after every push, replace pre-commit checklist with two-tier
model (iterative push vs pre-merge).
Branch protection on main/dev must be updated post-merge to require the
new matrix status checks instead of the single legacy "validate" check.
- replace hardcoded GLM endpoint with vars.AI_REVIEW_BASE_URL
- replace hardcoded model with vars.AI_REVIEW_MODEL
- use secrets.AI_REVIEW_API_KEY for auth token
- add configuration reference in header comment
- switch the reviewer workflow from glm-5-turbo to glm-5.1
- increase workflow timeout to 20 minutes and max turns to 45
- lock the workflow test to the full reviewer model configuration
Closes#922