Skip to main content
spec scan walks your repo’s documentation (PRDs, ADRs, RFCs, READMEs, design notes), plus any registered llms.txt site, and curates it:
  1. Pre-filter: a deterministic (zero-LLM) pass drops whole non-spec directory trees the classifier can’t separate by content: agent-config trees (agents/rules/**, agents/skills/**), changelogs/release-notes, and template/boilerplate dirs. (.truecourse/, node_modules/, .git/ etc. are always skipped.)
  2. Relevance filter: an LLM drops obvious non-spec material (task lists, research logs, AI agent prompts). Dropped docs are recorded with a reason so you can force-include them later.
  3. Area tagging: each kept doc is tagged into areas (product/concern), grouping the corpus by what part of the product it describes.
  4. Overlap flagging: within each area, the LLM flags overlaps where two docs may disagree. Only genuine disagreements flag; docs that agree never surface. You resolve them via spec conflicts or the dashboard.

Which documents are scanned

Markdown: every .md, .mdx, .markdown, .mdown, and .mkd file outside build and vendor directories. MDX is scanned like any other markdown: headings, prose, and fenced code are read normally and JSX passes through untouched, so docs sites built on Mintlify, Docusaurus, or Nextra are covered without extra configuration. OpenAPI / Swagger documents are auto-detected as spec sources too. A .yaml, .yml, or .json file whose top level declares an openapi or swagger version is admitted into the corpus automatically (structurally, without the relevance filter), and each of its operations (an HTTP method on a path) becomes a guardable spec section: guard generate authors api-driver scenarios against them, and editing an operation flips its scenario stale. Ordinary config (package.json, lockfiles, compose files) is never mistaken for a spec. Split specs are supported: in-file $refs and external $ref targets are resolved (confined to the repo; cycles terminate; the resolved document is capped at 5 MB). Files with any other extension are never discovered; a force-include bypasses the relevance filter, not discovery.

Output

Cost estimate and caching

Before calling the LLM, spec scan prints a deterministic, offline token + ceiling-cost estimate and asks for confirmation (-y / --yes skips the prompt). The real bill lands at or below the estimate. Every per-doc stage is cached content-keyed under .truecourse/.cache/, so re-running a scan only pays for docs that actually changed, and the estimate reads the real caches so it reports “N of M docs changed”. When nothing changed, the confirm prompt is skipped entirely. The cache is gitignored and safe to delete.
If a scan is interrupted (e.g. an LLM usage limit), just re-run it; it resumes from the cached successes instead of starting over.

Curating the corpus

The relevance filter is a judgment call you can override, per doc:
include and exclude accept multiple paths and trigger a single re-scan. Overrides are stored in specs/decisions.json, so they survive future scans and travel with the repo. The dashboard’s Guard → Coverage view surfaces the skipped docs too, with one-click include.

Scoping the scan

Doc discovery has an optional per-repo include-scope in .truecourse/config.json under spec (a gitignore-style glob list): when present and non-empty, only files matching a glob enter the scan universe, markdown and OpenAPI docs alike (absent or [] = everything; scope narrows discovery, it can’t widen it to other file types). .truecourseignore and the relevance filter still run on top. Web-source pages are exempt from both; registering the source is already the opt-in.

Next steps

Web sources

Register llms.txt documentation sites as extra spec docs.

Resolving conflicts

Review the flagged overlaps: pick a side or dismiss.