Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ jobs:
- name: Typecheck
run: npx tsc --noEmit

- name: Code standards (no new violations vs baseline)
run: npm run check-standards

- name: Architecture (no new findings vs baseline)
run: npm run check-architecture

- name: Full test suite
run: npm test

Expand Down
6 changes: 6 additions & 0 deletions .roo/rules/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Before considering any change to `src/` done:
re-run the relevant suites under `scripts/e2e*/run.sh` — these exercise
real spawn/watch/review flows against a local mock LLM server, not just
unit-level logic.
- `npm run check-standards` and `npm run check-architecture` must pass —
both are content-anchored baseline ratchets (see
`docs/typescript-standards.md`): no new violations. Regenerate the
baselines in the same change when you fix a file, never by hand.
- Refactors are tests-first: cover the target with tests before reshaping
it, and keep `npm run coverage -- --min <floor>` from regressing.

## Established conventions

Expand Down
84 changes: 84 additions & 0 deletions docs/typescript-standards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# TypeScript code standards (headlesscode)

No fleet-wide TS size/complexity standard exists — HQ `standards/CAPSIZE_PROJECT_STANDARDS.md`
§4/§8 only fixes the toolchain (Prettier + ESLint via `typescript-eslint`,
`tsc --noEmit`), and `capsize-commons/typescript/eslint.config.js` only adds
`no-explicit-any` and `consistent-type-imports`. This file defines the
headlesscode instantiation. The measurable rules are enforced by
`npm run check-standards` (`scripts/check-code-standards.mjs`); run it before
every commit. CI runs it against the committed baseline: no new violations.

## Formatting (Prettier direction)

Tabs for indentation, double quotes, no semicolons, 80-column width,
trailing commas in multiline literals. A `.prettierrc`/`eslint.config.js`
adoption is still open; until then match the surrounding code exactly.

## Size limits (enforced)

| Unit | Limit | Rule id |
|---|---|---|
| Line length | 80 characters | `line-length` |
| File | 250 lines | `file-lines` |
| Class | 200 lines (declaration span) | `class-lines` |
| Function / method | 20 lines (body span) | `function-lines` |

When a limit is reached, split the logic — extract a helper, move a class
into its own file. Counts are source lines including blanks and comments.

## Structure and types (enforced)

- One class per file (`one-class`), except the vendored tree.
- Full type annotations on every function and method signature, parameters
and return type both (`explicit-return-type` checks the return side;
`strict` + review cover the rest).
- No `any`: no `any` annotations, no `as any`, no `any[]` (`no-any`).
- No suppression comments: no `eslint-disable`, `@ts-ignore`,
`@ts-expect-error`, `# noqa`, `# type: ignore` (`no-suppress`).
Narrow or guard instead.

## Scope

Checked: all `src/**/*.ts` except `src/vendor/**` (vendored upstream, kept
verbatim). Tests follow the same rules — a 400-line test file is still a
400-line file; split the fixture or the suite.

## Baseline ratchet

`scripts/code-standards-baseline.json` records violations that predate
enforcement. The check passes only when the current violation set is a subset
of the baseline (nothing new). Keys are content-anchored (file path + rule +
hash of the offending line or declaration), not line-numbered, so inserting
lines above a violation never trips the gate — but editing a flagged line
does count as new: fix it instead. When you fix a file, regenerate the
baseline (`npm run check-standards -- --write-baseline`) in the same change
so the ratchet tightens. Never add entries to the baseline by hand.

Arrow functions in expression position (inline callbacks) are exempt from
`explicit-return-type`, matching `explicit-function-return-type` with
`allowExpressions`: inference is the idiom there.

## Architecture (enforced via `npm run check-architecture`)

Fewest lines for the most complexity — no repetition, no knots:

| Finding | Rule id | Limit / meaning |
|---|---|---|
| Import cycles | `cycle` | none tolerated; the full cycle path is reported |
| Duplicated blocks | `clone` | 10+ identical normalized lines (imports/exports/comments excluded) |
| Cyclomatic complexity | `complexity` | > 10 per function |
| Nesting depth | `nesting` | > 4 control levels per function |
| Parameter count | `params` | > 5 parameters (bundle into an options object) |
| `console.*` in library code | `console` | tests and `cli.ts` entry points exempt |
| `process.exit` in library code | `process-exit` | `cli.ts` entry points exempt; libraries throw |

Same content-anchored baseline ratchet
(`scripts/architecture-baseline.json`): no new findings; regenerate in the
same change when you fix a file.

## Tests-first refactoring

No refactor without covering tests first: the test for a class or function
must exist and pass before that unit is reshaped, and
`npm run coverage -- --min <floor>` must not regress. See the worker brief
in the quality-campaign issues.
Loading
Loading