Model or dataset
truecourse-ai/truecourse avatar
truecourse-ai/truecourse

truecourse: turning your PRDs and READMEs into tests that fail when the code drifts

Turns the documentation you already write into tests that run. A failing test means your product and your docs disagree, and names the section.

530 stars43 forksTypeScriptMIT

At a glance

What is it?
TrueCourse is a TypeScript CLI from truecourse-ai that ships two independent tools: a tree-sitter plus LLM analyzer for code defects, and guard, which curates your docs into a spec corpus, authors scenario tests bound to each section, and runs them deterministically. A failing scenario means that section and the code disagree.
Who is it for?
Adopt truecourse guard if your repository already carries PRDs, ADRs or READMEs that describe intended behaviour and you want a deterministic signal when the implementation stops matching a named section. Skip it if your documentation is aspirational rather than a specification, or if your team cannot supply an LLM provider, because scenario authoring is LLM-dependent even though guard run itself is not.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The defect truecourse guard is built to catch

Most test suites answer one question: does the code do what the code was written to do. They are silent on a second question that causes just as much damage. Does the code still do what the documentation says it does. A PRD that describes a refund window, an ADR that fixes a retry policy, a README that promises pagination defaults: none of these are executable, so nothing fails when the implementation quietly diverges. The divergence is discovered later, by a support ticket or an incident.

TrueCourse's guard tool targets exactly that gap, and the README is unusually direct about the mechanism: it curates your PRDs, ADRs and READMEs into a spec corpus, an LLM authors scenario tests bound to each spec section once, and guard run executes them deterministically. The binding is the part that matters. When a scenario fails, the failure names the section, so the reviewer knows whether the code or the prose is wrong. That is a different output from a normal test failure, which names a function.

The audience follows from that. This is for teams that already write specifications as part of their process, not teams hoping a tool will generate documentation for them. If your docs are marketing copy or a stale wiki, there is nothing to bind to.

Analyze and guard are two tools, not one pipeline

The README is explicit that TrueCourse catches two classes of defect through two independent tools, usable separately or together. That is a design decision worth reading carefully, because it means the project is not a single opinionated workflow.

The first tool, truecourse analyze, is a static analyzer with an LLM layer. Its deterministic half runs through tree-sitter AST visitors and covers eight categories: security, bugs, architecture, code quality, performance, reliability, database and style. The README's rule table lists 1,200+ deterministic rules and 100 LLM rules, with code quality the largest deterministic bucket at 500+ and performance the largest LLM bucket at 10. The deterministic rules make no API calls, which is why they can run in a worker thread with a per-file time budget; the README states the budget defaults to 30 seconds per file and is overridable with TRUECOURSE_DET_FILE_TIMEOUT_MS. A pathological file is skipped with a warning rather than freezing the run.

The second tool, truecourse guard, is the documentation-drift path described above. Both write into .truecourse/ as plain JSON, and both surface in the same dashboard. Keeping them separate means you can run the analyzer on a repository with no specs at all, which is most repositories.

Installing truecourse and running the first analysis

The README gives a single global install. It puts the truecourse command on your PATH, and every documented example uses that form.

bash
npm install -g truecourse

If you would rather not install globally, the README says any command can be run one-off as npx truecourse <command>. Node is a prerequisite; the README points to a Prerequisites section for Node, Claude Code and C# requirements, and the Dockerfile pins node:22.23.2-bookworm-slim, which is the version the project's own CI runs.

The first real use is the analyzer, run from inside your repository:

bash
truecourse analyze
truecourse list

The README states that analyze runs the full analysis in-process and that the first run creates .truecourse/ in your repo, storing results as plain JSON files. truecourse list then shows the violations it found. Nothing else is required to get output: there is no setup step and no database.

For LLM-powered work, the README says TrueCourse uses the Claude Code CLI by default or a provider API with your own key. Your first truecourse command asks which, and truecourse config llm setup changes it later. With neither available, deterministic analysis still runs and LLM-dependent features are skipped. That fallback is the honest part of the design: you get partial value with no credentials, and the tool says so rather than failing.

What lives in .truecourse and what you commit

The README draws a clear line through the .truecourse/ directory. Three files are committable and travel with the repository: LATEST.json, the most recent analysis snapshot, which doubles as the baseline for truecourse analyze --diff and the pre-commit hook; config.json, holding per-repo rule categories and LLM toggles; and hooks.yaml, created by truecourse hooks install.

Everything else is local-only and added to .truecourse/.gitignore automatically: analyses/, diff.json, history.json, ui-state.json, logs/ and .analyze.lock. The README also gives an explicit workflow for the first baseline:

bash
truecourse analyze
git add .truecourse/LATEST.json .truecourse/config.json
git commit -m "add truecourse baseline"

Then a warning that deserves to be read twice. Refresh the baseline by re-running analyze after merging to main and committing the updated LATEST.json, but do not commit LATEST.json from feature branches, because two PRs both updating it will conflict on a large generated JSON. This is the kind of concrete operational constraint that separates a tool you can adopt from one you cannot. The payoff is that LATEST.json is tracked, so git worktree add and fresh clones inherit the baseline through git, and truecourse analyze --diff works on the first commit in a new worktree with no per-checkout cold start. The diff result lands in .truecourse/diff.json, which is gitignored per checkout.

Where truecourse is the wrong tool

The guard path has a dependency the README does not hide: an LLM authors the scenario tests. The README states that with neither Claude Code CLI nor a provider API key available, LLM-dependent features are skipped. Scenario authoring is LLM-dependent, so a team with no provider cannot use guard at all, even though guard run executes deterministically once the scenarios exist. That is a real adoption barrier, not a configuration nuisance.

There is a second, subtler failure mode. The binding is only as good as the prose it binds to. A README section that says the API is fast and reliable gives an LLM nothing to assert against. Vague documentation produces vague scenarios, and vague scenarios that pass are worse than no scenarios, because they create false confidence in a section that was never really specified. The tool cannot fix underspecified docs, and the README does not claim it can.

Finally, the analyzer is not a substitute for a type checker or a compiler. Its deterministic rules run over tree-sitter ASTs, and a rule that fires on a pattern is not the same as a proof of a bug. Treating the violation list as a queue of mandatory fixes will produce churn, particularly in the style and code quality categories, which together account for the majority of the 1,200+ deterministic rules.

truecourse versus a conventional end-to-end suite

The obvious alternative is a hand-written end-to-end suite: Playwright, Cypress or a similar runner, with tests written by a person who read the spec. The difference in approach is where the binding lives. In a hand-written suite, the link between a test and the document section it enforces exists only in the author's head and, if you are disciplined, in a comment. When the spec changes, nothing tells you which tests to update.

TrueCourse inverts that. The spec corpus is the input, scenarios are generated bound to each section, and guard run executes them deterministically. A failure points at a section rather than a test name. The trade-off is control. A hand-written Playwright test can assert anything you can express in code, including timing, network interception and visual state. A generated scenario is bounded by what the LLM inferred from the section text, and the README does not describe an escape hatch for hand-editing a scenario after generation. Teams with an existing, well-maintained E2E suite are likely better served by adding a comment convention that links tests to doc anchors than by regenerating that suite.

The analyzer half has a closer analogue in conventional linters such as ESLint, which the README implicitly positions against by describing categories that linters cover and ones they do not reach: circular dependencies, layer violations, dead modules, race conditions. A linter is configured rule by rule and runs in milliseconds; TrueCourse's deterministic pass is broader in scope but runs a worker thread with a per-file time budget, which is a heavier operation. The two are complementary rather than competing, and the README's own framing of analyze as covering both linter territory and beyond supports running it alongside an existing lint setup rather than replacing it.

Licence, self-hosting and the cost of staying current

The repository is MIT licensed, and the package.json at the root carries "license": "MIT". The root package.json is also marked "private": true, which means the repository itself is not the published artifact; the npm package installed by npm install -g truecourse is. MIT terms permit commercial use and modification, but the repository also contains an ee/ directory and a set of @truecourse/ee-* workspace packages, which suggests a separation between the MIT-licensed core and enterprise components. The README does not describe what the ee/ packages contain or under what terms they are offered, so anyone planning to self-host should read the licence files in that directory rather than assume the root MIT declaration covers everything. This is a factual gap in the documentation, not a legal opinion.

Self-hosting the dashboard is a separate exercise from the CLI. The docker-compose.yml defines two services: db, running postgres:16 with a named volume tc-pgdata and a port published only on 127.0.0.1:5432, and app, built from the repository Dockerfile and exposed on 3001. The compose file's own header comments state that WORKOS_* and DATABASE_URL are required and that the server boots authenticated and on Postgres or not at all. The .env.example lists DATABASE_URL, WORKOS_API_KEY, WORKOS_CLIENT_ID, WORKOS_COOKIE_PASSWORD, WORKOS_REDIRECT_URI and WORKOS_APP_URL as required, and the GitHub App variables as needed for repository connection. That is a meaningful operational surface for a tool whose CLI half needs none of it.

Upgrade cost is dominated by the baseline file. The release history shows a run of v0.8.0-next.* and v0.8.1-next.0 prereleases in early August 2026, and the last push to the repository was on 2026-09-15. The README does not document rollback if a new analysis snapshot changes the violation set substantially, so the practical safeguard is the one the README already gives: commit LATEST.json from main and review the diff before merging.

Editorial conclusion

Adopt truecourse guard if your repository already carries PRDs, ADRs or READMEs that describe intended behaviour and you want a deterministic signal when the implementation stops matching a named section. Skip it if your documentation is aspirational rather than a specification, or if your team cannot supply an LLM provider, because scenario authoring is LLM-dependent even though guard run itself is not. Before committing a baseline, verify three things: that your docs are specific enough to bind a scenario to a section, that .truecourse/config.json selects the rule categories you actually want, and that LATEST.json is committed from main only, since the README warns that two feature branches updating it will conflict on a large generated JSON file.

Frequently asked questions

Does truecourse need an API key to run?

Not for deterministic analysis. The README states that with neither the Claude Code CLI nor a provider API key available, deterministic analysis still runs and LLM-dependent features are skipped. The guard scenario authoring step is LLM-dependent, so it is among the features that get skipped.

What does truecourse store in the .truecourse directory?

The README says results are stored as plain JSON files, with LATEST.json, config.json and hooks.yaml committable, while analyses/, diff.json, history.json, ui-state.json, logs/ and .analyze.lock are local-only and added to .truecourse/.gitignore automatically.

Which languages does truecourse analyze?

The README header states JavaScript, TypeScript, Python and C#. The deterministic rules run through tree-sitter AST visitors, and the repository's own primary language is TypeScript.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. truecourse-ai/truecourse on GitHub
Community notes

Community notes