8.0 KiB
CCS Code Standards
These standards describe current repository practice. Enforced configuration and tests take precedence over prose.
Source of Truth
| Contract | Authoritative source |
|---|---|
| TypeScript and CLI lint rules | eslint.config.mjs |
| Formatting | .prettierrc |
| Root commands and test entry points | package.json |
| UI commands and dependencies | ui/package.json |
| Commit syntax | commitlint.config.cjs |
| Release effects | .releaserc.cjs |
| Repository workflow | CLAUDE.md and CONTRIBUTING.md |
Update this document when one of those contracts changes. Do not duplicate exhaustive rule, command, target, or dependency lists here.
Design Priorities
Apply these in order:
- YAGNI: build only behavior required by the accepted scope.
- KISS: prefer a small local change over a new abstraction or dependency.
- DRY: share stable domain behavior, not incidental similarity.
Preserve public contracts unless the change intentionally updates them. Error messages should explain recovery, and configuration changes should remain CLI-complete with dashboard parity where the feature supports both surfaces.
Organization and Imports
- Keep code inside the domain that owns its behavior.
- Split files at cohesive boundaries such as types, pure utilities, lifecycle services, hooks, or view components.
- Follow nearby naming and import patterns. New TypeScript files normally use descriptive kebab-case names.
- There is no universal maximum directory depth.
- Barrel exports are optional compatibility boundaries, not a repository-wide requirement. Preserve an existing barrel when consumers depend on it; use a direct import when that is the local pattern or the symbol is intentionally private.
- Before creating a module, check whether the runtime, standard library, installed dependencies, or an existing repository utility already owns the behavior.
File Size
Two different thresholds serve different purposes:
- 200 lines is a review heuristic. When a code file grows beyond roughly 200 lines, check whether it contains multiple responsibilities. A cohesive file may remain larger.
- 400 lines is the current CLI lint warning.
eslint.config.mjsconfiguresmax-linesas a warning forsrc/**/*.ts, excluding blank lines and comments.
Do not split mechanically to satisfy a number. Split when the result improves ownership, testing, reuse, or reviewability. Preserve existing exports and behavior when they are part of a consumed contract.
TypeScript and Linting
The CLI uses strict TypeScript. Current enforced CLI rules include:
- no explicit
any - no non-null assertions
- no unused variables, except names intentionally prefixed with
_ prefer-const,no-var, and strict equality
Use unknown at untrusted boundaries and narrow it before use. Keep types close
to their owning domain; export types only when another module consumes them.
New generic throw new Error(...) sites are blocked by the local
ccs/no-new-throw-error rule. Use the typed errors in
src/errors/error-types.ts so
src/errors/error-handler.ts can map failures
consistently. The generated baseline in
eslint-rules/throw-error-baseline.json
grandfathers existing sites; do not expand it to bypass a new error design.
Terminal and Process Behavior
- CLI terminal output is ASCII only:
[OK],[!],[X],[i]. - Respect
NO_COLORand TTY-aware output. - Prefer argument arrays when spawning processes. When a platform wrapper requires a shell, use the existing quoting and wrapper utilities rather than interpolating untrusted input.
- Preserve target-specific stdio, signal, and exit-code behavior. The adapter
contract lives in
src/targets/target-adapter.ts.
Target Adapters
Runtime target behavior is divided across:
| Concern | Source |
|---|---|
| Target names, aliases, persistence | src/targets/target-metadata.ts |
| Selection priority and flag parsing | src/targets/target-resolver.ts |
| Adapter contract | src/targets/target-adapter.ts |
| Registration and lookup | src/targets/target-registry.ts |
| Startup registration | src/ccs.ts |
A target change should update metadata, implementation, registration, tests, help text, and public documentation as applicable. Do not copy the target list into new guides; link to metadata when maintainers need the current set.
Configuration and Test Isolation
- Treat persisted environment values as strings.
- Route CCS paths through
getCcsDir()insrc/utils/config-manager.ts. - Never run tests against a contributor's real
~/.ccs/or~/.claude/. SetCCS_HOMEto a temporary directory. - Preserve documented configuration and profile-resolution priority. Add a focused test when changing precedence or fallback behavior.
- Treat Docker service
ccsand networkccs-netas public contracts; seeCONTRIBUTING.md.
Dashboard Code
- Keep page orchestration in
ui/src/pages/, reusable UI inui/src/components/, server-state access inui/src/hooks/, and shared helpers inui/src/lib/when that matches the owning domain. - Preserve unsaved input across background refreshes. Destructive replacement of dirty state requires an explicit user action or confirmation.
- Use the existing API and query helpers before adding a new data-access layer.
- Keep accessibility, keyboard interaction, responsive layout, loading, empty, and error states in scope for user-facing changes.
- Locale codes and normalization are owned by
ui/src/lib/locales.ts; translations are wired throughui/src/lib/i18n.ts.
The UI has its own ESLint, TypeScript, Prettier, and Vitest configuration.
Verify commands against ui/package.json.
Tests and Quality Gates
The root TypeScript suites run with Bun's test runner. The dashboard uses
Vitest. Native shell and PowerShell probes cover platform-specific behavior.
See tests/README.md for ownership and commands.
Run the smallest relevant test first, then the normal gate:
bun run format
bun run lint:fix
bun run validate
Before review or merge confidence:
bun run validate:ci-parity
For dashboard changes:
cd ui
bun run format
bun run validate
bun run test:run
Do not weaken or skip a failing check to make a change pass. If a full gate cannot run, report the exact focused checks completed and the blocker.
Commits and Releases
Commitlint accepts conventional types defined in
commitlint.config.cjs, with a 100-character header
limit. Use focused subjects such as:
fix(doctor): handle missing config
feat(cliproxy): add provider quota check
docs(contributing): clarify validation
Semantic-release owns versions, changelog entries, tags, npm publishing, and
GitHub releases. Do not bump versions, tag, or publish manually. Release effects
for each branch and commit type are defined in
.releaserc.cjs.
Documentation Triggers
Update the owning guide when a change affects behavior, commands, setup,
architecture, security posture, public contracts, or future maintainer
decisions. Start at docs/README.md. Public CLI, provider,
configuration, installation, or workflow changes also require the matching page
in the separate kaitranntt/ccs-docs repository; see the docs index for
checkout guidance.
Prefer source links over copied trees and metrics. Remove stale sections rather than leaving TODO markers.