Files
ccs/docs/code-standards.md
T

200 lines
8.0 KiB
Markdown

# 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`](../eslint.config.mjs) |
| Formatting | [`.prettierrc`](../.prettierrc) |
| Root commands and test entry points | [`package.json`](../package.json) |
| UI commands and dependencies | [`ui/package.json`](../ui/package.json) |
| Commit syntax | [`commitlint.config.cjs`](../commitlint.config.cjs) |
| Release effects | [`.releaserc.cjs`](../.releaserc.cjs) |
| Repository workflow | [`CLAUDE.md`](../CLAUDE.md) and [`CONTRIBUTING.md`](../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:
1. **YAGNI**: build only behavior required by the accepted scope.
2. **KISS**: prefer a small local change over a new abstraction or dependency.
3. **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.mjs`](../eslint.config.mjs) configures `max-lines` as a
warning for `src/**/*.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`](../src/errors/error-types.ts) so
[`src/errors/error-handler.ts`](../src/errors/error-handler.ts) can map failures
consistently. The generated baseline in
[`eslint-rules/throw-error-baseline.json`](../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_COLOR` and 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`](../src/targets/target-adapter.ts).
## Target Adapters
Runtime target behavior is divided across:
| Concern | Source |
| --- | --- |
| Target names, aliases, persistence | [`src/targets/target-metadata.ts`](../src/targets/target-metadata.ts) |
| Selection priority and flag parsing | [`src/targets/target-resolver.ts`](../src/targets/target-resolver.ts) |
| Adapter contract | [`src/targets/target-adapter.ts`](../src/targets/target-adapter.ts) |
| Registration and lookup | [`src/targets/target-registry.ts`](../src/targets/target-registry.ts) |
| Startup registration | [`src/ccs.ts`](../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()` in
[`src/utils/config-manager.ts`](../src/utils/config-manager.ts).
- Never run tests against a contributor's real `~/.ccs/` or `~/.claude/`.
Set `CCS_HOME` to a temporary directory.
- Preserve documented configuration and profile-resolution priority. Add a
focused test when changing precedence or fallback behavior.
- Treat Docker service `ccs` and network `ccs-net` as public contracts; see
[`CONTRIBUTING.md`](../CONTRIBUTING.md#if-you-change-the-docker-network-or-service-name).
## Dashboard Code
- Keep page orchestration in `ui/src/pages/`, reusable UI in
`ui/src/components/`, server-state access in `ui/src/hooks/`, and shared
helpers in `ui/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`](../ui/src/lib/locales.ts); translations are wired
through [`ui/src/lib/i18n.ts`](../ui/src/lib/i18n.ts).
The UI has its own ESLint, TypeScript, Prettier, and Vitest configuration.
Verify commands against [`ui/package.json`](../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`](../tests/README.md) for ownership and commands.
Run the smallest relevant test first, then the normal gate:
```bash
bun run format
bun run lint:fix
bun run validate
```
Before review or merge confidence:
```bash
bun run validate:ci-parity
```
For dashboard changes:
```bash
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`](../commitlint.config.cjs), with a 100-character header
limit. Use focused subjects such as:
```text
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`](../.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`](./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.