Skip to content

ShipGate architecture

Repo-policy-first quality orchestration. Projects declare intent; ShipGate runs tools through catalog metadata, Execution Requests, and canonical reports.

Consumer usage (suites, config, CI, tools): Usage, Configuration, Tools.\ Tool YAML → shipgate check walkthrough: Check flow.

Vision

ShipGate sits between repository policy and external tools (linters, scanners, formatters, project gates). Projects say what should run; the catalog says how each tool is invoked. Tools still do the work.

Ready by default means opinionated and strict, not loose. Suites group tools; there is no separate workflow or capability catalog.

Layers

domain/ → config/ → catalog/ → planning/ → adapter/ → runtime/ → normalize/ → formatters/
                                                                         → app.py → cli.py
Package Responsibility
domain/ Frozen types: project, catalog, options, execution, reports
config/ Project policy discovery and loading
catalog/ Bundled tool/suite YAML, validation, load
planning/ Suite expansion, scopes, Execution Request resolution
adapter/ Resolved request → argv
runtime/ Executor, install, environment, reports, session
normalize/ Tool output → canonical CheckReport
formatters/ Canonical report → human/CI text
gates/ Script-gate runtime and local-gate discovery
app.py / cli.py Orchestration and argparse entry
Parallel package Responsibility
policy/ Bundled PolicyGate modules (module: catalog tools)
frontend/ Report UI; reads canonical reports only
project/ init / scaffold / project Python helpers
registries/ Shared ID registries
core/ Shared process/path utilities
plugins/ Deferred stub (Decision 005)

Policy stays out of argv details. Catalog metadata owns flags, config discovery, and normalizer binding. No tool-specific branches in planner, adapter, or executor.

Scoping and ignores

ShipGate resolves check paths through gitignore-aware scoping (planning/utils/gitignore.py) before building argv. Prefer that single source of truth over duplicating exclude lists in every bundled tool config.

Bundled per-tool excludes remain only when the tool also walks trees itself outside ShipGate's path list (for example Bandit's exclude_dirs in configs/bandit.yaml). Tools that only see the paths ShipGate passes (yamlfmt notes this in its config header) should not grow parallel ignore YAML.

Project pain belongs in .shipgate/configs/ or .shipgate/allowlists/, not in portable bundled catalog defaults.

Decisions

Decision 001: keep policy separate from execution

Project config describes intent (suite, scopes, thresholds). Command flags, recursion, and install paths belong in the catalog and runtime.

Decision 002: Execution Request is the internal API

CLI, Python API, and CI all produce the same Execution Request. Runtime does not care how the request was started.

Decision 003: tools and suites are runnables

A tool runs one external executable. A suite expands to tools (and nested suites). Groups like security and full need no special execution path.

Decision 004: normalize before formatting

Normalizers convert tool output to canonical ShipGate JSON. Formatters consume only that shape.

Decision 005: catalog first, public plugins later

Bundled catalog entries, configs, and normalizers are the extension surface until those contracts are stable enough for a public plugin API.

Decision 006: ship opinionated defaults

A conventional repo should get a high-signal gate after init / install without hand-tuning every tool. Project config overrides policy; it is not required to make the product usable.

Decision 007: keep the product UX, metadata-driven internals

Small command set (install, update, format, check), managed installs, quiet success, structured failures. Tool behavior lives in declarative Tool Definitions, not hardcoded runtime branches.

Decision 008: seed the catalog from proven tools

Bundled tools start from the useful set proven in earlier work (Ruff, ty, Bandit, Semgrep, Gitleaks, and the rest of the current catalog). Extend via Tool Definition YAML + config + normalizer.

Decision 009: report frontend on canonical JSON

A local report UI may browse runs and findings, but it must read canonical reports, not raw tool output. CLI and report schema come first.

Package view

flowchart TB
  CLI["cli.py"] --> App["app.py"]
  App --> Config["config/"]
  App --> Catalog["catalog/"]
  App --> Gates["gates/ — merge local gates"]
  App --> Session["runtime/ session"]
  Session --> Plan["planning/"]
  Session --> Adapter["adapter/"]
  Session --> Exec["runtime/executor"]
  Session --> Norm["normalize/"]
  Session --> Fmt["formatters/"]

For how a Tool Definition becomes argv and a report, including shipgate check, --suite, and --check examples, see Check flow.