Repository navigation
Feature: Qa System
Thatch ships a suite of QA use cases that verify end-to-end behavior. Each use
case is a bun:test file that runs in an isolated environment — a
temp-directory copy of the repo, not the working tree itself. Use cases are
split into automatable (no LLM, fast) and live (spawns opencode, costs
model tokens).
- Automatable use cases run without an LLM — they test CLI behavior, hooks, DB operations, and tool execution directly via bun assertions
- Live use cases spawn real opencode sessions — they test the full nudge/extraction/skill pipeline with a real model
-
misetasks for running subsets:qa(all),qa-auto(fast),qa-live(model),qa-dry-run(list only) - Each use case follows a structured format: preconditions, steps, expected results
Each use case file (tests/qa/auto/uc-NNN-name.ts or
tests/qa/live/uc-NNN-name.ts) imports registerUseCase and UseCase from
../runner, defines the scenario with preconditions, steps, and expected
as string arrays joined by newlines, then calls registerUseCase(useCase).
Most use cases use the default runViaOpencode (no custom run). Automatable
use cases override run with direct CLI/bun assertions. Manual-only use cases
set manualOnly: true.
Individual use case files are .ts (not .test.ts) so bun does not discover
them as standalone test files. Each directory has an index.test.ts barrel
file that imports all use case modules. Bun's --concurrent flag
parallelizes tests within a single file, not across files. The barrel pattern
registers all use cases in one suite, enabling --concurrent --max-concurrency 5 to run 5 use cases at once (33s vs 88s sequential for the
auto suite).
Run without an LLM. Test CLI behavior, hooks, DB operations, and tool execution directly via bun assertions. These are fast and run in parallel.
| UC | Name | What it tests |
|---|---|---|
| UC-004 | cli-inspection |
stores, list, show, forget, search subcommands |
| UC-005 | setup-install | setup installs correct files for Claude Code and Cursor |
| UC-008 | hygiene-heartbeat | hygiene report at session start (duplicates, stale, orphaned) |
| UC-009 | flush-tools-tiers | three priority tiers: extraction, recall, write nudge |
| UC-011 | write-time-similarity-warning |
memory_remember warns about near-duplicates |
| UC-012 | model-migration | switching THATCH_MODEL makes old entries invisible |
| UC-014 | skill-install-drift |
installSkills overwrites drifted content |
| UC-015 | env-override-matrix |
THATCH_RECALL_THRESHOLD, THATCH_PREDICTION_THRESHOLD, THATCH_BEHAVIOR_THRESHOLD
|
| UC-016 | concurrent-session-isolation | sessions don't interfere with each other |
| UC-017 | buffer-tool-vs-buffer-batch | Cursor per-tool vs Claude Code batch queueing |
| UC-018 | mcp-startup-setup-detection |
checkSetup at MCP server startup |
| UC-021 | prediction-autofire | prediction nudge fires at chat.message
|
| UC-022 | prediction-dedup | cosine >= 0.85 dedup for matchers and predictions |
| UC-023 | prediction-confidence-model | Bayesian posterior with confirm/disconfirm/soft |
| UC-024 | prediction-delete | semantic-match deletion with cascade |
| UC-025 | behavior-autofire | behavior nudge fires at chat.message
|
| UC-026 | behavior-dedup | cosine >= 0.85 dedup for matchers and behaviors |
| UC-027 | behavior-confidence-model | ham/spam adjusts Bayesian confidence |
| UC-028 | behavior-delete | semantic-match deletion with cascade |
Spawn real opencode sessions. Test the full pipeline with a real model. These
cost model tokens and have a 20-minute per-use-case timeout. Live use cases
check for opencode on PATH at runtime: if it is present they run; if not,
they skip with [MANUAL]. Three (UC-006, UC-010, UC-013) are
manualOnly: true for infrastructure reasons (Cursor hook lifecycle, nested
opencode session timeout, compaction trigger). Run them locally with
mise run qa-live.
| UC | Name | What it tests |
|---|---|---|
| UC-001 | memory-roundtrip | remember -> recall -> show -> forget |
| UC-002 | dedup-cycle |
find_duplicates -> classify -> merge -> mark_checked
|
| UC-003 | extraction-nudge | tool use -> buffer -> nudge -> extraction_done
|
| UC-006 | cursor-hook-lifecycle | Cursor hooks: sessionStart, postToolUse, beforeSubmitPrompt
|
| UC-007 | recall-nudge | prompt-aware recall at chat.message
|
| UC-010 | prime |
thatch prime runs the project-primer skill |
| UC-013 | compaction-context | compaction hook injects context |
| UC-019 | archived-memory-lifecycle | archive -> search excludes -> unarchive -> search includes |
| UC-020 | extraction-escalation | polite -> insistent -> ALL-CAPS over missed nudges |
| Task | Command | Purpose |
|---|---|---|
qa |
bun test tests/qa/auto/index.test.ts --concurrent --max-concurrency 5 && bun test tests/qa/live/index.test.ts |
Run all QA (auto first, then live) |
qa-dry-run |
QA_DRY_RUN=1 bun test tests/qa/auto/index.test.ts tests/qa/live/index.test.ts --concurrent --max-concurrency 5 |
List use cases without spawning sessions |
qa-auto |
bin/qa-run auto |
Run automatable only (fast, no LLM). Pass UC names as args to select a subset. |
qa-live |
bin/qa-run live |
Run live only (spawns opencode, costs tokens). Pass UC names as args to select a subset. |
Every task that runs opencode-driven use cases runs one leg per
DISCOVERED opencode install, with test names suffixed
(UC-001-memory-roundtrip [v1], UC-001-memory-roundtrip [v2]) when
there are several. There is no separate matrix command - the ordinary
tasks just do the matrix based on what is available.
Discovery (tests/qa/binaries.ts) asks the package manager where its
formulas are installed (brew --prefix opencode, brew --prefix opencode-v2), falls back to the PATH-resolved binary, validates each
candidate by running --version, tags it by major (v1, v2 - the
version output decides, not the formula name), and dedups candidates
that resolve to the same binary. A machine with one install runs
single-leg with unsuffixed names, so single-binary setups behave
exactly as they did before the matrix existed.
A use case opts into or restricts the matrix with hosts: ["v1"] /
["v2"] / ["v1", "v2"] on the UseCase:
- Live use cases (no custom
run) are matrixed by default - the defaultrunViaOpencodepath spawns opencode by definition. - Automatable use cases keep a single leg unless they declare
hosts- the fast suite is not doubled for cases that never touch opencode. Declarehostswhen a customrunspawns opencode itself (UC-098) or drives a serve (UC-100).
QA_HOSTS=v1 (comma-separated) narrows the legs when iterating against
one major; a value matching no discovered install registers nothing
with an explanatory line.
Legs get separate fixtures (the fixture directory is keyed by test name suffix) because hosts cannot share one: a v2 session db in a shared fixture fails the v1 leg's serve with "Database is not empty and has no session table".
The v1 and v2 run CLIs disagree on flags (v1 has --dir, which v2
dropped; v2 needs --standalone to skip the background daemon, which a
fixture sandbox must not boot). opencodeRunArgs(ctx, prompt) in the
runner builds the right invocation per leg by version-detecting the
binary the leg's PATH selects; runViaOpencode and UC-098's custom runs
go through it.
The qa task runs auto first via &&. Auto failures stop before live runs.
The qa-live task does not use --concurrent — live sessions are
resource-heavy and benefit from sequential execution.
tests/qa/runner.ts provides the registerUseCase function and the UseCase
type. The runner index files (tests/qa/auto/index.test.ts and
tests/qa/live/index.test.ts) import all use case files in their directory,
which self-register via registerUseCase. The runner then executes each use
case.
The QA_DRY_RUN=1 environment variable causes the runner to print use case
names and descriptions without executing them. This is useful for CI and for
reviewing coverage without spawning sessions.
The runner creates a master copy of the repo at /tmp/thatch-qa/master/
via git archive HEAD (tracked files only — no .git, no node_modules, no
untracked artifacts). node_modules is symlinked from the real repo to share
deps including the HF embedding model cache. opencode's own npm deps
(~/.config/opencode/node_modules/) are copied into the master so each use
case has them without a warm-up session.
Each use case gets a per-use-case copy of the master via createFixture(name),
with isolated HOME, XDG_CONFIG_HOME, THATCH_DB_PATH, and
THATCH_QUEUE_DIR environment variables. The repo is read-only during QA —
sessions run inside /tmp/thatch-qa/<uc-name>/ copies.
The qa skill (.opencode/skills/qa/SKILL.md) instructs the
LLM on how to execute a single use case in the isolated directory. The skill's
safety contract is intentionally fixed text — do not paraphrase it.
-
Live session (default): no custom
runfunction →runViaOpencodespawnsopencode run --dir <ctx.dir> --model <MODEL> --autowith the use case content as prompt. RequiresVENICE_API_KEY. Default model:venice/zai-org-glm-5-2(override withQA_MODELenv var). UsesBun.spawn()(notBun.$) so the process can be killed on timeout (590s SIGTERM, SIGKILL in finally). The 20-minute per-test timeout is the wall-clock budget for a complete LLM-driven agent session — do not reduce it. -
Automatable: custom
run(ctx)function with direct CLI/bun assertions. No opencode session, no API key. -
Manual-only:
manualOnly: true→ test is skipped, prints[MANUAL].
mise run check uses a flat glob (tests/*.test.ts) that excludes
tests/qa/. QA tests spawn opencode sessions and cannot run in the normal
quality gate. The QA files are typechecked by tsconfig.check.json (which
includes all of tests/), just not executed during check. See
cicd.md for the full CI pipeline.
- All features: QA use cases verify end-to-end behavior of every feature
-
cicd.md:
mise run checkuses a flat glob (tests/*.test.ts) that excludestests/qa/; CI runsbun testwhich includes the QA barrels;mise run qaruns the full QA suite separately - setup.md: UC-005 and UC-018 test setup installation and detection
- nudge-pipeline.md: UC-007, UC-009, UC-021, UC-025 test nudge tiers
- extraction.md: UC-003, UC-020 test extraction nudge and escalation
- hygiene.md: UC-008 tests the hygiene heartbeat
- memory-store.md: UC-001, UC-004, UC-011, UC-012, UC-019 test memory CRUD, search, warnings, migration, archival
- deduplication.md: UC-002 tests the dedup cycle
- prediction-engine.md: UC-021 through UC-024 test auto-fire, dedup, confidence, delete
- behavior-engine.md: UC-025 through UC-028 test auto-fire, dedup, confidence, delete
- ../skills.md: UC-014 tests skill drift detection
- multi-host.md: UC-006, UC-017 test Cursor hooks and buffer-tool vs buffer-batch
| File | Role |
|---|---|
tests/qa/runner.ts |
registerUseCase, UseCase type, ensureMaster, createFixture, runViaOpencode, execution logic |
tests/qa/auto/index.test.ts |
Automatable use case barrel — imports all auto use case modules |
tests/qa/live/index.test.ts |
Live use case barrel — imports all live use case modules |
tests/qa/auto/uc-NNN-*.ts |
Automatable use case files, one per use case |
tests/qa/live/uc-NNN-*.ts |
Live use case files, one per use case |
No separate QA docs directory exists. QA use cases live in tests/qa/ as executable tests, not docs.
- Automatable use cases never spawn an LLM. They test CLI, hooks, DB, and tool execution directly.
-
Live use cases spawn real opencode sessions and cost model tokens. They
skip with
[MANUAL]whenopencodeis not on PATH; three aremanualOnly: truefor infrastructure reasons. -
QA_DRY_RUN=1lists use cases without executing. Use this for CI and coverage review. - Each use case follows the structured format: preconditions, steps, expected.
-
Use cases self-register via
registerUseCase— no central registry to maintain. Add a new.tsfile and import it from the barrel. -
The repo is read-only during QA. Sessions run inside
/tmp/thatch-qa/<uc-name>/copies created fromgit archive HEAD. - The 20-minute test timeout is the agent-session budget, not test execution time. Reducing it will cause live use cases to time out.
User
- Guide: Behavior Engine
- Guide: Cli
- Guide: Code Review
- Guide: Commands
- Guide: Cross Session Chat
- Guide: Deduplication
- Guide: Default Behaviors
- Guide: Extraction
- Guide: Hygiene
- Guide: Memory
- Guide: Notifications
- Guide: Prediction Engine
- Guide: Overview
- Guide: Setup
- Guide: Skills
- Guide: Watchers
Developer
Dev Feature Guides
- Feature: Behavior Engine
- Feature: Cicd
- Feature: Cli
- Feature: Commands
- Feature: Compaction Recovery
- Feature: Cross Session Chat
- Feature: Database
- Feature: Deduplication
- Feature: Extraction
- Feature: Hygiene
- Feature: Memory Store
- Feature: Multi Host
- Feature: Notifications
- Feature: Nudge Pipeline
- Feature: Opencode Plugin
- Feature: Prediction Engine
- Feature: Qa System
- Feature: Overview
- Feature: Repo Identity
- Feature: Session Lifecycle
- Feature: Session Tabs
- Feature: Setup
- Feature: Sideband
- Feature: Watchers