What Carrick covers
What Carrick scans, the requirements a repo needs to scan cleanly, the framework and protocol story, and the cases that fall outside MVP scope.
Carrick scans TypeScript services. It finds the operations each service exposes — REST/HTTP endpoints, GraphQL schema fields, WebSocket events, and pub/sub topics — and the calls between services, resolves their request and response types, and indexes the result. Your agent queries that index over MCP, and the GitHub App surfaces contract drift on pull requests.
Before you wire Carrick into a repo, check the requirements below hold and the limitations don’t block your use case. Both lists are short on purpose: the supported scope is deliberately narrow.
Requirements
A repo scans cleanly when all of these are true:
- TypeScript source. Carrick reads request and response types from TypeScript. Pure JavaScript repos can still produce a route map, but endpoint shapes will be
unknownbecause there are no types to read. - A
package.json. Used to detect the framework, resolve dependencies, and ground the dependency conflict checks. Any package manager works — npm, pnpm, Yarn, or Bun. Carrick only reads the manifest: it never runs your install, so it needs no lockfile, nonode_modules, and no particular Node version in your repo. A missingtsconfig.jsonfalls back to sensible defaults. - Statically discoverable routes. Carrick finds routes by reading your source, not by running your code. If a path string or HTTP method is a literal in source (or resolvable to one), it gets indexed. If it is computed at runtime from a config file, a database, or an environment variable, it does not. See “Dynamic routing” below.
- GitHub Actions. Carrick runs as a GitHub Action, once per push or pull request, and uploads the result on main-branch runs. Local-only scans aren’t supported.
Framework coverage
Carrick works across REST frameworks. It matches on call shape (app.get, router.use, axios.post, decorators, common HTTP-client patterns) rather than specific framework packages, and adapts to the framework and version it detects in your package.json. Express 4 vs Express 5, Fastify 3 vs Fastify 4: version differences are handled automatically.
End-to-end fixture coverage as of today:
- Express has full producer plus consumer coverage, including response-type extraction and parameter and body-type extraction.
- Koa, Fastify, Hapi, and NestJS are each verified by an end-to-end fixture: a real app in that framework is scanned in CI and its routes asserted against the index.
- Hono has a code-present extraction path and should work in practice, but is not yet covered by an end-to-end fixture, so report a bug if a route does not appear in your index after a clean scan.
For unusual or in-house Express-flavoured routers, the scanner generally still picks them up because matching is on imported symbols and call shape rather than framework name. If your framework is detected but routes do not appear in the index, that is a bug worth reporting. If the framework is not detected at all, the dashboard report will say so explicitly.
Protocol coverage
Four protocols are indexed:
- REST/HTTP. Route handlers on the producer side,
fetch/axios/got/ky-style calls on the consumer side, with method-and-path matching and full request/response type extraction. - GraphQL. Producers are SDL schemas:
.graphql/.gqlfiles andgql/graphqltagged template literals containing type-system definitions. Consumers are executable documents: tagged templates and.graphqlfiles containing operations. Extraction is parse-based (no LLM), so a document the parser can’t handle is a coverage gap, never a false positive. Out of scope by design: Relay compiled artifacts, persisted-query manifests, and code-first schemas (Pothos, TypeGraphQL, Nexus) unless an emittedschema.graphqlis committed — the report suggests committing one when GraphQL libraries are detected but no operations were extracted. - WebSockets. Events are keyed by event name plus direction, matched on call shape rather than a specific library: listeners (
socket.on("x", ...)) produce the key; emitters (socket.emit("x", ...)) consume it. Socket.IO is the most-tested client, and any realtime library with the same on/emit shape is picked up the same way. Only string-literal event names count: dynamic names, custom namespaces (io.of(...)), and CommonJSrequirebootstrapping are skipped — again a coverage gap rather than a false match. Reserved lifecycle events (connect,disconnect, …) never become contract events. In MCP responses these rows carry the protocol tagsocket. - Pub/sub (early). Topic-keyed publish/subscribe over message brokers — Kafka (kafkajs), NATS, Redis pub/sub, and any client with the same send-to-topic / handle-from-topic shape (detection matches call shape against the messaging clients found in your
package.json, not a fixed library list). A subscriber registers a handler for a topic and is the contract producer; a publisher sends to it and is the consumer; the two match on the exact topic string alone — the broker is recorded as a diagnostic, not part of the identity. Payload types are extracted when a named TypeScript type decodes the message. Caveats while support matures: only literal topic strings are extracted (a topic computed at runtime is skipped), wildcard/pattern subscriptions don’t match, request/reply round-trips and key-value or admin calls on the same client libraries are excluded, and extraction is LLM-assisted rather than parse-based — so treat a missing topic as a bug worth reporting.
What Carrick does not cover
These are out of scope at MVP and will stay that way unless we hear consistent demand.
Non-TypeScript services
Python, Go, Rust, Java, Ruby. Carrick relies on TypeScript’s compiler to extract type shapes. Porting the type sidecar to other languages is a large undertaking that does not move the needle for the TypeScript-monorepo problem Carrick exists to solve. If your stack is mixed, the TypeScript services will scan and the rest will be invisible.
Deno-native projects
A project with no package.json — configuration in deno.json, dependencies imported from jsr: or URLs — can’t be scanned: dependency discovery, framework detection, and type resolution all start from the manifest, so there is nothing for the scanner to stand on. A Deno project that also maintains a package.json (npm-compatibility mode) may scan partially. Every other mainstream setup — npm, pnpm, Yarn, or Bun — is expected to work; if yours doesn’t scan cleanly, that’s a bug we want to hear about.
Other protocols
- gRPC, tRPC over WebSocket, custom RPC frameworks. These do not produce operations in any of the shapes the scanner looks for, so they will not appear in the index even if the consumer and producer are both TypeScript.
- Async messaging beyond topic-keyed pub/sub. Publish/subscribe over Kafka, NATS, Redis and similar clients is indexed (see “Protocol coverage” above, with its early-support caveats). Queue semantics that don’t reduce to a named topic — work queues with competing consumers, delayed/scheduled jobs (BullMQ-style), request/reply over a broker, exchange/routing-key topologies — are not part of the contract surface.
Dynamic routing
Carrick reads your source, it does not run your code. The following patterns are invisible to it:
- Routes built at runtime from a config file, a database, or environment-driven feature flags.
- Routes registered from arrays in a loop where the path strings are computed from variables the scanner cannot resolve to literals.
- Routes added by a plugin system where the plugin path is determined after the process starts.
The bar is: static enough to read from source. If the path strings and method names are literal in source (or trivially resolvable to literals), Carrick finds them.
Compiled or generated code
.d.ts-only services without source. There is nothing to scan.- Generated routes that live only in
dist/at runtime. If the generator commits its output to the repo, the routes scan; if the generator runs at build time and the output is gitignored, they do not. - Pre-compiled bundles. The scanner operates on TypeScript source, not on compiled JavaScript bundles or minified output.
Monorepos
Polyrepos work out of the box: one repo, one service. Monorepos work too, in either of two shapes:
- Workspace monorepos (pnpm workspaces, npm workspaces, Yarn workspaces, Nx with per-app manifests), where each service directory has its own
package.json, are split into services automatically. - Single-root-
package.jsonmonorepos are split by declaring aservicesarray in carrick.json: one entry per service, each with its owndirectoryand optionalincluderoots andtsconfig. Each entry is scanned, type-checked, and indexed as its own service, so contract checks run between them rather than flattening the repo into one.
Related
- Introduction. What Carrick is for and what you can do with it.
- carrick.json. Split a monorepo into services and classify env-var-driven outbound calls.