Skip to content

Development

This repo holds two Go modules, joined by a go.work file so they build and test together but stay independently deployable. See Server → Control plane for why they’re split.

main.go CLI: check, baseline, serve, migrate, hook, hash,
license, login, logout, version
internal/
check/ the deterministic check engine (colors, fonts, terms),
plus per-format extraction for .pptx/.docx
ruleset/ YAML ruleset schema, loader, hot-reloading registry
verdict/ shared Verdict/Violation types, the core contract
baseline/ accepted-debt baselines (content-identity matching)
config/ operational config resolution (flags > env > file)
checker/ the port between adapters and the core: Local | Remote
githook/ git pre-commit adapter (staged blobs)
claudehook/ Claude Code PostToolUse adapter
codexhook/ Codex PostToolUse adapter
pptx/ .pptx (zip-of-XML) parsing: slide text, theme, media
docx/ .docx (zip-of-XML) parsing: body text, theme, media
assetmeta/ in-content/document-property metadata markers
imagehash/ perceptual + exact image hashing for outdated logos
tone/ LLM tone-judge Provider implementations (Anthropic)
oidcverify/ shared ID-token verification (dashboard SSO, /v1/check)
clilogin/ stylist login's OIDC device-code flow
credentials/ persisted personal login credential (~/.stylist/…)
server/ core API HTTP server + auth middleware
store/ check store: portable SQL, SQLite/Postgres, migrations
license/ ed25519 license keys
dashboard/ server-rendered dashboard UI (go:embed templates)
controlplane/ separate module: admin UI for org creation,
API token issuance/revocation, and ruleset
publishing, see server.md
testdata/ fixture ruleset + good/bad HTML used across suites
docs/ this documentation

Dependency direction: adapters and surfaces depend on checker, verdict, config; the core (check, ruleset) depends on nothing above it. All harness-specific code stays in its adapter package. controlplane depends on the shared Postgres schema only, never on internal/ packages as a Go import. The two modules are coupled by schema, not by shared library code.

  • TDD. Every feature lands as failing specs first, then the implementation. Tests live next to the code (*_test.go, same package, unexported access); file fixtures go in testdata/.
  • Fail-open is a product invariant for hooks: violations block, infrastructure failures warn and allow (unless fail_open: false).
  • Secrets are env-only. Never add a config-file key for a credential; there is a test asserting api_token: in YAML is ignored.
  • Single binary. New surfaces are subcommands; assets are go:embeded; dependencies that break CGO-free static builds are off the table.
Terminal window
make test # go test ./...
make e2e # agent-seam scenarios: real claude/codex CLIs vs scripted fake models
make vet # go vet ./...
make build # dist/stylist with version stamped
make release # cross-compiled static binaries
make docker # distroless server image

Run the store suite against a real Postgres:

Terminal window
docker run -d --name pg -e POSTGRES_PASSWORD=test -p 5433:5432 postgres:16-alpine
STYLIST_TEST_POSTGRES_DSN='postgres://postgres:test@127.0.0.1:5433/postgres?sslmode=disable' \
go test ./internal/store/

The e2e agent scenarios (make e2e) need the claude and/or codex CLIs on PATH; each scenario skips cleanly when its CLI is missing, and STYLIST_E2E_AGENTS=1 promotes the skip to a failure for CI runners that promise to have them. Design notes and the per-CLI findings live in documents/e2e-agent-harness.md (last green: claude 2.1.206, codex 0.144.1).

  1. Spec it in internal/check/check_test.go.
  2. Implement the Check interface (Name, Enabled, Run) in a new file in internal/check/; register it in deterministicChecks.
  3. Extend the ruleset schema in internal/ruleset/ruleset.go if the check needs configuration.
  4. Give violations a stable Snippet: it is the identity baselines match on.

Adapters are deliberately thin: gather (asset name, content) pairs, call checker.FromConfig(...), report verdicts, honor fail-open. Use internal/githook (≈100 lines) as the template. Rule knowledge never belongs in an adapter.

See Database → Adding another engine.