mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-04 12:13:09 +00:00
Adoption moved the canonical settings.json aside with rename() and left
the path empty until publication, roughly 100 ms later. Claude Code or a
second `ccs` starting inside that window found no file and seeded an
empty placeholder; publication then failed with EEXIST because link() is
no-replace, and the rollback published a backup and unlinked the claim,
destroying the only remaining copy of the user's settings. Recovering
meant digging through sidecar files by hand.
Publish by replacement instead: write a temp file next to the canonical
inode and rename() it over the target, so the path always holds a regular
file and no placeholder can be seeded. A compare-and-swap guard on
(ino, mtime, size) runs immediately before the rename and refuses to
publish when the canonical inode changed since it was read, so a writer
that got there first is still never clobbered. The pre-image backup is
published before the replacement, keeping the old content recoverable if
publication is interrupted.
Drops the canonical claim entirely along with restoreCanonicalClaim, and
folds the two identical sidecar publishers into one helper.
recoverOrphanedCanonicalClaim stays, since claims written by older
versions may still be on disk.
New tests cover both writers seen in the incident: Claude Code seeding
`{}` with a trailing newline, and a second `ccs` seeding the 2-byte
variant from shared-dir-linker. Four tests that pinned the claim-based
design were rewritten, among them `preserves a canonical write that
lands during no-replace publication`, whose intent is now enforced by
the CAS guard instead of by an EEXIST from a no-replace link.
Built [OnSteroids](https://onsteroids.ai)
CCS Test Suite
Root TypeScript and JavaScript tests run with Bun's test runner. Native shell, PowerShell, Docker, and standalone probes cover contracts that need a real platform or process boundary.
Ownership
| Area | Path | Use for |
|---|---|---|
| Unit | unit/ |
Focused module and command behavior |
| Integration | integration/ |
Cross-module, process, proxy, auth, and web-server behavior |
| End to end | e2e/ |
Packaged CLI workflows |
| npm package | npm/ |
Installation, exports, and package behavior |
| Native | native/ |
Unix and Windows shell behavior |
| Docker | docker/ |
Compose and stable network/service contracts |
| Documentation | docs/ |
Repository documentation invariants |
| Shared support | shared/ |
Fixtures and helpers reused by suites |
| Mocks | mocks/ |
Bounded test doubles and fixtures |
Some source domains also keep focused tests in src/**/__tests__/. Follow the
nearest established pattern and avoid moving tests solely for taxonomy.
Commands
Commands are defined in ../package.json. Bucket membership
and execution live in
../scripts/run-test-bucket.js.
bun run test:fast # Fast Bun test bucket
bun run test:slow # Slow Bun test bucket
bun run test:all # All root non-e2e Bun test buckets
bun run test:unit # tests/unit
bun run test:npm # tests/npm
bun run test:native # Native Unix edge-case script
bun run test:e2e # tests/e2e with fail-fast and extended timeout
bun run test # Build, then test:all
For the normal contributor gate:
bun run format
bun run lint:fix
bun run validate
For the closest local equivalent to PR CI:
bun run validate:ci-parity
Dashboard tests use Vitest and are documented in
../ui/README.md.
Test Isolation
- Set
CCS_HOMEto a temporary directory. - Never read or modify a contributor's real
~/.ccs/or~/.claude/. - Use
getCcsDir()from../src/utils/config-manager.tsfor CCS paths. - Keep fixtures deterministic and free of credentials or private account data.
- Use real behavior at the boundary under test; do not weaken assertions to hide regressions.
Adding Coverage
- Add a focused unit test for isolated logic.
- Add integration coverage when behavior crosses modules, processes, HTTP, or persistence boundaries.
- Add e2e coverage when command routing or packaged CLI behavior is the contract.
- Add native or Docker coverage only when platform/runtime behavior cannot be represented faithfully in Bun tests.
- Run the smallest relevant command first, then broaden to the contributor or CI-parity gate when shared contracts changed.