# AGENTS.md

Guidance for AI coding agents working in the AgentLitmus repository.

## Project overview

AgentLitmus is a Next.js 15 (App Router) + TypeScript app that scans a URL,
scores it across 10 "agent readiness" signals (llms.txt, AGENTS.md,
structured data, robots.txt, sitemap, etc.), and serves a shareable report
page, an embeddable SVG badge, and a methodology page. Scans are stored in
SQLite via Drizzle ORM.

## Setup

- Install dependencies: `npm install`
- Run database migrations: `npm run db:migrate`
- Start the dev server: `npm run dev`

## Testing

- Run the unit test suite: `npm test` (vitest)
- Type-check: `npx tsc --noEmit`
- Lint: `npm run lint`
- Self-scan (scans the running local instance and prints the report): `npm run self-scan`

## Code layout

- `src/lib/scanner/` — the scanner core: fetcher, robots parser, scoring, and
  one module per signal under `src/lib/scanner/signals/`.
- `src/app/` — Next.js routes, including the homepage, `/report/[id]`,
  `/badge/[filename]`, and `/methodology`.
- `src/db/` — Drizzle schema and query helpers.

## MCP server

AgentLitmus also exposes a Model Context Protocol server over Streamable HTTP
at `/api/mcp` (manifest at `/.well-known/mcp.json`), with two tools:
`scan_site` (scan a URL) and `get_scan_report` (fetch a stored report by id).
Both tools share the rate-limiting, SSRF, and persistence logic in
`src/lib/scan-service.ts` with the REST `/api/scan` route — don't duplicate
that logic. The tool definitions in `src/lib/mcp/tools.ts` are the single
source of truth for both the manifest and the `tools/list` response.

## Security notes

- `src/lib/supabase/admin.ts` creates a service-role Supabase client
  (`SUPABASE_SERVICE_ROLE_KEY`). It is only ever imported from `"use server"`
  modules (currently `src/lib/account-actions.ts`) — never import it from a
  client component, and never prefix the env var with `NEXT_PUBLIC_`.
- `user_sites` has RLS policies (`user_sites_select_own`, `_insert_own`,
  `_delete_own`, all `TO authenticated USING/WITH CHECK (auth.uid() =
  user_id)`) — see `drizzle/0001_giant_jimmy_woo.sql`. These are defense in
  depth: the app's Postgres connection (`src/db/client.ts`) uses a direct
  connection, not the `authenticated` PostgREST role, so the real ownership
  boundary is the `auth.getUser()` checks in `src/lib/site-actions.ts`. The
  Drizzle-based test suite can't exercise `auth.uid()` (it doesn't run as
  `authenticated` with a JWT), so verify the RLS policies manually against a
  real Supabase project: as user A, `select`/`insert`/`delete` your own
  `user_sites` rows via the PostgREST/`authenticated` role (e.g. through the
  Supabase JS client with user A's session) — confirm user A cannot
  read/insert/delete rows where `user_id` is user B's id.
- Claiming sites (`claimSite`, `claimDomains` in `src/lib/site-actions.ts`) is
  rate-limited to `CLAIM_LIMIT` (20/user/day, see `src/lib/rate-limit.ts`) via
  `checkRateLimit`, keyed by `claim:{userId}`.

## Conventions

- Signal modules are the single source of truth for scoring: each exports an
  `id`, `name`, `maxPoints`, `description`, `group`, and a `check()` function.
  The methodology page renders directly from this metadata, so update it
  there rather than duplicating descriptions elsewhere.
- Keep signal `check()` functions pure and side-effect free; all network
  access happens in `src/lib/scanner/fetcher.ts`.
- Add a fixture-based test under `src/lib/scanner/__tests__/` for any new or
  changed signal logic.
