AiRaccoon manual checklist
The hand-run pass over a live build: the things dotnet test cannot answer because they need a
real install, a real server and a real bank.
A checklist is only worth the evidence behind it. The whole design below exists to keep a filled
checklist distinguishable from a plausible-sounding one — read "How this checklist rots" before
adding, removing or "tidying" anything here, because every failure listed there was found in a
real predecessor and each is easy to reintroduce.
When NOT to use
- Anything
dotnet test already covers. This pass is for behaviour that only appears in a real
install; duplicating unit or integration coverage by hand buys nothing and goes stale.
- Judging a diff or a PR — that is a code review, not a build verification.
- Debugging one failing symptom. Run the checklist to find out what is broken; trace why
somewhere else.
Never
These three protect the person running the checklist, not the checklist:
- Never write to the user's live bank at
~/.ai-raccoon. Read it only through ?mode=ro /
PRAGMA query_only=1. The checklist's own writes go to a scratch data root you create with
--data-root.
- Never bind the default port (7721) for a step that starts a server — the user's own server
is usually on it, and
--restart will cycle theirs. Use --port 0 and read the bound port back.
- Never mark an item accepted from a plan. Accepted means you ran the command and read its
output.
Process
Copy templates/checklist-template.json to
docs/work/checklist/<yyyy-mm-dd>-<what-you-are-checking>.json, creating the directory if
needed. Results live under docs/work/checklist/ — never the repo root, and never
.ai-raccoon/, which is a bank directory, not a reports directory.
Derive the facts the checklist compares against, before running anything, and write them
into derived. Run python3 scripts/derive-facts.py <repo-root> (the root defaults to the
current directory); it prints the version, the MCP tool count and the prompt count, and it
fails loudly — exit 1, with the reason and a standing instruction on stderr — when any of them
comes back empty or zero, rather than handing you a confident 0. A count typed from memory
or copied from a previous run is a second copy of a number that already exists, and it goes
stale silently.
The checklist then asserts that the running binary matches what the tree says. That
comparison is the point: it is the only step that can catch a build that installed something
other than what you think you are testing.
Work each item, filling in every field:
command — the exact command or tool call you ran.
evidence — the output you read, verbatim, trimmed to the deciding lines.
observed-result — what it means.
status — pass, fail, skipped or substituted. A skipped needs a reason; it is not
a pass. substituted means the behaviour was checked by a different instrument than the item
names — an automated test standing in for a path you could not drive live, say. Folding that
into pass overstates it and into skipped understates it, so it gets its own word, and the
reason must name the instrument. Record any deliberate deviation from the item's stated
method the same way.
accepted + acceptance-reason — whether the observed result is acceptable, and why. A
fail may still be accepted as a known, tracked defect, as long as the reason names where it
is tracked.
accepted starts as null, meaning nobody has answered yet. Leave it null until you decide;
a run with any null left in it is unfinished, not a run with no objections.
Never inherit a prior verdict's reason. Where an item was partial, fail, skipped or
substituted last time, re-derive why against the source in front of you before reusing the
explanation. A previous run blamed a job's cadence for three event ids never firing; the real
cause was that they log only when there is something to purge, and a fresh bank has nothing.
The outcome matched, so the wrong mechanism survived a release — and it discouraged seeding the
data that would have exercised them. A reason that is right about the outcome and wrong about
the cause is the hardest kind of stale fact to see.
When a run finds that an earlier one was wrong, that belongs in findings-against-prior-runs,
not buried in an item. A checklist that can only describe the current build cannot report that
the last checklist lied, and those findings are often the most valuable thing a run produces.
Items whose feature no longer exists get deleted from the template, not marked skipped. A
step for a removed feature is worse than no step: it either fails forever and gets waved
through, or it quietly "passes" against nothing.
Report counts by status, every fail with its evidence line, and every finding against a prior
run. A run where some item has no command or no evidence is not complete — say so instead
of reporting a total.
Items are independent, so lanes can run in parallel against one packed binary. The axis that keeps
them from colliding is the data root: give every lane its own --data-root, and nothing else needs
coordinating.
Making an item able to fail
Most of these steps can be written so that they pass whether or not the feature works. Three
shapes account for nearly all of it:
- A filter needs a negative control. Feed clean content alongside the shapes you expect
rejected. A policy that rejects everything passes a rejection-only test, and looks healthiest
exactly when it is most broken.
- A semantic-retrieval query must be one that keyword match cannot carry. Query for
"an antique navigation instrument reflecting evening light in a stargazing room" against content
that says "astrolabe", "lamplight", "observatory" — no literal overlap, so a dead vector leg
actually fails the item. A query sharing words with the stored text passes on BM25 alone and
tells you nothing about the half you meant to test.
- An empty list is a weak check. A queue or candidate list on a fresh bank returns
[]
whether it works or is broken. Create the thing first, then assert on its content — the score,
the reasons, the identity — not on the shape of the response.
Scope
Derived per run from the product, not from this list. The headings below are the stable shape of
the pass; the specific steps under each come from what step 2 found and from what is actually
registered in the build in front of you.
- Build and install — Release build, pack, force-update the global tool, and
--version
matches the derived version.
- Server lifecycle — the server starts and
--restart cycles the one it finds, both on a
non-default port.
- Write path — a write stores and returns a hash; a rejected write says so, with a reason,
rather than returning a fabricated entry.
- Read path — search returns the written entry, get returns its content by hash, and a
file#section anchor resolves its exact chunk.
- Noise filtering — each registered write-path policy rejects what it claims to, and the
rejected content stays retrievable from the noise store. Check which policies are registered
before writing steps for them.
- Read-path query guard — the refuse and annotate tiers behave as specified, and any detector
that ships disabled is still disabled until explicitly armed.
- File watch — watch status reflects live registrations.
- Promotion queue — the promotion list reports candidates accurately.
- Full MCP surface — every derived tool and prompt is reachable.
- Observability — emitted event ids resolve against the logging event-id reference.
Anchor each item to the decision record that defines the behaviour, so a step whose ADR was
superseded is easy to spot and delete.
How this checklist rots
Two predecessors were deleted after both drifted the same six ways. Each defence below is here
because its absence already caused a silent failure:
- Facts pinned by hand. One asserted
--version → 1.9.1 and "25 tools" while the tree was at
1.12.0 with 26 tools. The pins had been wrong for three releases and nothing noticed, because
the only thing comparing them was a human reading two numbers. → step 2 derives them.
- Steps for a deleted feature. Both still tested a noise policy that had been removed by a
later ADR. A step whose subject does not exist cannot pass honestly. → step 4 deletes them.
- Results written into a bank directory. Reports landed in
.ai-raccoon/, the directory name
the product uses for banks. → step 1 fixes the destination.
- No evidence field. The template recorded a claim with no room for the command or the output
behind it, so a filled checklist and an invented one were indistinguishable afterwards. →
command and evidence are required.
- Booleans that cannot express "skipped".
checked/accepted flags collapsed three states
into two: an unrun item and a failed one both read as false/false. → status is a tri-state
and accepted starts null.
- Two drifting copies. Two directories each held a copy, and one had lost its
templates/
directory entirely, so its own step 1 pointed at a file that was not there. → one copy, and the
template ships beside the skill.
Retiring a checklist skill means deleting it from every root it can load from, not from the
one you were looking at. The deletion that removed the two .ai-badger/skills/learned/ copies
missed ~/.hermes/skills/, where a third copy was found still installed long afterwards, still
carrying its templates/, still pinning an expected version three minors stale. It was the
first hit for someone searching for this checklist, and they began executing it; that copy has
since been removed. Enumerate the roots — the project's
.ai-badger/skills/learned/, .claude/skills/, ~/.claude/skills/, ~/.hermes/skills/ — and
confirm the removal in each. The stale copy wins whoever searches first, so a copy you did not
delete is not dormant; it is the one in use.
Gotchas
- A count that cannot fail is not a count. Every counting primitive here has a silent zero
in it:
rglob over a directory that no longer exists yields nothing, sum(()) is 0, and
grep -c across several files prints one number per file so a bad sum still looks like a
number. None of them raise. derive-facts.py closes that by treating zero as a failure rather
than a result — it exits 1 and says which fact it could not derive — and by counting only lines
that are code. Do not "simplify" either rule away: a file that documents [McpServerTool] in a
/// comment already inflated the surface by one for three releases, and the number looked
derived the whole time. Both properties have tests in the framework; change the script there,
never by editing your copy.
--port 0 binds an ephemeral port, so the port must be read back from the server's own output.
Assuming a port here is how a checklist step ends up talking to somebody else's server.
- Force-updating a global tool can silently keep the previous build if the pack step failed
earlier in the same run. The version comparison in step 2 is what catches it; do not skip it
because the build "looked fine".
- A scratch
--data-root must be a path the running user can create. Pointing it inside a
read-only or root-owned directory produces failures that look like product bugs.
1---2name: ai-raccoon-manual-checklist3description: Use when hand-verifying a live AiRaccoon build — a pre-flight or release checklist, a manual smoke test after installing the global tool, a "does this actually work end to end" pass before shipping, or any question `dotnet test` cannot answer because it needs a real install, a real server and a real bank. Derives the version and tool surface from the product instead of pinning them, records the command and the output behind every answer, and writes the filled checklist to docs/work/checklist/.4license: MIT5---67# AiRaccoon manual checklist89The hand-run pass over a live build: the things `dotnet test` cannot answer because they need a10real install, a real server and a real bank.1112A checklist is only worth the evidence behind it. The whole design below exists to keep a filled13checklist distinguishable from a plausible-sounding one — read "How this checklist rots" before14adding, removing or "tidying" anything here, because every failure listed there was found in a15real predecessor and each is easy to reintroduce.1617## When NOT to use1819- Anything `dotnet test` already covers. This pass is for behaviour that only appears in a real20 install; duplicating unit or integration coverage by hand buys nothing and goes stale.21- Judging a diff or a PR — that is a code review, not a build verification.22- Debugging one failing symptom. Run the checklist to find out *what* is broken; trace *why*23 somewhere else.2425## Never2627These three protect the person running the checklist, not the checklist:2829- **Never write to the user's live bank** at `~/.ai-raccoon`. Read it only through `?mode=ro` /30 `PRAGMA query_only=1`. The checklist's own writes go to a scratch data root you create with31 `--data-root`.32- **Never bind the default port** (7721) for a step that starts a server — the user's own server33 is usually on it, and `--restart` will cycle theirs. Use `--port 0` and read the bound port back.34- **Never mark an item accepted from a plan.** Accepted means you ran the command and read its35 output.3637## Process38391. Copy `templates/checklist-template.json` to40 `docs/work/checklist/<yyyy-mm-dd>-<what-you-are-checking>.json`, creating the directory if41 needed. Results live under `docs/work/checklist/` — never the repo root, and never42 `.ai-raccoon/`, which is a bank directory, not a reports directory.43442. **Derive the facts the checklist compares against, before running anything**, and write them45 into `derived`. Run `python3 scripts/derive-facts.py <repo-root>` (the root defaults to the46 current directory); it prints the version, the MCP tool count and the prompt count, and it47 fails loudly — exit 1, with the reason and a standing instruction on stderr — when any of them48 comes back empty or zero, rather than handing you a confident `0`. A count typed from memory49 or copied from a previous run is a second copy of a number that already exists, and it goes50 stale silently.5152 The checklist then asserts that the *running binary* matches what the *tree* says. That53 comparison is the point: it is the only step that can catch a build that installed something54 other than what you think you are testing.55563. Work each item, filling in every field:57 - `command` — the exact command or tool call you ran.58 - `evidence` — the output you read, verbatim, trimmed to the deciding lines.59 - `observed-result` — what it means.60 - `status` — `pass`, `fail`, `skipped` or `substituted`. A `skipped` needs a reason; it is not61 a pass. `substituted` means the behaviour was checked by a different instrument than the item62 names — an automated test standing in for a path you could not drive live, say. Folding that63 into `pass` overstates it and into `skipped` understates it, so it gets its own word, and the64 reason must name the instrument. Record any deliberate deviation from the item's stated65 method the same way.66 - `accepted` + `acceptance-reason` — whether the observed result is acceptable, and why. A67 `fail` may still be accepted as a known, tracked defect, as long as the reason names where it68 is tracked.6970 `accepted` starts as `null`, meaning nobody has answered yet. Leave it null until you decide;71 a run with any null left in it is unfinished, not a run with no objections.72734. **Never inherit a prior verdict's reason.** Where an item was `partial`, `fail`, `skipped` or74 `substituted` last time, re-derive *why* against the source in front of you before reusing the75 explanation. A previous run blamed a job's cadence for three event ids never firing; the real76 cause was that they log only when there is something to purge, and a fresh bank has nothing.77 The outcome matched, so the wrong mechanism survived a release — and it discouraged seeding the78 data that would have exercised them. A reason that is right about the outcome and wrong about79 the cause is the hardest kind of stale fact to see.8081 When a run finds that an earlier one was wrong, that belongs in `findings-against-prior-runs`,82 not buried in an item. A checklist that can only describe the current build cannot report that83 the last checklist lied, and those findings are often the most valuable thing a run produces.84855. Items whose feature no longer exists get **deleted from the template**, not marked skipped. A86 step for a removed feature is worse than no step: it either fails forever and gets waved87 through, or it quietly "passes" against nothing.88896. Report counts by status, every `fail` with its evidence line, and every finding against a prior90 run. A run where some item has no `command` or no `evidence` is not complete — say so instead91 of reporting a total.9293Items are independent, so lanes can run in parallel against one packed binary. The axis that keeps94them from colliding is the data root: give every lane its own `--data-root`, and nothing else needs95coordinating.9697## Making an item able to fail9899Most of these steps can be written so that they pass whether or not the feature works. Three100shapes account for nearly all of it:101102- **A filter needs a negative control.** Feed clean content alongside the shapes you expect103 rejected. A policy that rejects *everything* passes a rejection-only test, and looks healthiest104 exactly when it is most broken.105- **A semantic-retrieval query must be one that keyword match cannot carry.** Query for106 "an antique navigation instrument reflecting evening light in a stargazing room" against content107 that says "astrolabe", "lamplight", "observatory" — no literal overlap, so a dead vector leg108 actually fails the item. A query sharing words with the stored text passes on BM25 alone and109 tells you nothing about the half you meant to test.110- **An empty list is a weak check.** A queue or candidate list on a fresh bank returns `[]`111 whether it works or is broken. Create the thing first, then assert on its content — the score,112 the reasons, the identity — not on the shape of the response.113114## Scope115116Derived per run from the product, not from this list. The headings below are the stable shape of117the pass; the specific steps under each come from what step 2 found and from what is actually118registered in the build in front of you.119120- **Build and install** — Release build, pack, force-update the global tool, and `--version`121 matches the derived version.122- **Server lifecycle** — the server starts and `--restart` cycles the one it finds, both on a123 non-default port.124- **Write path** — a write stores and returns a hash; a rejected write says so, with a reason,125 rather than returning a fabricated entry.126- **Read path** — search returns the written entry, get returns its content by hash, and a127 `file#section` anchor resolves its exact chunk.128- **Noise filtering** — each *registered* write-path policy rejects what it claims to, and the129 rejected content stays retrievable from the noise store. Check which policies are registered130 before writing steps for them.131- **Read-path query guard** — the refuse and annotate tiers behave as specified, and any detector132 that ships disabled is still disabled until explicitly armed.133- **File watch** — watch status reflects live registrations.134- **Promotion queue** — the promotion list reports candidates accurately.135- **Full MCP surface** — every derived tool and prompt is reachable.136- **Observability** — emitted event ids resolve against the logging event-id reference.137138Anchor each item to the decision record that defines the behaviour, so a step whose ADR was139superseded is easy to spot and delete.140141## How this checklist rots142143Two predecessors were deleted after both drifted the same six ways. Each defence below is here144because its absence already caused a silent failure:145146- **Facts pinned by hand.** One asserted `--version → 1.9.1` and "25 tools" while the tree was at147 1.12.0 with 26 tools. The pins had been wrong for three releases and nothing noticed, because148 the only thing comparing them was a human reading two numbers. → step 2 derives them.149- **Steps for a deleted feature.** Both still tested a noise policy that had been removed by a150 later ADR. A step whose subject does not exist cannot pass honestly. → step 4 deletes them.151- **Results written into a bank directory.** Reports landed in `.ai-raccoon/`, the directory name152 the product uses for banks. → step 1 fixes the destination.153- **No evidence field.** The template recorded a claim with no room for the command or the output154 behind it, so a filled checklist and an invented one were indistinguishable afterwards. →155 `command` and `evidence` are required.156- **Booleans that cannot express "skipped".** `checked`/`accepted` flags collapsed three states157 into two: an unrun item and a failed one both read as `false`/`false`. → `status` is a tri-state158 and `accepted` starts null.159- **Two drifting copies.** Two directories each held a copy, and one had lost its `templates/`160 directory entirely, so its own step 1 pointed at a file that was not there. → one copy, and the161 template ships beside the skill.162163**Retiring a checklist skill means deleting it from every root it can load from**, not from the164one you were looking at. The deletion that removed the two `.ai-badger/skills/learned/` copies165missed `~/.hermes/skills/`, where a third copy was found still installed long afterwards, still166carrying its `templates/`, still pinning an expected version three minors stale. It was the167*first* hit for someone searching for this checklist, and they began executing it; that copy has168since been removed. Enumerate the roots — the project's169`.ai-badger/skills/learned/`, `.claude/skills/`, `~/.claude/skills/`, `~/.hermes/skills/` — and170confirm the removal in each. The stale copy wins whoever searches first, so a copy you did not171delete is not dormant; it is the one in use.172173## Gotchas174175- **A count that cannot fail is not a count.** Every counting primitive here has a silent zero176 in it: `rglob` over a directory that no longer exists yields nothing, `sum(())` is `0`, and177 `grep -c` across several files prints one number per file so a bad sum still looks like a178 number. None of them raise. `derive-facts.py` closes that by treating zero as a failure rather179 than a result — it exits 1 and says which fact it could not derive — and by counting only lines180 that are code. Do not "simplify" either rule away: a file that documents `[McpServerTool]` in a181 `///` comment already inflated the surface by one for three releases, and the number looked182 derived the whole time. Both properties have tests in the framework; change the script there,183 never by editing your copy.184- `--port 0` binds an ephemeral port, so the port must be read back from the server's own output.185 Assuming a port here is how a checklist step ends up talking to somebody else's server.186- Force-updating a global tool can silently keep the previous build if the pack step failed187 earlier in the same run. The version comparison in step 2 is what catches it; do not skip it188 because the build "looked fine".189- A scratch `--data-root` must be a path the running user can create. Pointing it inside a190 read-only or root-owned directory produces failures that look like product bugs.