This file is the canonical entry point for AI coding agents working in
trellis, following the agents.md convention. For the
full design record see SPEC.md; for ecosystem context see
CLAUDE.md.
trellis — a deterministic, offline-by-default sloppiness audit for
TypeScript/TSX and Python workspaces. It parses source with the TypeScript
compiler API or a pinned in-process Python grammar 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 score
contributions, ranked hotspots, and safeguard evidence.
Three invariants define the product (SPEC §1):
- No-model execution. No audit path — CLI, SDK, fleet, or CI — spawns an agent, calls a model, or consumes model-derived grading.
- Offline and zero-footprint by default. The first audit needs neither Git nor credentials, a database, network, or installed project dependencies; it writes nothing unless the operator asks.
- One core, every surface. Local, fleet, and CI runs exercise the same deterministic core; CLI and SDK are thin pass-throughs.
Optional quality-evidence providers (plan
pl-43c5, SPEC §16). jscpd and dependency-cruiser run as explicitly opt-in, unscored evidence. Knip supplies contextual reachability candidates; SonarJS remains deferred. Native analysis remains the authoritative scoring basis. Current setup, trust boundaries and migration examples:docs/quality-evidence.md. Executed platforms and remaining acceptance:docs/provider-acceptance.md.
trellis is part of os-eco, the AI agent tooling ecosystem. It is the measurement surface: it gives the fleet an objective, reproducible read on structural code health. trellis mirrors the warren/burrow Bun + TypeScript-strict + Biome + SQLite stack and dogfoods its own audit (SPEC §14).
All commands run from the repo root unless noted. Bun must be on PATH.
bun install # install dependencies
bun test # run all tests
bun test <path/to/file> # run a single test file
bun run lint # biome check --error-on-warnings .
bun run lint:fix # biome check --write --error-on-warnings .
bun run typecheck # tsc --noEmit
bun run check:all # full quality-gate suite (see below)
bun run verify # alias for check:all (agent-facing entry point)
bun run check:coverage # tests + coverage ratchet
bun run test:ci # bun test with junit + coverage reporterstrellis ships a CLI (trellis, bin ./src/cli/main.ts). The deterministic
surface (SPEC §12) includes optional fleet/history and separate canonical
standards/drift inspection:
trellis audit <path> # measure + score one workspace; print the sloppiness report
# [--json|--md] [--out <file>] [--baseline <report.json>]
# [--config <file>] [--history] [--db <path>]
trellis compare <a> <b> # compare two saved report artifacts (no audit)
trellis fleet # audit every target in targets.yaml through the same core
# [--history] [--db <path>] (drift rides along, never scored)
trellis report # sloppiness history from SQLite
trellis drift <repo-path> # canonical-config drift only (separate, unscored)
trellis standards # show canonical manifest + versions
trellis guide cleanup # print bundled, read-only cleanup guidance--json / --md switch terminal output to machine/report shapes. The
default audit run is stateless — no database, no report files — unless
--history / --out ask.
Every command exits 0 clean, 2 when a policy trips (the report is still
emitted to stdout; the reason goes to stderr), or 1 on an operational
error (the command could not run). On the deterministic surface the policy
is declarative: the policy block of the workspace's trellis.yaml (SPEC §6.5 — max
index, metric budgets, score regression, new-finding kinds) gates audit
and compare, and an incompatible compare pair fails closed. EXIT
lives in src/cli/output.ts; the assessment is core (assessPolicy in
src/compare/policy.ts), so the CLI and SDK gate identically. fleet
gates on each target's own declarative policy result plus per-target
operational failures (assessFleet in src/fleet/assess.ts); canonical
drift rides along as a separate, non-scoring capability and never gates.
The separate drift command retains --fail-on drift|none for canonical
configuration policy.
src/client/index.ts exposes audit / compare / fleet / report over
the deterministic core plus separate drift and the
assessPolicy / assessFleet exit-code rules. Each is a
direct call to the same core service the CLI folds (runWorkspaceAudit,
runComparison, driftRepo, runFleetTargets, buildReport) — no logic beyond type shaping. Request types mirror
the core option types (// Mirrors src/<x>); responses are the core report
shapes. The deep-equal tests in src/client/index.test.ts prove a CLI
audit/compare/fleet and an SDK audit/compare/fleet are one code path —
measurement and policy alike.
bun run check:all (alias bun run verify) is the canonical quiet runner
scripts/check-all.ts — byte-identical across the os-eco fleet (see the
os-eco meta-repo's docs/check-all-standard.md, the same standard trellis's
own audit checks for). Never edit it in place; per-repo variation lives in
package.json script bodies. It runs the nine core gates in canonical order:
lint—biome check --error-on-warnings .typecheck—tsc --noEmit(strict,noUncheckedIndexedAccess, noany)check:agents—scripts/validate-agents-md.ts(this file's references)check:dups—bunx jscpd(duplicate-code detector)check:deps—knip --dependencies(unused / undeclared deps)check:size—scripts/check-file-sizes.ts(line-count ratchet)check:debt—scripts/check-debt-markers.ts(tracker-pinned TODOs)check:coverage—scripts/check-coverage.ts(per-package floors)check:ci-parity—scripts/check-ci-parity.ts(CI ⇄ check:all parity; escape hatches inscripts/ci-parity-config.json)
The ratchet scripts and their JSON budgets live under scripts/. Budgets ratchet in one direction only (file-size and
debt-markers tighten downward; coverage tightens upward). Do not loosen a
budget without filing trellis-XXXX and noting it in the commit body.
- Source files: kebab-case
*.ts. Tests are<name>.test.tsnext to the file under test. - Directories:
kebab-case. - Golden fixtures live under
__golden__/. Regenerate only via a documented update gate, never by hand. - YAML config keys (audit configuration,
targets.yaml) stay in the schema's casing.
Enforced by Biome's style.useFilenamingConvention rule in biome.json.
camelCasefor functions, variables, instance fields.PascalCasefor types, interfaces, classes.SCREAMING_SNAKE_CASEfor module-level true constants (VERSION).- Booleans read as predicates:
isSkippable,hasDetector.
- Strict mode with
noUncheckedIndexedAccess— always handle possibleundefinedfrom indexing. - No
any; useunknownand narrow (zod at every external boundary). - Import with
.tsextensions. - Tab indentation, 100-char line width (Biome enforces).
- All behavior lives in the core modules under
src/(current core:src/audit/,src/config/,src/contract/,src/discovery/,src/syntax/,src/metrics/,src/python/,src/safeguards/,src/scoring/,src/compare/,src/standards/,src/fleet/,src/store/,src/history/,src/report/,src/providers/,src/guides/; see SPEC §4). No business logic anywhere else.src/audit/is the deterministic audit core (trellis-ef85):auditWorkspace(root)runs discover → parse → measure → safeguards → score → assemble and returns the versioned §6.4AuditReport, with no model, network, project-command, Git, or database access.runWorkspaceAudit(root)(trellis-9a88) is the service the surfaces fold: it composes configuration, the pure pass, baseline resolution, policy assessment (src/compare/), and opt-in history (src/store/) around the measurement — never inside it. src/cli/is a thin commander pass-through;src/client/is a typed SDK whose types mirror the core (annotate// Mirrors src/<x>). Both call the same core functions so a programmatic audit and a CLI audit exercise one code path.- The analysis seam: deterministic analyzers over one shared syntax inventory;
scoring is a pure function of raw metrics; safeguards never enter the
score. Keep that seam clean. The provider seam (SPEC §16, plan
pl-43c5) keeps native analyzers and their shared inventory authoritative while optional pinned providers contribute unscored evidence through controlled execution — never through the shared inventory or the score.
The full contract is SPEC §16 (integration contract for plan pl-43c5);
the digest for agents working in this repo:
- Native is default and authoritative. Optional providers are supplemental in this delivery; provider observations are unscored, namespaced evidence. Backend promotion or provider-derived weights need a separately versioned calibration — never the current scoring version.
- Identity and states. Every provider result carries provider identity
(id, pinned tool version, adapter version, mode/options), analysis
identity (input snapshot + provider parser/version) and asserted observed
coverage — never exit-status-inferred. Allowed states:
unrequested,unavailable,unsupported,incomplete,complete. No zero-valued metrics are invented for absent execution. - Failure semantics. A requested provider that fails is located
unavailable/incompleteevidence; a violated declarative requirement on it trips policy (exit2). Invalid provider configuration or inability to run the audit stays operational error (exit1). Advisory failure is visible evidence, not an abort. - Execution boundary. Default native audits create no scratch files.
Explicitly enabled external execution may use trellis-owned isolated
temporary storage with cleanup and never writes to the target. No target
scripts, executable target configuration, arbitrary command strings,
audit-time downloads, or models — ever. The controlled process runner
(
src/providers/process.ts, trellis-eddc) is the only seam that may start a provider subprocess: fixedargv, supported executables only, explicit environment, wall-time/output limits, process-group termination, scrubbed diagnostics — no shell, no success claims. The staging layer (src/providers/workspace.tswithsrc/providers/staging.ts,src/providers/context.tsandsrc/providers/staged-run.ts, trellis-2fe6) builds the isolated view that seam consumes: a content-fingerprinted, classified source snapshot with opt-in declarative project context, realpath-validated containment (no symlink/traversal escapes, canonical paths) and owned scratch cleanup on every exit path — native audits never invoke it. The supported-tool manifest/resolver (src/providers/manifest.ts+src/providers/resolve.ts, trellis-ff52; seedocs/provider-tools.md) pins the exact external artifacts (initially jscpd 5.2.1 and — with the architecture-evidence adapter — dependency-cruiser 18.3.1, isolated devDependencies) and resolves them only from an operator-prepared local installation — verified against recorded digests before use, never via PATH/bunx, never installed or downloaded at audit time. The jscpd adapter (src/providers/jscpd/— raw report schemas and validation, pinned argv and identity, and per-mode plus full-adapter execution, trellis-f4e2) runs the pinned exact/normalized/near modes over a staged view through that runner and validates the raw JSON into typed evidence before any normalization (trellis-da4c owns that); it stays unscored and outside the default audit. The dependency-cruiser adapter (src/providers/dependency-cruiser/, trellis-adbf) evaluates the declarative architecture-rule subset through a trellis-generated tool config over a staged view, records the TypeScript parser the tool resolves locally, and keeps an empty or partial graphincompletewith the coverage loss named — never a clean pass; it stays unscored and outside the default audit. The Knip reachability context is declarative (src/contract/reachability-policy.tsplus pure compilation and context preparation insrc/providers/knip/, trellis-5da5): declared entries, public surfaces and test participation compile into a normalized, assumption-carrying context — omitted entries, unresolvable declarations, missing dependency context and disabled plugin discovery are recorded contextual assumptions that can never imply confirmed dead code — and the delivered adapter (trellis-8ebc) runs the pinned Knip (6.16.1, the repo's own check:deps gate tool, no second copy) through trellis-generated configuration over a staged view with the runtime plugin registry explicitly disabled, differentiating orphan files, unused exports/types and unresolved imports as contextual advisory candidates with stable path/symbol ordering; declared public surfaces exempt their own candidates as visible evidence, and an empty or partial pass is locatedincomplete— never a clean pass and never a quality verdict. - Compatibility. Provider changes never fragment score history; provider
evidence compares only on identical provider/analysis identity; older
artifacts without provider evidence read as
unrequested, never as regressions. - Sonar gate. SonarJS requires an affirmative documented
distribution/metric-interface decision; until then it is explicitly
deferred — a valid request resolves to
unsupportedwith the reason, visible and policy-testable, never claimed implementation. The decision is recorded as deferred indocs/sonarjs-decision.md(trellis-db3e) and carried by the typed capability metadata insrc/providers/capabilities.ts; the clearance prerequisite is tracked astrellis-7f5d.
describe("<unitUnderTest>")+test("verb-led behaviour description").- No
should, noit. - No mocks for filesystem or SQLite — use temp dirs and
:memory:/temp DBs. Stub only at true external process boundaries; the layers above run real code paths against golden fixtures.
Every TODO / FIXME / HACK / XXX on a source line must carry a tracker
reference on the same line. Accepted prefixes:
trellis-XXXX— repo-local seeds trackermx-XXXX— cross-repo mission tracker#NNN— GitHub issue- A URL (any http link) — external reference
scripts/check-debt-markers.ts fails CI on bare markers.
CLI progress and errors write to stderr. Never include sensitive fields
(token, api_key, password, secret, authorization, set-cookie) in
diagnostics. Any future structured logger must redact those keys.
When an agent works in trellis, it should:
- Prime context. Read this file (
AGENTS.md),SPEC.mdfor the area under change, and the most recentCHANGELOG.mdentry. Runml primeandsd prime(os-eco session bootstrap). - Find unblocked work.
sd ready(Seeds) orgh issue list. - Make focused changes. One concern per commit. Preserve existing conventions — adapt, don't overwrite. Respect the api>cli>sdk seam.
- Run gates locally.
bun run lint && bun run typecheck && bun test && bun run check:allmust all exit 0 before commit. - Pin debt markers. Any new
TODO/FIXMEmust reference a tracker id created in the same change. - Commit & sync. Commit message follows
<area>: <summary>(e.g.metrics: cap nesting contribution at package boundary). - Record insights.
ml record <domain> --type <type>for any convention discovered, pattern applied, or failure encountered.
Before ending a session:
- File issues for remaining work (
sd create). - Run
bun run check:all. - Close finished issues (
sd close <id>). - Record session insights (
ml record/ml sync). - Push only when the user requests it; otherwise leave commits local.
- Verify
git statusis clean.
After every code change (before commit):
bun run lint
bun run typecheck
bun test
bun run check:allAll must exit 0. CI runs the same suite — local greens are the contract.
bun run check:coverage enforces scripts/coverage-budgets.json. The ratchet
only goes up. Raising a floor when coverage improves is encouraged;
lowering one requires a trellis-XXXX in the commit body explaining what tests
were removed and why.
bunfig.toml sets the test root to src. New tests belong next to the file
under test as <name>.test.ts. Golden-fixture suites read from __golden__/
and must pass with no network.
.github/workflows/ci.yml runs bun run check:all and uploads
coverage/lcov.info and junit.xml. Local check:all failures will break CI;
do not push hoping CI will pass. The check:ci-parity gate enforces this
mechanically: every bun run <x> in ci*.yml must be reachable from the
check:all manifest, or carry a justified entry in
scripts/ci-parity-config.json (test:ci aliases to check:coverage; the
report:* summaries are CI-only).
trellis audits itself (SPEC §14):
trellis audit . runs offline with no model, and a regression in trellis's
own sloppiness index is a real failure.
SPEC.md— the deterministic product contract (authoritative)README.md— user-facing pitch + installCLAUDE.md— tool-specific conventions for agentsCHANGELOG.md— release historyRUNBOOK.md— release / triage / rollback proceduredocs/architecture.mmd— module graph of the deterministic core (SPEC §4)docs/corpus-validation.md— the fixed-corpus validation record (trellis-e924): score-behavior evidence, runtime/memory budgets, and the duplication-minimum calibration; corpus lives incorpus/, harness inscripts/validate-corpus.tsdocs/research/provider-spike.md— the fixed-corpus provider research (jscpd/SonarJS/dependency-cruiser/Knip) behind the SPEC §16 optional-provider contractdocs/provider-tools.md— the pinned supported-tool manifest and local resolver: operator-prepared installation, artifact verification, honest platform records, and upgrade/identity rules (trellis-ff52)scripts/— ratchet scripts and pre-commit hook.github/workflows/— CI + sync-labels + publish