Deterministic, offline-by-default sloppiness audit for TypeScript/TSX workspaces. trellis parses source with the TypeScript compiler API and measures structural debt — complexity, structural erosion, duplication, and import cycles — plus a separate, non-scoring inspection of safeguard configuration (hooks and check wiring). Each run emits a versioned report with a 0–100 sloppiness index where lower is better (not a percentage of bad code; infrastructure cannot offset it), raw metrics, traceable contributions, ranked hotspots, and safeguard evidence.
Invariants (SPEC §1): no-model execution on every audit path; the first audit needs neither Git nor credentials, a database, network, or installed project dependencies; one deterministic core serves local, fleet, and CI use with CLI/SDK parity.
SPEC.md is the authoritative design record. AGENTS.md
is the canonical agent guide; this file adds tool-specific conventions and the
os-eco session bootstrap.
- Runtime: Bun (runs TypeScript directly, no build step).
- Language: TypeScript strict (
noUncheckedIndexedAccess, noany). - Validation: zod (report/configuration contracts, §6).
- Lint/format: Biome,
--error-on-warnings, tab indent / 100-col. - Storage:
bun:sqlite(opt-in run history only; audits are stateless by default). - CLI: commander; progress and handled errors write to stderr.
- Parsing: the pinned TypeScript compiler API — one shared parse layer reused by all native metrics within an audit. No LLM provider exists in the product (no-model invariant, SPEC §1); optional analysis providers (contracted, SPEC §16) are deterministic local tools — never models.
All behavior lives in one surface-agnostic domain core; every other surface is a thin pass-through, so the surfaces cannot drift out of sync. The tree below is the current layout.
src/
cli/ # THIN commander entrypoints; parse args, call core, shape output
client/ # typed SDK; request/response types MIRROR the core (// Mirrors src/<x>)
audit/ # deterministic core: auditWorkspace + runWorkspaceAudit service
analysis/ # native analyzer registry, wrapped analyzers, provenance (§16)
guides/ # bundled, read-only task guidance
config/ # declarative audit configuration (trellis.yaml, SPEC §6.5)
contract/ # versioned zod contracts: metrics, findings, report, config (§6)
discovery/ # TS/TSX source discovery → classified source-set inventory (§3.1)
syntax/ # shared parse layer (pinned TS compiler API) + function inventory
metrics/ # complexity, erosion, duplication, import graph/cycles (§5)
providers/ # optional pinned evidence providers: staging, process runner,
# jscpd / dependency-cruiser / knip adapters (unscored, §16)
safeguards/ # hook/check configuration inspection (non-scoring, §5.5)
scoring/ # pure provisional sloppiness formula (§7)
report/ # terminal / JSON / markdown renderers
compare/ # artifact comparison + declarative failure policies (§9)
store/ # migrations/ (opt-in audit history, SPEC §10)
history/ # sloppiness dashboard projection over the store
fleet/ # targets.yaml loader + multi-repo orchestration (optional, §11)
research/ # internal research spikes; no public surface or scoring input
standards/ # canonical/ (bundled files), manifest.yaml, drift.ts
# (separate capability; never feeds the sloppiness index)
index.ts # public lib entry — VERSION constant only (lockstep w/ package.json)
- Core = every module above except
cli/andclient/. It holds all validation, measurement, scoring, and drift logic. There is no HTTP server (a network API is a deferred surface over this same core). - CLI never reimplements logic; SDK calls the same core functions in-process, so a programmatic audit and a CLI audit exercise one code path.
- Sync enforcement: single core + strict
tscover mirrored SDK types + golden snapshots of stable output shapes, all wired into onecheck:allCI runs verbatim. Drift becomes a red build, not a review judgment call.
Provider evidence (SPEC §16, plan pl-43c5): optional
supplemental providers — pinned jscpd, dependency-cruiser, Knip; SonarJS
gated on a documented distribution decision — run only on explicit opt-in,
stay unscored, and never relax the no-model, offline, no-target-command
invariants. Native analysis and the calibrated scoring remain
authoritative; provider failures are located unavailable/incomplete
evidence (policy exit 2 only via a violated declarative requirement;
invalid configuration stays exit 1). SonarJS remains deferred. Setup and
executed-platform evidence: quality evidence and
provider acceptance.
See docs/architecture.mmd for the rendered graph.
- Filenames:
kebab-case.ts. Tests are<name>.test.tsbeside the unit. Golden fixtures under__golden__/. - Identifiers:
camelCasevalues,PascalCasetypes,SCREAMING_SNAKE_CASEconstants (VERSION). - Imports:
.tsextensions; zod at every external boundary. - Tests:
describe("<unit>")+test("verb-led …"), noshould/it. No mocks for fs/SQLite — temp dirs and:memory:/temp DBs. Stub only true external process boundaries; layers above run real code against goldens. - Debt markers: every
TODO/FIXME/HACK/XXXcarries a tracker on the same line —trellis-XXXX/mx-XXXX/#NNN/ a URL. - Dogfood: trellis audits itself offline; a regression in its own sloppiness index is a real failure. The quality-gate ratchets stay binding for every change.
bun install # install dependencies
bun test # run all tests
bun test src/scoring/sloppiness.test.ts # single file
bun run lint # biome check --error-on-warnings .
bun run typecheck # tsc --noEmit
bun run check:all # canonical quiet runner: 9 core gates in fleet order
bun run verify # alias for check:all
bun run check:coverage # tests + coverage ratchetcheck:all is scripts/check-all.ts, byte-identical to the fleet template
(os-eco meta-repo docs/check-all-standard.md) — never edit it in place. It
runs lint, typecheck, check:agents, check:dups, check:deps, check:size,
check:debt, check:coverage, then check:ci-parity (CI ⇄ local parity; escape
hatches in scripts/ci-parity-config.json). Run bun run check:all before
every commit.
At the start of every session, run all three:
sd prime # Seeds: rules, command reference, workflows
ml prime # Mulch: project conventions, patterns, decisions, failures
cn prime # Canopy: prompt workflow context (if .canopy/ present)Quick reference:
- Seeds —
sd ready(unblocked work),sd update <id> --status in_progress,sd close <id>,sd syncbefore pushing. - Mulch —
ml search "query"before implementing;ml record <domain> --type <convention|pattern|failure|decision|reference|guide> ...for insights;ml syncto validate + commit.mulch/. - Canopy —
cn list,cn render <name>,cn emit --all. Do not hand-edit emitted files;cn updatethencn emit.
- Close completed issues:
sd close <id>; file remaining work:sd create. - Record insights worth preserving:
ml record <domain> --type <type> .... - Validate + sync:
sd sync && ml sync. - Run
bun run check:all; verifygit statusis clean. - Push only when the user asks.
Use the /release slash command (.claude/commands/release.md): patch-default
bump, sync package.json "version" and src/index.ts VERSION, roll
CHANGELOG.md [Unreleased] into a dated section, update this file and
README.md if the surface changed, commit. The version-gated publish workflow
(seeds trellis-3447/trellis-7baf) fails if the two version sources disagree.
See RUNBOOK.md for the full release / triage / rollback
procedure.