The boost roadmap
Every finding from the autonomous quality loop lands here — what shipped, what's mid-flight, and what's queued — scored by complexity, impact, and a little bit of wow.
Shipped
// merged to main & publishedGitHub Pages deploy is broken every push
Mutation hardening — core/store.py
Mutation hardening — core/gitutil.py
Untrack generated build noise
browse crashes when you pick a rule or workflow
Consolidate skill-staleness / drift logic into core
Write-up · consolidate-skill-staleness-drift-logic-into-cor.md
Mutation hardening — core/frontmatter.py
Extension-free tests — core/dense.py
Crash-recorder error paths — core/logs.py
Extract MCP + HTTP servers out of configuration.py
Write-up · extract-mcp-http-servers-out-of-configuration-py.md
Autonomous ship-workflow & isolated worktree
Write-up · autonomous-ship-workflow-and-isolated-worktree.md
Fix self-update version detection (dead branch)
End-of-options -- guard on git commands
Crash-correlation breadcrumbs in the invocation log
The copyleft protected nothing and cost the one thing boost needs
bmad on knew about a second host and wrote to one anyway
A Gemini user got the heuristic fallback from every AI command
Reuse helpers; kill minor dead work
All 80 boost commands audited against a disposable HOME — four defects fixed
Next up
// triaged findings, starting soonReconcile the theme drift
Planned
// on the listFinish mutation hardening across core/
Bring commands/ under mutation testing
The ~8,100-line command layer has zero mutation coverage: mutmut is
scoped to core/ only. A blocking 80% floor was attempted and the
baseline has now actually been measured — it does not clear the bar, and
two of the three constraints recorded here earlier were wrong. What a sample run
of commands/taps.py (the best-covered module in the package,
95.8% lines) against tests/unit/ + tests/functional/
reports through this repo's own gate:
570/793 killed — 71.9%, under the 80% floor, with all 223
survivors spread across every one of the module's 7 functions rather than
concentrated in one testable gap. Mutant density is 4.2 per
statement in taps.py but 2.46 across
core/ (9,909 mutants over 4,033 statements), so
commands/'s 5,314 statements imply somewhere between
13,000 and 22,300 mutants; at the measured
1.42 mutations/sec — roughly 6.5× slower per mutant than the
core/ job, which matches the functional-vs-unit suite cost — that is
2.5 to 4.5 hours on one runner, against an ~18-minute job today.
The job sets no timeout-minutes, so it inherits GitHub's 360-minute
cap rather than failing fast.
Two blockers sit underneath that number. Selecting only tests/unit/,
the way core/ does, leaves commands/ at
17.9% line coverage versus 91.5% with functional included — and
"no tests" mutants count against the score, so that route floors out near 18%.
Selecting tests/functional/ instead crashes the run: mutmut's
record_trampoline_hit calls p.resolve(strict=True) on the
relative source path, so any test that chdirs into a temp
project dies with FileNotFoundError: <tmp>/boost_cli — confirmed
on test_verify_sees_a_project_skill. Corrections to the earlier note:
pytest_add_cli_args_test_selection is not single-valued, it
is a list that configparser splits on newlines (a space-separated line is what
errors), and a sibling pytest_add_cli_args takes extra pytest flags.
One prerequisite is already fixed: --no-mcp leaked through
os.environ and made the suite order-dependent, which mutmut exposes
because it runs whichever test subset covers each mutant.
Declined 2026-07-29. Not "hard" — blocked, and the block is
upstream. mutmut 3.6.0 is still the latest release, and
src/mutmut/__main__.py:120 still reads
source_paths = [p.resolve(strict=True) for p in Config.get().source_paths]
— unconditionally, before the max_stack_depth guard — on paths that
configuration.py:102 builds as plain relative Paths. Every
chdir-ing functional test therefore kills the run, and the functional
suite is precisely what takes commands/ from 17.9% to 91.5% line
coverage. So the two candidate configurations are "floors out near 18%" and "crashes";
there is no third. Even granting a fix, the measured numbers already refuse the
proposal on their own terms: 71.9% against an 80% blocking floor, with
survivors spread evenly rather than pooled in one testable gap, at
2.5–4.5 hours per run against an ~18-minute job. A gate that is 8 points red
on the day it lands does not gate anything; it just makes main red.
The lead, if anyone reopens this: the crash is a relative-path bug, and
source_paths is not required to be relative — an absolute path survives
resolve(strict=True) from any working directory. Whether the rest of
mutmut's mutants/ copy machinery tolerates one is untested. That, plus a
non-blocking scheduled job that rotates one module per week, is the shape worth
trying; a blocking 80% floor is not.
What is not lost by declining. The architecture already puts behaviour in
core/ — which is mutation-gated at 80% — and keeps
commands/ as thin CLI glue. The uncovered layer is the one deliberately
designed to hold the least logic, and it still carries 91.5% line coverage from the
functional suite.
Visual regression pass on the guide
Refresh the marketing surface
a Tier 3 eval for tool-call behaviour, floored in both directions
boost's required gate floors four retrieval metrics — recall@k ≥ 0.78, hit@1 ≥ 0.40,
MRR ≥ 0.52, nDCG@k ≥ 0.58 — over a 91-query golden set and a 10,152-entry corpus, every
row pinned to a commit SHA. All of it measures what boost returns once it is asked. Nothing
measures whether an agent asks. **The call itself is unmeasured**, and it is the step everything
downstream depends on.
The miss that exposed it. A Gemini CLI session was asked to "create a new, simplified app
demonstrating RAG implementation in Python3 using langGraph, langChain, and langSmith" — a new
project, an architecture decision and a dependency choice, which is three of the triggers
boost_search's description names explicitly. It activated two already-installed
skills, built the app, and never called boost. Asked why, it paraphrased boost's own lock-in
trigger list back verbatim, so the text was read and was not persuasive. A gate that floors
recall@k at 0.78 reported nothing, because retrieval was never invoked.
Every claim in the MCP surface is argued, not measured. The triggers, the 10-15s stated
cost, the skip list, the three-kind framing, the "already covered is not already checked" defeater
— each survived a careful review and none has a number behind it. That is a
high-variance lever tuned blind: Tool Preferences in Agentic LLMs are Unreliable
(EMNLP 2025, arxiv 2505.18135) measures description-only edits swinging call rate by
more than 10×. boost currently ships those edits on reasoning alone.
The design constraint that decides whether this is worth building: floor both directions.
A tier that measures call rate alone rewards making boost maximally assertive, which is precisely
the capture the surface is written to avoid — and boost has already learned this exact lesson one
tier down. Flooring recall alone was a hole rather than a simplification: a ranker that
finds the right answer every time and never ranks it first scores recall@10 1.000 with hit@1
0.000, and passed. So the prompt set needs two halves — a should-call set (multi-file work,
a new subsystem, a config or CI job that outlives the session) and a should-not-call set
drawn from the shipped skip list (a question, a one-line edit, a command the user just handed
over) — with a false-call ceiling as binding as the call-rate floor. One number without the other
is an incentive to ship the thing boost refuses to be.
Per host, never averaged. The two registered hosts do not see the same boost text. Claude
Code puts server instructions in the system prompt; Gemini CLI never delivers them in
interactive mode at all — Config.initialize() does not await
mcpInitializationPromise, so getMcpInstructions() returns "",
startChat stamps the context entry once with a stable id, and the later
refreshMcpContext() re-renders Tier 1 only. A single averaged score would hide a host
where 1,786 characters of guidance are simply absent, and would credit or blame wording for a
delivery failure.
Shape. An opt-in make eval-tools beside eval-ai /
eval-rec / eval-explain — real hosts and real LLM calls, so it is
non-deterministic and key-gated and must not join the required check gate;
same degrade-cleanly contract as the other Tier 2 evals. Because the outcome is stochastic, report
N runs per prompt with an interval rather than a single pass/fail, the way
golden-set-statistical-power established for retrieval — a one-shot replay cannot tell
a wording regression from a sampling wobble.
Where this stands (2026-08-31), and a correction. The Claude Code arm shipped in #616:
scripts/eval_tools.py, a 16-prompt set halved into should-call and should-NOT-call,
Wilson intervals over N runs, and a verdict that floors call rate and ceilings false calls.
Its probe was broken, and the finding this card recorded was an artifact of it. An earlier
revision of this card reported “3/3 false calls — boost's tools fired on What is the
difference between a Python list and a tuple?”. They did not fire. called_boost()
substring-scanned the raw event stream, and claude -p --output-format stream-json --verbose
opens with a system/init event enumerating every tool available to the
session — which on any machine where boost is registered contains
mcp__boost__boost_search and the other three CONSULT names. So the check returned
true on every run, including runs with no tool call at all. Measured directly:
Say OK and nothing else. produced zero tool_use blocks and scored as a
boost consult.
Two consequences shipped with it. make eval-tools could never pass — eight
no-call rows × three runs is 24 forced trues, so the false-call rate's lower bound sat at
1.00 against a 0.20 ceiling, red on every machine forever. And the tier built to retire
unfalsifiable claims had produced one. The lesson is the tier's own: the existing tests
passed because they fed hand-written one-line fragments with no init event — a
fixture the author invented could not catch the author's wrong model of the input. The probe now
parses the NDJSON and counts only tool_use blocks inside assistant
events, and the regression test drives a captured real stream.
The second host arm is still unwritten, and should stay that way until the fixed probe is
re-run. Building arm two on a probe that cannot tell an offer from a call would produce two
hosts scoring an identical, meaningless 1.00. When it is built, the candidate is Gemini CLI
proper, not Antigravity CLI: the delivery claim below is about Gemini's Node bundle, and
agy is a third mode again — it receives boost's instructions and
writes them to ~/.gemini/antigravity-cli/mcp/boost/instructions.md, pointing the agent
at the file rather than inlining it. Substituting it would measure a different mechanism than the
one this card argues about.
Cost, now measured. Two trivial runs on a real host reported $0.657 and
$0.682 of total_cost_usd, so 16 prompts × 3 runs is roughly
$30–50 per host per invocation on a machine with a crowded tool surface.
--strict-mcp-config with a boost-only config cuts that sharply and controls the
surface confound in the same move.
2026-08-31: --strict-mcp-config shipped. eval_tools.py now takes
a --strict-mcp-config flag: it writes a boost-only mcpServers config
(the same <launcher> mcp --stdio invocation and fork-safety env
core.mcphost.register_argv uses for a real registration — confirmed against an actual
claude mcp add-json write, not guessed at the schema) to a temp file and passes
--strict-mcp-config --mcp-config <path> to every claude -p call,
cleaning the file up afterward. This session's sandbox had no network path to PyPI, so the pinned
toolchain (pytest, ruff, mypy, …) could not be installed and
make check could not be run here; the change was verified by hand instead — direct
python3.12 import of the module, the new unit tests executed by eye against the
interpreter, py_compile, a manual line-length check against ruff's 88-column default,
and an end-to-end dry run with subprocess.run mocked that confirms the flags land on
the argv and the temp file is created and removed. CI runs the real gate on the PR.
Still unwritten: the second host arm (Gemini CLI). No gemini CLI was reachable
in this sandbox to capture a real stream from, and building that arm on an invented model of
Gemini's non-interactive output format is the exact mistake this card's own probe fix (2026-08-30)
already paid for once — "a fixture the author invented cannot catch the author's wrong model of the
input." That arm stays a placeholder until it can be built against a captured real stream, on a
machine with the gemini CLI installed.
What it unlocks. The first honest answer to "did that description edit help", a baseline the
next surface change can regress against, and a way to retire claims that survive only because
nobody can check them.
unpin the [eval] langchain stack when ragas ships its fix
The [eval] extra pins langchain-core<0.4,
langchain-community<0.4 and langchain-openai<1 because ragas hard-imports
ChatVertexAI from a langchain_community chat-models path that 0.4.x
deleted. The LangChain integration card originally made this unpin its phase 0 and was
corrected in place: ragas 0.4.3 still carries the import (measured 2026-08-04 — declared
bounds are open, but import ragas crashes beside langchain 1.x), while upstream main
already has the removal merged. So the unpin is one release of someone else's package away.
What to do when it lands. Check pip index versions ragas (or the PyPI JSON) for
a release after 0.4.3; verify in a throwaway venv that import ragas succeeds beside
langchain>=1; then move [eval] to that floor, delete the three langchain
pins, and adapt scripts/eval_explain.py if the 0.4 scoring API moved (its
evaluate/to_pandas surface is what
test_eval_faithfulness.py stubs in the unit suite). The eval-explain workflow is
the live proof — it must stay green with real keys.
Re-checked 2026-08-30. pip index versions ragas still reports
0.4.3 as the newest release, so nothing has changed and this card is still not
claimable. Recorded here rather than left implicit: a card that says "check before starting"
gives a reader no way to tell a check that came back negative from a check nobody ran.
Why it stays its own card. The shipped integration card documents the block but will not be
re-read; an unpin nobody remembers is how a workaround pin outlives its reason by years. This card
is the reminder, and it is deliberately not claimable until the upstream release exists.
give boost-langchain a release path to PyPI
Declined, deliberately. Both missing pieces below were owner-only or upstream-blocked, and
the research they prompted dissolved the premise: a second PyPI project bought a separate release
cadence nobody needed (boost releases more often than langchain), while the ecosystem evidence —
langchain-community sunset, non-langchain-* names in LangChain's own integrations
listing, in-host precedents from ragatouille to mlflow — showed the
standalone distribution was never required. The integration now ships inside the
boost-skill-cli wheel behind a [langchain] extra instead; see
langchain-in-the-wheel. The original card follows for the record.
The boost-langchain distribution shipped under integrations/langchain/
with its whole point being a separate release cadence from boost-skill-cli —
langchain majors move faster than boost does, and the conformance workflow already builds the
sdist/wheel and runs twine check on every touching PR. What does not exist is any way
for those artifacts to reach PyPI: the name 404s there, and nothing publishes on any trigger.
Two pieces, one of which only the repo owner can do. First, create the PyPI project and
configure a Trusted Publisher for it — pending-publisher registration works before the first
upload, and the filename-matching rule that pinned boost's own workflow name applies here too.
Second, a publish workflow with a deliberate trigger: not boost's every-merge cadence
(publish.yml releases boost-skill-cli on every push to main, which is
exactly the coupling the separate distribution exists to avoid) — a tag like
boost-langchain-v0.1.0 or a manual dispatch that bumps the static version, builds from
integrations/langchain/, and publishes with the OIDC token. Remember the repo's own
lesson: a release:-triggered workflow can never fire here (GITHUB_TOKEN events do not
chain), so trigger on the tag push or dispatch directly.
The floor is already honest. The package requires boost-skill-cli>=1.0.320 —
measured against the actual API it calls, verified by an adversarial install — so the first
published version works against PyPI as it stands today.
publish the keyword index the way vectors are published
Dense vectors are built once in CI and downloaded. The BM25 index is not:
core/rag.py has no export or import function at all, and
shards.yml / scripts/publish_shards.py are dense-only end to end. Every
install rebuilds the same index from the same registries, at the same pinned commits, to produce
the same bytes.
Measured, on a real 458-tap machine. The on-disk index is
rag_index.json 43.7 MB plus rag_postings.sqlite
653.0 MB — 696.7 MB for 18,619,658 postings. Build cost, timed over a
9,306-entry / 69-tap slice: 4.54 s reading bodies and tokenizing, 3.83 s
writing postings, 8.4 s total — about 0.9 ms per entry, so roughly
65 s and ~900 MB extrapolated to the full 71,700-entry catalogue.
Which user actually pays it. Not the default one: boost quickstart taps the
7 starter registries and indexes them in about a second. The cost lands on
boost quickstart --catalog — 463 registries, 2 min 10 s of parallel cloning
and then a minute of indexing on top — and on anyone who taps their way there gradually.
The bug that makes this worth doing is not speed. boost catalog --import
already exists and already looks like the answer: shareable-catalogue-bundle advertises
10.9 MB replacing a 12 GB clone and "59,972 searchable items in 4 seconds". That 4
seconds is fast for a reason the card does not state. rag.read_body degrades
silently to name + description when the item's clone is absent
(rag.py: "Missing files degrade to just the catalog metadata"), and a bundle import
restores catalogues with zero repositories cloned. So the index it builds is not the
full-content index the evals gate floors — it is a frontmatter index wearing the same
file name.
Measured directly over 3,015 real entries, indexing them with and then without their
clones: 3,041,326 tokens versus 182,507. A bundle-only index carries 6.0% of the
searchable text, and nothing in the output says so. That is the same failure shape as an
unpinned eval corpus — a number that still renders confidently while measuring something else.
Why this is easier than the dense shards, not harder. BM25 looks like it needs global
statistics, and it does — but none of them are frozen at build time. _bm25 derives
n = len(docs) and df = len(plist) on every query, so IDF is
computed from whatever corpus is loaded. A per-registry shard therefore merges by offsetting
doc_id, unioning the postings, and recomputing avg_len from per-shard
totals — arithmetic, not re-derivation. And unlike vectors there is no embedding space to match
and no API key to hold, so shards.incompatible() has no analogue here: a published
keyword index is importable by everyone, including the keyless user who cannot use vectors at all.
Shape. rag.export_shard / rag.import_shard mirroring
dense's pair, per-registry assets on the existing shards-latest release,
rows carried in the same manifest.json with the same commit pin and sha256 — the
carry-forward machinery in publish_shards.py manifest --carry-forward applies
unchanged, because a registry whose commit did not move has an index that did not change either.
Three invariants transfer verbatim from the dense side and each is load-bearing: verify before
replacing, refuse a shard whose commit is not the tap's commit, and never treat a missing digest
as a match.
The open question is payload size, and it is large enough to be its own decision — see
shrink-the-published-index. This card should not ship
until that one has an answer, because publishing 697 MB per refresh to save 65 s of CPU
is not obviously the right trade, and at the compressed sizes measured there it clearly is.
Partly landed, and deliberately still inflight — 2026-09-10. What shipped is
the half that needed no size decision: the index now records what it is.
read_body_full returns the text and whether it contains the item's body,
build() reports metadata_only over every document written (reused ones
included, or an incremental build reports zero on the run after a bundle import),
index_completeness() reads the share back off disk, and boost reindex
says it out loud instead of reporting the same confident count for a 6% index. The share is of
tokens, not documents: a bodyless entry still produces a document, so a document share sits
at 1.0 until it drops to 0.0. INDEX_VERSION moved to 9, because the flag is
written only when a body is missing and absence may only be read as "complete" once no older
document can survive.
What did NOT land: rag.export_shard / rag.import_shard, the
per-registry assets, and the manifest.json rows — the publishing pipeline itself.
That half is what the payload-size question governs, and
shrink-the-published-index still has no answer: its claim
is stale, not active — branch loop/shrink-postings-index was last touched
2026-09-02, carries one commit, has no pull request, and is 394 commits behind
main. Someone should un-claim it. One structural finding for whoever takes it: doc
ids are positional (_save does enumerate(docs)), so the card's
"merge by offsetting doc_id" is sound as written — and the shard format should
serialize logical postings (digest → term → tf) rather than the SQLite layout, so the
interning that branch was attempting cannot invalidate a published shard.
shrink the keyword index before publishing it — structure first, then compression
publish-the-keyword-index is worth doing only if the
artifact is small enough to ship weekly. This card is the measurement that decides it, and the
first answer is that compression is the second lever, not the first.
What the format actually stores. _write_postings creates
postings (term TEXT, doc INTEGER, tf INTEGER) and inserts one row per posting, so the
term string is repeated in every row. Measured on the real 458-tap store:
18,619,658 rows over 210,422 distinct terms averaging 6.6 characters. That is
~123 MB of term text to carry 1.4 MB of distinct term text — 88× redundancy,
before the per-row and B-tree overhead that turns it into a 653 MB file (page_size 4096,
167,174 pages, freelist 0, so it is not slack space).
Compression measured on that file, as it stands:
rag_index.json 43.7 MB raw · gzip -6 10.6 MB (4.12×).
rag_postings.sqlite 653.0 MB raw · gzip -6 201.7 MB (3.23×) · zstd -3 169.9 MB (3.84×) · zstd -19 106.7 MB (6.11×).
So even with no format change, zstd -19 puts the whole index near 117 MB — under half
the ~300 MB of dense vectors already published weekly. The trade is already good; the point
of this card is that it can be much better, and that the two levers compose.
Structure first, and it is the bigger win. Interning terms into
terms(id, term) with postings(term_id, doc, tf) removes ~123 MB of
duplicated strings and shrinks the postings_term index from a text key to an
integer one. Beyond that, the classic inverted-index encodings apply directly because doc ids
within a term are ascending: delta-encode them, varint or bitpack the deltas, and store one blob
per term rather than one row per posting. Both shrink the file on disk, not just in
transit, which is the half a compressed download never gives back — the user still ends up with
653 MB resident after import.
What must not regress. read_postings exists precisely so a query touches a
handful of terms instead of materialising the whole map — the change that took cold search from
8-13 s and multiple GB resident to 31-70 ms of scoring. A blob-per-term layout keeps
that property (one row read per query term, decoded on the spot); a scheme that requires decoding
neighbouring terms to find one does not. _bm25 must stay byte-identical, as it did
through the SQLite move, and TestBm25Math is what says so.
Decompression cost is the thing to measure, not assume. zstd -19 is slow to compress and
fast to decompress, which is the right asymmetry for a weekly build feeding many imports — but
"fast" needs a number on the import path before it is a claim, next to the 0.12 s that
importing dense rows costs today. A zstd dictionary trained across shards is the obvious follow-on
for the many-small-registries case, where per-shard compression has little context to work with.
Deliverable. A measured comparison — raw, interned, delta+varint, each × none/gzip/zstd
— on the real store, with import-side decode time beside each. That table is what tells
publish-the-keyword-index what to ship, and it is worth
having even if publishing is declined: the on-disk win applies to every install today.
Progress — PR 688, merged as f003fa03 in train 691. The structural half shipped: _write_postings now
interns terms into their own terms(id, term, df) table, with postings
carrying an integer term_id instead of repeating the term string on every row —
exactly the "structure first" change this card calls the bigger win, and it bumps
INDEX_VERSION so every store picks it up on its next rebuild.
stem_expansions now reads the precomputed df column directly instead of
a GROUP BY COUNT(*) over postings on every prefix lookup. Not done: the
delta/varint doc-id encoding, and the full raw/interned/delta+varint ×
none/gzip/zstd comparison table with import-side decode times on a real multi-hundred-MB store —
this sandbox has no such store to measure against, only a small synthetic one (interning alone cut
a 1.8M-posting/20k-term synthetic store from 63.6 MB to 47.9 MB, directionally consistent
with the real-store estimate above but not a substitute for it). Left as follow-on work before this
card can be called shipped.
A best-effort log handler prints a traceback over every command's output
Near-identical copies survive content-hash dedup and take the whole result page
Content-hash dedup shipped and worked:
rag.dedupe_by_content took duplicate result slots from 4.94 to 0.60 per query
over a 77-tap corpus. That card closed naming one thing still open — near-identical
rather than byte-identical clustering, where core/typosquat.py's confusion machinery
would apply — and buried it under a shipped status where nobody would claim
it. This card is that remainder, with a measurement that makes it look considerably worse than
“refinement”.
Observed on a real 466-tap install with hybrid RRF serving (658,131 chunks): for the query
exa search, every one of the top ten rows is exa-search, and the
descriptions are what give the
shape away — one Japanese (Exa MCPによるウェブ、コード、企業調査), two Chinese
(通过Exa MCP进行神经搜索), five English variants of Neural search via Exa MCP,
plus Use Exa MCP for current web… and AI-powered web search….
All ten are ★ curated. The footer reads
51 matches · ranked by hybrid RRF (BM25 + dense).
Every one of those passed dedup correctly. They are not byte-identical: they are the same
skill in Japanese, in Chinese, and in five English phrasings across different registries. The body
digest differs, so dedupe_by_content keeps them all — which is exactly the
behaviour #366 proved must be preserved, since two entries sharing a name can be
genuinely different rules. The shipped fix is not misbehaving. It simply does not reach this shape.
What the 0.60 residual actually was. The prior card described its leftover as “entries
sharing a name whose bodies genuinely differ, which must stay separate” — true
as stated, and it reads as a rounding error. At 466 taps the same residual is a full result page.
The gap between 0.60 and 10.0 is worth understanding before designing anything: the 77-tap
measurement used 50 natural-language queries averaged, and an average hides the shape here.
Duplicate pressure was already known to be a step function of which registries are tapped
rather than how many; near-identical pressure looks like a step function of which query —
harmless across a query set, total on any query that lands on a widely-mirrored skill. Re-measure
per-query maxima, not means.
The hard part is the safety proof, not the clustering. Content hashing was adoptable because
one count settled it: of 14,153 distinct bodies, clusters spanning more than one name numbered
zero, so collapsing could not merge two different skills. Near-identical clustering has no
such free proof — any similarity threshold loose enough to merge a Japanese translation with
its English original is loose enough to merge two genuinely different skills that share boilerplate.
Establish the equivalent bound first (over a real corpus, at the chosen threshold, count clusters
spanning more than one meaning) or the fix trades a visible problem for a silent one.
Three things to get right. Translations are the motivating case and the hardest: they
share almost no tokens with the original, so token-overlap similarity will not find them while an
embedding will — and the vectors are already on disk, which makes this cheaper here than it
would be anywhere else. Collapse before k, and at both the
retrieve and retrieve_any seams, for the reason the shipped dedup already
documents: fusion reintroduces copies either engine dropped, because the copies are distinct
(tap, skill_md) keys and RRF has no reason to treat them as one. The existing
quality prior carries over unchanged — rag.source_rank orders on the user's
curated flag first and shipped confidence second, and choosing among
near-identical copies is the same question as choosing among identical ones: where should the user
install from.
Not to be confused with #629, which deduplicated vector storage (one
row per distinct embedding, 39.7% repeats reclaimed). That is a disk-size fix beneath the index and
changes no ranking; this is about which rows reach the user's screen.
What shipped, and what did not. rag.collapse_near_duplicate_hits is the same
"keep the earliest rank slot, promote a better source" contract as dedupe_by_content,
run over cosine similarity of the entries' first-chunk embeddings
(dense.entry_vectors, an index probe through chunks_entry on a quantized
store) instead of a body hash, at the retrieve_any seam before k is
applied. It is covered by unit tests down to the arithmetic (_cosine's dimension-
mismatch and zero-vector guards), the clustering contract (rank order, quality-prior promotion,
limit-after-collapse), the dense.entry_vectors lookup against a real quantized
sqlite-vec store, and the retrieve_any/boost search
--collapse-near-duplicates wiring in both directions (on and off).
It ships opt-in and off by default — retrieve_any(..., collapse_near_duplicates=True)
or boost search --collapse-near-duplicates — rather than replacing
dedupe_by_content's output on the default path. Two things this card asks for are still
open, and both need a real embedding backend (a built dense index, over a real multi-tap corpus)
that the environment this was implemented in cannot reach — no network path to an embeddings
provider or to the local ONNX model download, confirmed rather than assumed: huggingface.co
and pypi.org both refuse at the network policy layer. First, the safety proof
this card itself demands before defaulting the mechanism on — “over a real corpus, at
the chosen threshold, count clusters spanning more than one meaning” — has not been run;
NEAR_DUPLICATE_THRESHOLD = 0.97 is a starting point, not a validated floor. Second,
re-measuring the exa search case (and per-query maxima generally) against the fix needs
that same corpus and index. Whoever runs that measurement should flip the CLI flag's default, fold
the corpus count into this card's evidence, and only then consider this shipped.
The bound has now been measured, and it says the acceptance test in this card is the wrong
one. scripts/measure_near_duplicate_bound.py runs the count this card asks for
against the pinned 20-repo eval corpus (10,152 entries, 104,271 chunks, BAAI/bge-small-en-v1.5
at 384-d). Those entries reduce to 5,714 distinct chunk-0 vectors — 44% of entries
already share a chunk-0 embedding byte for byte — and at
NEAR_DUPLICATE_THRESHOLD = 0.97, 162 pairs clear the threshold and 56 clusters span
more than one name. Sweeping the threshold moves that number but never to zero: 0.96 → 91,
0.97 → 56, 0.98 → 28, 0.99 → 13, 0.995 → 8, 0.999 → 4.
Four of those 56 are not the threshold's doing at all. They are clusters of a single vector
shared by several names, so they cluster at any threshold, which is why the sweep bottoms
out at 4 rather than 0. The largest is the same at every threshold and is worth naming: 28
differently-named agents from one tap (affaan-m/ECC — architect,
code-reviewer, chief-of-staff, database-reviewer,
e2e-runner, …) whose chunk 0 is the same Spanish preamble
(No cambiar rol, persona ni identidad…) in every file. Chunk 0 is
name + description + opening of body, and where a registry opens every file with
identical boilerplate, the name does not move the vector enough to separate them. A floor exists
that no threshold can reach under, so “count must be zero” was never achievable.
Worse for the test: most of the other 52 are the feature working. Hand-classifying all 56 at
0.97, roughly two-thirds are genuinely one skill under two names — twelve are pure
hyphen-versus-underscore renderings of one integration (zoho-mail /
zoho_mail, google_maps / google-maps,
anthropic_administrator / anthropic-administrator), and the rest are
suffix variants of one document (tdd / tdd-guide,
rust-review / rust-reviewer, testing-patterns /
code-showcase-testing-patterns). Collapsing those is precisely what this card exists to
do. A metric that counts them as violations would reject every threshold that works.
The dangerous merges have a shape, and this card already named it. The ~20 clusters that are
real false merges are dominated by near-miss brand names: coinmarketcal with
coinmarketcap, bugbug with bugsnag, parsehub
with parseur, linkhut with linkup,
mx-technologies with mx-toolbox,
salesforce-marketing-cloud with salesforce-service-cloud. These are
distinct products whose descriptions are boilerplate around a swapped word. That is the
core/typosquat.py confusion shape this card's opening paragraph pointed at, arrived at
independently from the other end: the guard this needs is not a tighter cosine floor but a
name-confusability veto — refuse to collapse two entries whose names are a confusable
edit apart, however close their vectors sit.
So the default stays off, for a better-supported reason than before. The measurement does not
say 0.97 is too loose; it says similarity alone cannot separate tdd/tdd-guide
(collapse) from coinmarketcal/coinmarketcap (never collapse), because both
pairs sit in the same cosine band. Flipping the default needs the confusability veto first, and a
re-count with it applied. And this bound is space-specific: it was measured in
bge-small 384-d, while a keyed production install is voyage-4 at 1024-d.
Cosine thresholds do not transfer between embedding spaces — rerun the script against each
space before trusting a number in it.
The monthly corpus refresh rewrote the pins and the baseline but left every documented number stale — taps.txt now contradicts its own header, and nothing checks it
With a corrupt config.json, doctor reports "no registries tapped" and verdicts "● ready to set up" exit 0 while search is dead; heal says "nothing to heal"
BOOST_NO_EMBED has no state in the reason ladder: doctor calls a deliberate kill switch a degraded fault (exit 1) and hands advice that is a measured no-op in both branches
fix_hint's no-key guard has been unreachable since the day it was written; a missing API key now prescribes the full re-embed the guard exists to prevent
Measured. On a complete [rag] extra with a voyage-4-built vector store and no API key exported, dense.status() returns reason='provider-changed' (not no-key), so all three status-passing surfaces print rebuild it: \boost reindex --dense --force\` and boost doctor exits 1 — measured verbatim, including doctor's "live key is local; searches are using BM25" line — while the guard written to prevent exactly that (21f28223, #444) fires only under BOOST_NO_EMBED=1` or a partial install, both confirmed by direct probe.
Reproduce it.
cd <repo>
export HOME=$TMPDIR/verify-dense-f1; export BOOST_HOME=$HOME/.boost; mkdir -p "$HOME"
unset VOYAGE_API_KEY OPENAI_API_KEY BOOST_NO_EMBED
python3 tests/make_fixture.py $TMPDIR/verify-dense-f1-fix >/dev/null
./boost tap $TMPDIR/verify-dense-f1-fix >/dev/null
# NOTE: ./boost execs system python3, which has no sqlite_vec, so it always says
# "no-backend". Every dense command below MUST run under .venv.
.venv/bin/python - <<'PY'
import hashlib, sys; sys.path.insert(0, ".")
from boost_cli.core import catalog, dense, embed
DIM = 1024
def fake(texts, input_type=None, timeout=60): # no network, no API spend
out = []
for t in texts:
h = hashlib.sha256(t.encode()).digest()
…
What adversarial verification corrected. This card's numbers are the re-measured ones, not the ones first reported.
The defect is real and reproduced verbatim, but five stated details are wrong:
1. SOURCE ATTRIBUTION OF THE COST FIGURE. why_it_matters says "which CLAUDE.md prices at ~1.2 s per chunk on CPU". grep -n 'per chunk\|1\.2 s' CLAUDE.md returns nothing. The figure is real but lives in the roadmap: docs/roadmap/items/keyless-semantic-search-for-everyone.md:298 ("4,431 s — 74 minutes, about 1.2 s per chunk") and :308, plus the-shard-job-that-could-not-finish.md:17 and keyless-dense-tier-local-static-embeddings.md:75. Cite the roadmap, not CLAUDE.md.
2. quality.py:769 is wrong -> the fix_hint call is boost_cli/commands/quality.py:780 (fix = dense.fix_hint(st["reason"], st)). Line 769 sits inside the unrelated search-quantization warning.
3. pyproject.toml:55-57 is wrong -> the three pins are at lines 56, 67 and 68 (sqlite-vec>=0.1.6, onnxruntime>=1.17, tokenizers>=0.15), split by a 10-line comment justifying the fastembed rejection. They are in one extra, which is the load-bearing part, but the range is not 55-57.
4. THE CAUSAL-ORDER COMMIT PAIR NAMES THE WRONG COMMIT FOR THE GUARD. 86163e03 (2026-07-31, #364) created test_dense_fix_hint.py and the _FIX table, but not the guard.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
PRECONDITION — state it on the card so nobody reads it as unconditional. The misroute needs (a) a store built with an API provider, (b) the complete [rag] extra so provider() falls through to local, and (c) the key absent from the *process* environment. #444's own commit message documents that shape as recurring in the wild: "the shell exports one; the spawned server does not inherit it."
THE REAL MACHINE IS NOT CURRENTLY IN THE BUG STATE. ~/.boost/cache/rag_vectors.sqlite reads provider="local", model="BAAI/bge-small-en-v1.5", dim=384 — so provider() matches built_provider and its reason is None. The 645,592-chunk / 1.20 GB / 440-tap figure is cost-if-the-hint-is-followed, not present harm. Do not let the card imply the machine is broken today. (The 750,416-chunk voyage-4 store in the #444 message and the test docstring is a past state of the same machine.)
THE MISDIAGNOSIS WAS REFUTABLE FROM THE SAME FILE. The _FIX comment — "This reason means 'no key AND no local backend', which in practice is a partial install or BOOST_NO_EMBED" — was already in dense.py from 86163e03 (07-31) when #444 (08-03) added a guard whose docstring asserts the opposite: "an unfinished install with no store and a complete install whose key merely went missing both land here." Two claims about no-key, 30 lines apart, that contradict each other.
Why it is worth doing. A user who opens a new shell without exporting their key is told by doctor, search and the MCP server to re-embed their whole store. On the real machine in this repo that is 645,592 chunks / 1.20 GB, which CLAUDE.md prices at ~1.2 s per chunk on CPU — hours of compute, or a real API bill — to fix a problem whose actual remedy is one export. The repo already decided this was unacceptable and wrote six tests plus a paragraph of docstring to prevent it; the guard has simply never been able to fire.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CORRECTED. No fix is prescribed here — the measurement is the contribution.
status() has no state for "ready but the embedder does not work": doctor green-ticks a tier that never ran, the search hint is suppressed, and every search re-pays the failed model fetch
out.err's multi-line hint is coloured as one span, so line 1 ends with no RESET and lines 2+ carry no start code
The eval corpus's size and its concentration ceiling are counted with len(scan_dir) — the measure measure_registry.py exists to say is wrong — so 44.7% of the gate's corpus is vendored …
Measured. Two tools in this repo, run on the same clone at the same pinned SHA, give item counts 3.16x apart — scripts/measure_registry.py says est_items=2100 while scripts/eval_corpus.py --ensure records 6634 entries — and the tool that is right is the one the eval gate does not use: measure_registry.py's own docstring (lines 6-11) states that len(catalog.scan_dir(repo)) is not the measurement, "the same rule the eval gate's ranked list uses". eval_corpus.py:299 is still counts[repo] = len(entries), and MAX_SHARE = 0.65 (line 113) ratchets on that number inside CI's required lint job — so 976 extra vendored copies from one stranger's repository, containing zero new content, turn every open pull request red.
Reproduce it.
cd <repo>
export HOME=$TMPDIR/audit-corpus-verify && mkdir -p "$HOME" && export BOOST_HOME=$HOME/.boost
.venv/bin/python scripts/eval_corpus.py --ensure # -> "corpus: 10731 entries"; "concentration: ... 61.8%"
.venv/bin/python scripts/measure_registry.py "$BOOST_HOME/repos/sickn33__antigravity-awesome-skills" # -> est_items=2100
.venv/bin/python - <<'PY'
import json, pathlib, collections, os
home=pathlib.Path(os.environ["BOOST_HOME"]); ents=[]
for f in sorted((home/"cache").glob("*.json")):
if f.stem.startswith("rag_"): continue
d=json.loads(f.read_text())
for e in d["skills"]: e["_tap"]=d["tap"]; ents.append(e)
alld={e["content"] for e in ents}
…
What adversarial verification corrected. This card's numbers are the re-measured ones, not the ones first reported.
1. MATERIALLY WRONG — "The vendoring is 60 plugins/agentic-bundle-*/ directories". There are 58 agentic-bundle-* dirs (60 plugins/* dirs total), and they hold only 449 of the 6,634 entries (6.8%; median 8 entries each). The 3.13x inflation is two FULL CATALOG MIRRORS: plugins/agentic-awesome-skills-claude (2,049 entries) and plugins/agentic-awesome-skills (2,027) = 4,076 entries, digest-identical to skills/ (2,107 entries, all distinct). This reframes the ceiling scenario: "one more bundle render... adds ~2,117 entries" is impossible — a bundle is ~8-10 entries. The correct unit is "one more per-agent mirror", ~2,049 entries -> 67.9%, still over 65%. Sharper still: only 976 extra vendored sickn33 entries are needed to cross the ceiling (< half a mirror). The conclusion survives; the unit does not — and the correct unit is exactly the pattern the est-items card already documents.
2. "Read all 318 titles from grep -h '^title:' docs/roadmap/items/*.md" — there are 432 items / 432 title lines today (verified identical to origin/main except one unrelated file). The not-carded sweep was run over a smaller set than exists.
3. "Every one of them... none measures distinct content" is wrong.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
SCOPE OF MY REPRO — what I actually settled, and what I did not. - I materialised the corpus at the CURRENT taps.txt pins in my own disposable HOME and reproduced every headline number independently. The pre-refresh figures (10,152 / 5,790 / 62.1% / 34.5% / 3.16x) I took from the SHARED read-only eval-home at $TMPDIR/eval-home, which is still at the older pins — a read-only census, no writes. - The repo checkout was on a peer session's branch loop/missing-json, not main. I diffed every file this finding touches (scripts/eval_corpus.py, tests/eval/taps.txt, boost_cli/core/rag.py, .github/workflows/eval-corpus-refresh.yml, all of docs/roadmap/items/) against origin/main: identical except one unrelated roadmap item. The verification holds against main (origin/main af4bdbfd). - I could NOT isolate the causal mechanism of the 22 rank changes. The finding asserts "BM25 statistics (N and document frequency), not slot consumption". My data is CONSISTENT with that — dedupe_by_content does run over the full pool before k (rag.py:1053, confirmed), and recall@k is bit-identical while the rank-sensitive metrics move — but I did not separate idf from avgdl, nor rule out that a different byte-identical copy survives dedupe and carries a different grade_key. Write it as "consistent with", not "confirmed".
WHY HIGH RATHER THAN MEDIUM. The metric movement alone cannot fail anything: hit@1 +0.011, MRR +0.010, nDCG +0.008 against 0.06-0.10 of headroom over the floors. The severity is the ratchet's teeth, which the finder never traced.
Why it is worth doing. MAX_SHARE is the only shipped guard against the gate's corpus becoming one publisher's house style, and it is measured on a quantity a third party can inflate by ~3x without publishing a single new skill — so it can fire on a repo contributing a third of the content, and cannot fire on a repo that dominates the content without vendoring. The same raw count is the headline everywhere (10,152 entries / 62%) and is what the monthly refresh PR reports as growth, overstating real content growth 4x.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CORRECTED. No fix is prescribed here — the measurement is the contribution.
The corpus's 65.6% duplication is 99.93% inside a single tap, so the required gate never once exercises the cross-tap trust ordering dedup exists for — 0 swaps in 264,735 comparisons
Measured. Over the 91 required golden queries on the current pins, 264,544 of 264,735 collapse comparisons inside rag.dedupe_by_content (99.93%) were between two copies in the SAME tap and the source-preference branch executed 0 times; and forcing it to fire — marking one tap curated produces 191 swaps — leaves recall@k / hit@1 / MRR / nDCG@k at 0.8407 / 0.4835 / 0.6065 / 0.6552 both before and after, with all 91 graded ranked-key lists identical.
Reproduce it.
cd <repo>
export HOME=$TMPDIR/audit-corpus-verify && export BOOST_HOME=$HOME/.boost # after eval_corpus.py --ensure (see other finding)
.venv/bin/python - <<'PY'
import sys, json
sys.path.insert(0,"."); sys.path.insert(0,"scripts")
from boost_cli.core import rag
from boost_cli.core.rag import source_rank
stats={"collapses":0,"same_tap":0,"diff_tap":0,"swaps":0,"ties":0}
def patched(hits, limit):
best={}; out=[]
for hit in hits:
d=hit.get("content")
if not d: out.append(hit); continue
s=best.get(d)
if s is None: best[d]=len(out); out.append(hit); continue
stats["collapses"]+=1; kept=out[s]
…
What adversarial verification corrected. This card's numbers are the re-measured ones, not the ones first reported.
1. THE REMEDY CLAIM IS WRONG, and this is the correction that matters. The finding's why_it_matters says "adding a single mirror registry to taps.txt would be enough to make it visible." It would not. eval_retrieval.grade_key (scripts/eval_retrieval.py:197-209) keys every ranked slot on the content digest (body:<digest>), the exemplar class (cls:...), or the entry name — and a swap only ever replaces hit["entry"] INSIDE a content cluster, where the digest is identical by construction and, because catalog._content_digest hashes name + description + body, so is the name. Every return branch of grade_key is therefore invariant under a swap; the nohash:tap::skill_md branch at :209 is unreachable for a swapped hit because dedupe never collapses a hit with no digest. Proven, not argued: marking composio-community/awesome-codex-skills curated on the same pinned corpus fires 191 swaps (every cross-tap collapse becomes a swap — the composio copy arrives second in all 191) and leaves recall@k / hit@1 / MRR / nDCG@k at 0.840659 / 0.483516 / 0.606517 / 0.655233 before AND after, with all 91 graded ranked-key lists byte-identical. So a trust-ordering regression is invisible to the required gate BY CONSTRUCTION, regardless of corpus shape — the gap is that the harness never grades on source, not that the corpus lacks mirrors.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
SCOPE OF MY REPRO — read this before re-verifying. The finder's numbers reproduce ONLY on the CURRENT pins (10,731 entries). The shared read-only corpus at $TMPDIR/eval-home is PRE-REFRESH: 10 of its 20 clones are not at the SHAs in tests/eval/taps.txt (anthropics/skills, NeoLabHQ, LessUp, affaan-m/ECC, first-fluke, langchain-ai, minio, OneWave-AI, sickn33, anthropics/claude-agent-sdk-python), and it holds 10,152 entries. There the same instrumentation gives 1,986 clusters / 6,348 entries (62.5%) / 240,646 collapses / 240,455 same_tap / 191 diff_tap / 0 swaps. I materialised the current pins into a private copy (eval_corpus.py --ensure, network fetch works despite a harmless failed to store: 100001 commit-graph warning) to get the finder's exact figures. Anyone re-checking against $TMPDIR/eval-home will get the smaller set and should not read that as refuting the finding — the qualitative result (5 cross-tap clusters, all high/high, 0 swaps, ~99.9% same-tap) is identical on both.
DOC TRAP, not this finding's fault: tests/eval/taps.txt's own header prose says "10,152 entries" and "sickn33 … is 6,309", and CLAUDE.md repeats 10,152 — but the file's own per-repo rows sum to 10,731 with sickn33 at 6,634. The pins were moved in commit cbc0a58b ("test(eval): refresh the pinned retrieval corpus") and the header prose was not updated. A card author quoting corpus size must take the row sum, not the header.
NOT ALREADY CARDED, but read the existing card first.
Why it is worth doing. The required corpus contains essentially none of the duplicate shape that dominates a real install. A user's duplicates arrive as mirror registries republishing each other's skills across taps, which is what source_rank decides between and what determines where a user is told to install from; the gate's duplicates are one publisher re-vendoring itself into 60 plugin bundles, where every candidate has the same tap and the tie-break is a no-op.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CORRECTED. No fix is prescribed here — the measurement is the contribution.
The 2026-09-01 pin refresh moved the corpus and re-baselined it, but nothing re-derives the floors — CLAUDE.md's "~10% under measured" is now 7.2%–17.3%
Measured. The required gate's recall floor has 5.52 queries of headroom out of 91 ((0.8407 - 0.78) x 91), against the 6.55 that CLAUDE.md's published 0.852 implies — one golden query of margin the documentation says exists and does not, because commit cbc0a58b re-baselined the corpus to 10,731 entries and left CLAUDE.md, taps.txt and eval_corpus.py all stating 10,152 / 0.852.
Reproduce it.
cd <repo>
# 1. the refresh commit moved pins + baseline and nothing else
git show cbc0a58b --stat --format='%h %ad %s' --date=short
git show cbc0a58b -- tests/eval/baseline.json | grep -E '^[-+].*(recall|hit@1|MRR|nDCG)'
# 2. what taps.txt records NOW vs what the docs claim
awk '!/^#/ && NF>=3 {s+=$3; n++} END {print "rows:", n, " recorded total entries:", s}' tests/eval/taps.txt
grep -n '10,152\|0\.852' CLAUDE.md tests/eval/taps.txt scripts/eval_corpus.py
# 3. recompute the floor gaps against the committed post-refresh baseline
python3 -c "
import json
b=json.load(open('tests/eval/baseline.json'))['sets']['golden.jsonl@a0617183f8c9']['engines']['BM25 full-content']
…
What adversarial verification corrected. This card's numbers are the re-measured ones, not the ones first reported.
1. CAUSAL FRAMING IS WRONG (the headline correction). The title and claim say the 2026-09-01 pin refresh is what made "~10% under" untrue. It was untrue the day it was written. git log -S'~10% under' traces the sentence to 170d52c0 (2026-07-31), whose own commit message says the floors were "re-derived against the 20-tap numbers at the same ~10% relative headroom" against measured 0.863 / 0.473 / 0.607 / 0.662 — at which point the gaps were 9.6% / 15.4% / 14.3% / 12.4%, a 1.60x spread. The refresh WIDENED a spread (1.60x -> 1.82x -> 2.39x); it did not create one. Correct statement: "the floors were never uniformly ~10% under, and the refresh widened the spread from 1.82x to 2.39x."
2. "not the 6.60 the documented number implies" — 6.60 matches nothing. CLAUDE.md's published 0.852 gives (0.852-0.78) x 91 = 6.55 queries; the unrounded pre-refresh baseline 0.8406... gives 6.52. Use 6.55. (The optimism is therefore ~1.03 queries, not 1.1.)
3. "2.4x spread" -> 2.39x (rounding, harmless, but state it as 2.39x on a card).
4. DIRECTION OF DRIFT IS NOT UNIFORM. "every future passing refresh decays the calibration further" is true only of the SPREAD. This refresh moved two floors tighter (recall 8.4%->7.2%, nDCG 11.8%->11.5%) and two LOOSER (hit@1 15.3%->17.3%, MRR 14.0%->14.3%). A card must not say "the floors tightened."
5.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
SCOPE THE REMEDY CAREFULLY — the finding's phrase "nothing re-derives the floors" invites exactly the fix 170d52c0 warns against: auto-moving floors on every refresh ("Lowering a threshold deserves suspicion... a floor calibrated on an unrepresentative corpus measures the corpus"). The repo's design is deliberately fixed absolute floors with a relaxed regression-vs-baseline. The CONFIRMED defect is narrower and should be carded as such: the measured values and the "~10%" characterisation are stated in seven places across four files (CLAUDE.md:67,84; tests/eval/taps.txt:25,43; scripts/eval_corpus.py:30,108 and the runtime-printed operator message at :331-334), nothing re-states or guards them after a PASSING refresh, and no test asserts any of them.
SCOPE LIMIT OF MY REPRO #1: I did not re-measure 0.8407 / 0.4835 / 0.6065 / 0.6552. Those are the committed baseline written by cbc0a58b — which is also the finder's only source. Verifying them empirically needs a re-tap at the new pins, which is forbidden here (shared read-only corpus) and needs network. Every derived percentage in this verification inherits that.
SCOPE LIMIT #2: the shared $TMPDIR/eval-home is materialised at the PRE-refresh pins — live total 10,152 vs taps.txt's recorded 10,731, with the same 10 taps PIN-DIFFERS. make eval against it would trip eval_corpus.py's CORPUS DRIFT check, not run the gate.
Why it is worth doing. The "~10% under" statement is the entire published justification for where the four floors sit — CLAUDE.md calls it "loose enough that upstream drift can't flake the build, tight enough to catch a collapse." That is a claim about a margin, and the margin now differs 2.4x across the four metrics with recall the tightest at 5.52 queries out of 91. Because the monthly refresh re-baselines but never re-derives or re-states the floors, and prompts a human only when the refreshed corpus FAILS, every future passing refresh decays the calibration further with no signal at all.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CORRECTED. No fix is prescribed here — the measurement is the contribution.
make eval scores a corpus with every SKILL.md body missing, reports all four floors PASS, and scores HIGHER than the real corpus
boost explain's heuristic fallback prints every heading in the file — 541 lines for one skill — while the sibling list in the same function caps at 12
The CI-vs-Makefile floor-parity test compares only the floor VALUES, so changing -k in ci.yml turns a PASS into a FAIL with the test still green
The exemplar mechanism was applied to the ungated set only: golden.jsonl is 0/91 pinned, and 10 of its 43 hit@1 credits are on names the metric cannot adjudicate
Measured. On the 10,731-entry corpus that committed taps.txt materializes today, tests/eval/golden.jsonl — the only set the required eval gate floors — is 0/91 exemplar-pinned while the ungated golden-natural.jsonl is 50/50; 10 of its 44 hit@1 credits (22.7%) are awarded on a name that resolves to more than one distinct body, and the hit@1 floor's entire headroom is 7.60 queries, smaller than the 10 credits the metric cannot adjudicate.
Reproduce it.
cd <repo>
export BOOST_HOME=$TMPDIR/eval-home
# 0. confirm which corpus you are on (expect 10152)
.venv/bin/python -c "import sys;sys.path.insert(0,'.');from boost_cli.core import catalog;print(len(catalog.all_entries()))"; echo EXIT=$?
# 1. exemplar counts per set
python3 -c "
import json
for f in ['tests/eval/golden.jsonl','tests/eval/golden-natural.jsonl']:
r=[json.loads(l) for l in open(f) if l.strip() and not l.startswith('#')]
print(f,'rows:',len(r),'with exemplar:',sum(1 for x in r if x.get('exemplar')))"; echo EXIT=$?
# 2. the harness's own list of undecided gated rows
…
What adversarial verification corrected. This card's numbers are the re-measured ones, not the ones first reported.
The defect is real and the spine is exact, but five figures are stale by one corpus refresh. The finding measured on the 10,152-entry corpus CLAUDE.md documents; committed tests/eval/taps.txt moved on 2026-09-01 (cbc0a58b) and now materializes 10,731 entries, which is what make eval/CI builds today. Corrected values, all measured by me on that corpus:
- hit@1 credits: 43/91 = 0.473 -> 44/91 = 0.484 - un-adjudicated share of credits: 23.3% -> 22.7% (the count stays 10) - floor headroom: 6.60 queries -> 7.60 queries - worst-case bound stripping all 10: 33/91 = 0.363 -> 34/91 = 0.374 (still below the 0.400 floor) - BM25 four-metric line: 0.852 / 0.473 / 0.605 / 0.657 -> 0.841 / 0.484 / 0.607 / 0.655
The finding's numbers are correct for the corpus it names and it disclosed the provenance honestly; they are nonetheless the wrong numbers to print about "the set the required eval gate floors", because the gate no longer builds that corpus. A card must publish the 10,731 figures and name the corpus.
Nothing else in the finding is wrong. 0/91 vs 50/50, the 27 undecided rows / 62 candidate bodies, the same 10 names and 10 queries, the skill-creator worked example, the not-carded check, and the "realistic shift is 1-2 queries, the floor still passes" bound all hold on BOTH corpora.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
1. WHAT IS CORPUS-INDEPENDENT (identical on 10,152 and 10,731): exemplar counts 0/91 and 50/50; 27 undecided worksheet rows over 62 candidate bodies; the same 10 ambiguous-credit names and the same 10 queries; the skill-creator example. Only the credit count and the two ratios moved. Build the card on the invariant half and quote the 10,731 figures for the rest.
2. TRAP FOR THE NEXT VERIFIER: the shared read-only $TMPDIR/eval-home is a PRE-REFRESH corpus — its .eval-corpus-ready sentinel digest does not match sha256(tests/eval/taps.txt), and 10 of its 20 clones sit at a different commit. Anyone who measures only there reproduces the finding's old numbers verbatim and marks it CONFIRMED without noticing. Rebuilding at the committed pins in a disposable HOME took ~3 minutes and is what produced the correction.
3. DO NOT COPY Makefile:126, which still comments "over twenty it scores 0.863 / 0.473 / 0.607 / 0.662". That matches neither corpus (10,152 gives 0.852/0.473/0.605/0.657; 10,731 gives 0.841/0.484/0.607/0.655). CLAUDE.md:67's figures are right for the old corpus and now also stale.
4. SCOPE OF MY REPRO: I verified the BM25 engine only (--engines bm25), which is what the gate floors; I did not build a dense store (impossible here). I did not run make eval end to end, only its second command against a corpus I materialised with its first.
5. NOT A DEFECT IN THE HARNESS: exemplar_worksheet (scripts/eval_retrieval.py:219) and exemplar grading work correctly and fail loudly on a bad pin.
Why it is worth doing. golden.jsonl is the set the required eval gate floors, so it is the only one that can block a merge. 23.3% of its hit@1 credits are awarded on a name that maps to several genuinely different skills, and the floor's whole margin (6.60 queries) is narrower than the un-adjudicated credit count (10). That does not mean retrieval is worse than reported — the realistic shift is 1-2 queries — it means the published margin cannot be read as precision about the intended skill, so anyone tuning blend weights or pool depth against this gate inherits an unquantified slack.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CORRECTED. No fix is prescribed here — the measurement is the contribution.
boost info/deps on a not-installed rule or workflow widens the tap's sparse cone for a directory source_dir_for immediately rejects
boost_install's description enumerates four of the five enabled agent targets and omits Antigravity CLI, the fourth linking agent
boost_search advertises "10-15 seconds" unconditionally; with no AI configured it is 0.013 s median and the rerank never runs
Write-up · mcp-search-cost-overstated-on-keyless-machines.md
The skip list — the one bound on boost's triggers — ships in INSTRUCTIONS only, in zero of the seven tool descriptions
out.panel still fits its content to term_width(), so boost count | … clips the line to an assumed 80 columns
quickstart silently discards the incompatible shard status, so a user with any API key is never told why zero vectors arrived
A local manifest read error is reported as "no published shards", and the BoostError's hint — the only actionable line — is discarded
Write-up · quickstart-manifest-error-drops-hint-and-misnames-cause.md
With every registry unreachable, quickstart prints "✓ indexed 0 items" and "✓ ready", exits 0 — and the command it recommends exits 1
Write-up · quickstart-says-ready-exit-0-after-every-tap-failed.md
The shard download is invisible both before and during: --catalog --dry-run never names the 1,604.8 MB, and the live fetch passes no progress callback and has no spinner
Measured. On a virgin HOME, boost quickstart --catalog --dry-run prints exactly two lines — "would tap 464 registries (459 pinned to a published shard's commit)" and "would build the keyword index, then import 459 shard(s)" — and never names the 1,604.8 MB (1,604,753,775 bytes) those same 459 manifest rows sum to, although the dry-run has already read the manifest that carries every row's bytes and shards._size_label() exists to format it.
Reproduce it.
cd <repo>
grep -n 'on_event' boost_cli/commands/quickstart.py # -> no matches
grep -n 'on_event' boost_cli/commands/discovery.py boost_cli/commands/pkg.py
sed -n '195,215p' boost_cli/commands/quickstart.py # Spinner at 195, bare sync at 209
curl -sSL -o $TMPDIR/mf.json https://github.com/jonnyeclectic/boost/releases/download/shards-latest/manifest.json
python3 -c "import json;d=json.load(open('$TMPDIR/mf.json'));print(len(d['shards']),'rows', '%.1f MB' % (sum(r.get('bytes',0) for r in d['shards'])/1e6))"
export HOME=$TMPDIR/audit-qs-f3 ; export BOOST_HOME=$HOME/.boost ; mkdir -p "$HOME"
env HOME="$HOME" BOOST_HOME="$BOOST_HOME" BOOST_SHARD_MANIFEST="file://$TMPDIR/mf.json" \
…
What adversarial verification corrected. This card's numbers are the re-measured ones, not the ones first reported.
One stated detail is wrong: "shards.sync() … both other callers pass one."
shards.sync has THREE call sites, and only ONE passes on_event: - boost_cli/commands/discovery.py:409 (sync) — passes on_event=None if args.as_json else _shard_event at :414. ✓ - boost_cli/commands/quickstart.py:209 (sync) — passes none, no Spinner. ✗ - boost_cli/commands/pkg.py:941 (_resync_vectors) — shards.sync(list(by_name), by_name, manifest=manifest), passes NO on_event and is inside no Spinner either. ✗
The finding's second citation, pkg.py:997, is the on_event= line of a shards.ingest( call that starts at pkg.py:993 — not a sync caller. Both quoted line numbers (414, 997) are accurate as lines; the framing "both other [sync] callers pass one" is not, and so is the implied "quickstart is the lone outlier".
Everything else re-derived and correct to the digit: 459 manifest rows; 1,604,753,775 bytes = 1604.8 MB (1530.4 MiB); the five per-default byte counts and chunk counts verbatim; expo/skills and K-Dense-AI/scientific-agent-skills have NO ROW; 17,088,844 = 17.1 MB for the seven defaults; the two dry-run lines verbatim including "464 registries" / "459 shard(s)"; the --catalog help text verbatim.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
SCOPE LIMITS OF MY REPRO — a card must not overstate these: 1. The live silence was measured on the SEVEN DEFAULTS only, and the sandbox proxy truncates response bodies (every shard ended "failed verification" — bytes were received and hashed, then rejected). My 2.19 s gap ([3.93s] "indexed 962 items" -> [6.12s] first shard line) is a LOWER BOUND; a complete 17.1 MB fetch takes longer. The finder measured 2.53 s and 1.98 s — same shape. 2. --catalog was NEVER exercised live by me (it would pull 1.6 GB, and the proxy truncates). 1,604.8 MB is manifest arithmetic — the sum of bytes over all 459 rows — not a stopwatched transfer. It is the right number for a virgin HOME, where no row is "current" and all 459 download, but write it as a sum, not as a measurement. 3. The dry-run repro requires dense.have_backend() True (the repo .venv has [rag]). Without the extra the manifest is never fetched and the dry-run prints a different "0 because …" line — a different defect, already carded in audit-quickstart-findings.md. 4. The live run needs the REAL manifest URL. With BOOST_SHARD_MANIFEST=file://… the host check refuses every shard ("shard URL … is not on the manifest's own host") and no download is attempted, so that override is fine for the dry-run repro but useless for timing the live path.
FIX SCOPE IS WIDER THAN THE FINDING SAYS: because pkg.py:941 (_resync_vectors) is also a silent shards.sync caller, a fix that only touches quickstart leaves two of three sync sites inconsistent.
Why it is worth doing. --catalog is the one expensive, mostly-irreversible decision quickstart offers a brand-new user, and both surfaces that exist to describe it — --help and --dry-run — omit its dominant cost. A user on a laptop tether or a metered link is asked to approve "459 shard(s)" with no way to learn that means 1.6 GB, and once it starts there is no spinner, no per-shard line and no byte counter to tell them how far along it is or that anything is happening at all. Every ingredient of the fix is already in the file: pass _shard_event-style on_event, and sum row['bytes'] through the existing _size_label in the dry-run line.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CORRECTED. No fix is prescribed here — the measurement is the contribution.
README's "81 commands" table enumerates only 80 — the missing one is quickstart, the README's own first command
Write-up · readme-81-command-table-lists-80-omits-quickstart.md
README's hero block ends in exit 1: boost install tdd-workflow names a skill no starter registry ships
Write-up · readme-hero-installs-a-name-no-starter-registry-ships.md
boost recommend sizes the description cell per row from that row's because: text, so neither column lines up
The corpus refresh re-baselines only golden.jsonl, so golden-natural.jsonl's baseline silently describes a corpus that no longer exists
Measured. On the corpus tests/eval/taps.txt pins today (10,731 entries, 20 taps), eval_retrieval.py --golden tests/eval/golden-natural.jsonl -k 10 exits 1 and prints "REGRESSION vs baseline: catalog.search recall@k: 0.080 -> 0.060 (-0.020)" — while the keyword set on that identical corpus exits 0 and reproduces its baseline to four decimals (0.841/0.484/0.607/0.655), because the September refresh commit cbc0a58b moved golden.jsonl's baseline and left golden-natural.jsonl's describing the 10,152-entry corpus it replaced.
Reproduce it.
cd <repo>
# 1. the refresh re-baselines with no --golden, and the default is golden.jsonl
sed -n '112,119p' .github/workflows/eval-corpus-refresh.yml
grep -n 'DEFAULT_GOLDEN\|--golden' scripts/eval_retrieval.py
# 2. the refresh commit changed ONLY the golden.jsonl set
git show cbc0a58b -- tests/eval/baseline.json | grep -nE 'golden|^[-+] *"(recall|hit)'
# 3. the natural baseline still holds pre-refresh numbers
python3 -c "
import json;s=json.load(open('tests/eval/baseline.json'))['sets']
for k,v in s.items(): print(k, v['engines']['BM25 full-content'])"
# 4. and they reproduce exactly on the PRE-refresh corpus
export BOOST_HOME=$TMPDIR/eval-home
…
What adversarial verification corrected. This card's numbers are the re-measured ones, not the ones first reported.
The mechanism and every load-bearing measurement are correct. Four stated details are wrong:
1. LINE NUMBER. if drop > eps: is at scripts/eval_retrieval.py:545, not 546. (The other three cited lines are exact: DEFAULT_GOLDEN at 66, --golden default at 656, --regression-eps default 0.02 at 668, and the workflow's bare --save-baseline at eval-corpus-refresh.yml:119.)
2. ROADMAP FILENAME. The finding's not_carded_check names nothing-refreshes-the-eval-corpus-pins; no such file exists. That string is the card's TITLE. The file is docs/roadmap/items/eval-corpus-pins-have-no-refresh-path.md (status: shipped, pr: 431). Its conclusion still stands — I read it, and its only baseline sentence is "regenerate baseline.json", singular, with nothing about which sets a refresh maintains.
3. TITLE COUNT. "Checked all 318 titles" — there are 432 item files (405 board: code, 27 board: design). I re-ran the not-carded check over all 432 and reached the same conclusion.
4. UNDERSTATED, NOT OVERSTATED — the important one. The finding frames the false regression as a future risk ("a 2-query shift (0.040) reports a confident REGRESSION"). It is not future. On the corpus the current taps.txt pins, the natural set ALREADY exits 1 today with REGRESSION vs baseline: catalog.search recall@k: 0.080 -> 0.060 (-0.020) — the drop computes to 0.020000000000000004, which clears eps=0.02.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
SCOPE OF MY REPRO — what it does and does not settle. - It settles the mechanism completely: I read the current workflow and argparse default, walked every commit that ever touched baseline.json, and confirmed cbc0a58b is the first to move one set of two. - It settles the consequence empirically, which the finding did not: I built the post-refresh corpus (network fetch of the 8 moved pins onto copies of the shared clones, in HOME=$TMPDIR/verify-5, since deleted) and got a real exit-1 false regression on the natural set with a clean exit-0 control on the keyword set. That pair is the card's evidence — one command, two sets, same corpus, opposite results. - Measurement noise seen: BM25 MRR on the natural set came out 0.235 on the --build run and 0.237 on the next run against the same index. Do not quote the natural set's post-refresh MRR to three decimals in a card. recall@k (0.360), hit@1 (0.160) and the catalog.search regression line were stable across both runs.
BLAST RADIUS — do not overstate it in the card. The natural set is NOT wired into any gate. make eval and CI run golden.jsonl only, and both pass --regression-eps 1, so the required gate is untouched and stays green. grep for golden-natural across *.py/*.yml/Makefile/*.sh finds it only in tests/unit/test_eval_baseline.py and test_eval_grading.py (which test the harness, not the corpus) plus CLAUDE.md and roadmap prose.
Why it is worth doing. CLAUDE.md documents baseline keying as the fix for exactly this class of bug: "Before that, running the natural-language set printed eight confident 'REGRESSION vs baseline' lines that were only the gap between two different question sets." The key was made query-set-aware but not corpus-aware, and the monthly refresh only maintains one of the two sets. The natural set is the only instrument the project has for measuring whether a user's plain-English question finds the right skill (it scores BM25 at recall 0.360 / hit@1 0.160, and 0.077 / 0.000 on its 13 workflow rows) — so it is the set that matters most for judging the keyless-dense work, and its reference point is now silently wrong.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CORRECTED. No fix is prescribed here — the measurement is the contribution.
The two roadmap boards' install footers still advertise Python 3.9+, four minor versions under the real floor
search caps the name column at 32 and the tap column at 20 at every terminal width, while the description column grows without limit
The search footer is the one unfitted line on a screen search_layout just fitted: 55 columns in a 40-column pane
Write-up · search-footer-not-fitted-to-the-pane-its-rows-were-fitted-to.md
boost search's footer overflows a 40-column pane by 2-15 cells, and the gate that swears it doesn't passes only because its fixture returns exactly one match
Write-up · search-footer-overflows-40-col-pane-gate-passes-by-luck.md
The relevance meter and its "one gradient moment" are constant on the default result page: 138/150 rows full bars, 150/150 the same colour
Write-up · search-relevance-meter-is-constant-on-default-page.md
boost search rows fit to term_width(), not pane_width(), so a piped search silently loses the TAP column entirely
Write-up · search-rows-use-term-width-so-piping-drops-the-tap-column.md
The contributor-onboarding gate tables state three wrong suite sizes, and README and CONTRIBUTING disagree with each other
rag.surface's de-hyphenated name copy is justified by two claims that are both false, and its real effect — an undocumented 3x name / 2x description field weight — is guarded by a test …
_fit_widths shrinks data columns to a bare "…" and still overflows: boost taps is 54 columns wide on every terminal narrower than 54
Write-up · table-fit-widths-floor-destroys-columns-and-still-overflows.md
Tier 3's false-call ceiling is unreachable at its own default N, and tolerates zero false calls at the N make eval-tools uses
An unreadable tap cache is invisible to doctor ("✓ 1 tap cloned & cached", exit 0) while search, browse and heal all exit 70 with a crash report
Write-up · unreadable-tap-cache-healthy-doctor-crashing-heal.md
"! agent dir ~/.cursor/skills is not writable" is the one doctor issue with no next action — heal has no path for it and the next install crashes at exit 70
Nothing boost prints ever names an entry point: bare ./boost is byte-identical on a virgin machine and a working one, and the one command its failure-hints route you to is the only setup …
golden-natural.jsonl — the only fully exemplar-graded query set — is invoked by no make target and no workflow, so its numbers can only be produced by a human typing the command
Measured. Running the natural set today against the same 20-tap eval corpus prints "REGRESSION vs baseline: catalog.search recall@k: 0.080 -> 0.060 (-0.020)" (BM25 MRR 0.2447 -> 0.237, nDCG 0.2640 -> 0.259) — the snapshot has already drifted, and no Makefile target or workflow passes --golden, so nothing in the repo can ever emit that line.
Reproduce it.
cd <repo>
grep -n natural Makefile ; echo "makefile grep exit $?"
grep -rn natural .github/workflows/ ; echo "workflow grep exit $?"
grep -rn 'golden-natural' Makefile .github/workflows/ scripts/ tests/ | sed -n '1,20p'
sed -n '1,23p' tests/unit/test_eval_baseline.py
python3 -c "import json,hashlib,pathlib; b=json.load(open('tests/eval/baseline.json')); print('baseline keys:',list(b['sets'])); print('current digests:',[p+'@'+hashlib.sha256(pathlib.Path('tests/eval/'+p).read_bytes()).hexdigest()[:12] for p in ('golden.jsonl','golden-natural.jsonl')])"
What adversarial verification corrected. This card's numbers are the re-measured ones, not the ones first reported.
1. BASELINE NUMBERS ARE WRONG. The claim says the golden-natural row carries "0.350 / 0.160 / 0.245 / 0.259". It does not. tests/eval/baseline.json's golden-natural.jsonl@0d91b0cd8e41 BM25 row is 0.360 / 0.160 / 0.2447 / 0.2640. The quoted 0.350/0.160/0.245/0.259 are the *pre-migration* ("name-graded (before)") figures at docs/roadmap/items/golden-set-grades-by-name-not-by-skill.md:125; the baseline holds the *post*-migration row from line 126-127 (0.360 / 0.160 / 0.245 / 0.264). The claim read the wrong line of the card.
2. "the only tests that touch it are explicitly synthetic" IS WRONG. tests/unit/test_eval_grading.py reads the REAL shipped file in three live assertions: test_the_shipped_set_has_nothing_left_to_decide (line 217-228, asserts len(rows) == 50 and no unpinned row) and class TestTheMigrationIsFinished (line 247+): test_every_row_pins_an_exemplar, test_every_exemplar_is_well_formed, test_no_exemplar_is_a_localised_copy. Only tests/unit/test_eval_baseline.py is synthetic. The defect survives the correction, because those tests grade the file's SHAPE (row count, exemplar presence and syntax) and never run retrieval — so they cannot move or invalidate a single number in the baseline row. The accurate statement is: automation pins the query set's shape, nothing re-measures its scores.
3.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
Scope and limits of my repro: - The structural half (no --golden in any Makefile target or workflow; --golden defaults to golden.jsonl at scripts/eval_retrieval.py:66/:656) is exhaustive over Makefile, .github/workflows/ and scripts/ — read, not inferred. - The drift measurement was run against a PRIVATE COPY of the shared 20-tap eval corpus (I copied cache/ + config.json into $TMPDIR/lens2-exemplar and never wrote to the shared home). I did not verify that this corpus is byte-identical to the one that produced the committed baseline row: Makefile:130-133 says the corpus "tracks upstream HEAD rather than pinned commits", while CLAUDE.md says every taps.txt row pins a SHA — the repo disagrees with itself there and I did not resolve it. So the -0.020 delta could be corpus movement rather than ranker movement. That distinction does not affect the finding: corpus movement is exactly the thing a re-run is supposed to report, and no re-run happens. - I did not run make eval, make check, or any workflow; I read their recipes. - I did not measure how long a make eval-natural would add (the eval corpus was already materialised for me, so I never paid the tap cost). - catalog.search's numbers here are near the floor of resolution (3 of 50 queries vs 4 of 50), so its -0.020 is one query; the BM25 MRR/nDCG drift is below the default 0.02 eps and would not have been flagged even if someone ran it.
Why it is worth doing. CLAUDE.md documents the two-set design ("Baselines are keyed by query set (name@content-digest), so one file holds both golden.jsonl and golden-natural.jsonl without either overwriting the other") as if both sets are exercised. Only one is. The set that received 50 hand-made relevance judgments — the project's best available measurement of retrieval quality, and the one whose grading key actually identifies a skill — contributes to no gate and no scheduled monitor, so the investment decays silently. Adding a make eval-natural target (or a non-blocking scheduled run alongside eval-stats.yml) would cost one recipe and turn a frozen snapshot back into a signal.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CORRECTED. No fix is prescribed here — the measurement is the contribution.
The exemplar migration never reached the query set the required gate runs: 0 of 91 rows, and 27 of them have an ambiguous target on the gate's own corpus
Measured. Both invocations of the merge-blocking retrieval gate — the Makefile eval target and ci.yml's "retrieval quality gate" step — run scripts/eval_retrieval.py with no --golden, so both take DEFAULT_GOLDEN = tests/eval/golden.jsonl; 0 of that file's 91 rows carry an exemplar, and on the gate's own 20-tap corpus 27 of the 91 rows have at least one target name that resolves to more than one content digest (21 of 77 distinct target names are ambiguous).
Reproduce it.
cd <repo>
python3 -c "import json; rows=[json.loads(l) for l in open('tests/eval/golden.jsonl') if l.strip() and not l.startswith('#')]; print(len(rows),'rows;',sum(1 for r in rows if r.get('exemplar')),'with exemplar')"
python3 -c "import json; rows=[json.loads(l) for l in open('tests/eval/golden-natural.jsonl') if l.strip() and not l.startswith('#')]; print(len(rows),'rows;',sum(1 for r in rows if r.get('exemplar')),'with exemplar')"
grep -n 'DEFAULT_GOLDEN' scripts/eval_retrieval.py
sed -n '134,139p' Makefile
sed -n '269,279p' .github/workflows/ci.yml
export BOOST_HOME=$TMPDIR/eval-home
python3 -c "
import json,glob,collections,os
n2c=collections.defaultdict(set)
…
Verification found nothing to correct. Every number, file:line and command output above was independently re-derived and matched exactly.
What is NOT established. Recorded because a card that overstates its own evidence is worse than no card.
Severity lowered from the claim's High to Medium on a measurement the claim did not make. I inspected the bodies behind the ambiguous names and they are two different things. Genuine homonyms exist — brand-guidelines is Anthropic brand colors vs OpenAI brand colors vs Sentry *copy writing* (three different jobs), and pdf is a general PDF skill vs a workflow that converts PDF to Markdown with one specific tool — but they are a minority, and neither appears among the rank-1 hits. The names that actually score hit@1 through an ambiguous target (skill-creator, slack-gif-creator, theme-factory, prompt-engineering, mcp-builder) are re-publications of one upstream skill across anthropics/skills, composio-community/awesome-codex-skills and sickn33/antigravity-awesome-skills, differing by a few bytes. A "wrong body" there is still a correct answer to the user, so the looseness is much smaller in practice than in principle. I did not adopt the shipped card's "the looseness was latent" finding as a reason: that was measured at hit@1 0.160, where there was nothing for ambiguity to inflate, whereas this set measures 0.484 — a different regime, so it does not transfer.
Floor-margin exposure, with that qualification attached: hit@1 measures 44/91 = 0.484 against a floor needing 37 rows (margin 7 rows) with 10 rank-1 hits landing on an ambiguous name; recall@10 measures 77/91 = 0.846 against a floor needing 71 rows (margin 6 rows) with 20 rows matched only via ambiguous names.
Why it is worth doing. This is the only gate in make check that can fail a merge on retrieval quality, and every retrieval decision validated against it (RRF fusion vs preferring dense, pool depth, whether the LLM rerank earns its keep) inherits the looseness. The project already established the fix and built the harness for it; the required set simply never got migrated, and because the card is closed as shipped nothing tracks the remaining work on the set that matters most.
Found by an automated audit of retrieval/eval quality, the search & browse surfaces, and first-run onboarding; every finding was then re-measured from scratch by an independent adversarial verifier whose instruction was to refute it. Verdict: CONFIRMED. No fix is prescribed here — the measurement is the contribution.
The "taps last refreshed N days ago" hint can never fire on a machine that tapped and never ran boost update — the only writer of the marker is update itself
CLAUDE.md instructs the wrong licence: every new source file is told to carry GPL-3.0-only in a repo whose 373 headers, LICENSE and pyproject all say Apache-2.0
At a narrow pane boost hooks list drops name, the argument hooks remove -n takes, while host and scope survive
An unwritable agent rules/ or commands/ dir still crashes a rule or workflow install at exit 70
Write-up · unwritable-rule-or-workflow-dir-crashes-install.md
Two config.json shapes still slip past the corrupt-config handling. Invalid UTF-8 crashes every command, doctor included. {"taps": "x"} is a fresh install to doctor and a configured machine to --help.
Two cache writers still crash on what one sudo boost leaves behind. A read-only _names.txt fails heal, update and untap at exit 70 while doctor says healthy.
Write-up · cache-writers-that-still-crash-on-a-read-only-cache.md
Just before out.table drops a column, it shrinks the widest one with an ellipsis. In hooks list that is often name, and bmad-r… is not a name hooks remove -n accepts.
Measured on the branch that moved name to the front of boost hooks list, with the autopilot's hook names (bmad, bmad-route) piped at COLUMNS 40–100. name now shows at 52 widths, where it showed at 27. At 24 of those it is shortened, e.g. bmad-r…, in the widths 49–51, 57–59, 65–67, 74–79 and 83–91, the card's own width of 65 included. Before the reorder the same happened at 13 of 27.
The cause is the fitter's shrink-before-drop rule (output._fit_columns). Before it drops a column, it shrinks the widest one that can still shrink, down to _MIN_COL. For prose that is the right trade. For an identifier, a clipped value is worse than an absent one, because it reads like data and is not: boost hooks remove -n bmad-r… removes nothing. keep= cannot express "may drop, never shrink". It means never drop and never shrink, and two such columns together can outgrow the pane, the overflow PR 902 closed.
Likely fix: out.table takes a third class of column, whole or absent. The fitter never shrinks it and drops it in right-to-left order like any other column. hooks list would mark name, and taps and list would mark their NAME columns, the other arguments people copy. Measure every caller before and after: wide panes must stay byte-identical.
Under a read-only ~/.boost with no cache dir, update, heal and doctor crash at exit 70, and heal --dry-run says 0 for a run that crashes
Measured on the second release train and on a316c6b, where it behaves the same. Setup: tap the fixture, then rm -rf ~/.boost/cache; chmod 500 ~/.boost. search, info and browse now exit 0 (cache-writers-that-still-crash-on-a-read-only-cache), and reindex exits 1 naming ~/.boost. But update, heal and doctor exit 70 with crash reports. update dies in journal.log's ensure_dirs. The command whose job is to name the problem, doctor, is one that crashes.
heal --dry-run exits 0 in that state ("would rebuild catalog cache …") while heal exits 70. The cache-dir check heal and doctor gained in the first release train asks whether an existing directory refuses writes, deliberately, so that a missing one is not called unwritable. A missing cache dir under a parent that refuses the mkdir is a third case that neither branch names.
Likely fix: paths.ensure_dirs callers on the doctor, heal, update and journal paths tolerate a refused mkdir, like rebuild_tap now does. Doctor and heal then name the nearest existing directory that refuses writes, which is what rag._unsaved already does. The preview and the run must agree.
With an API key exported and no vector store yet, doctor and search send the user to a paid build while quickstart offers the free download
Found while verifying the quickstart shard-status fix. After that fix, shards.remedy()
tells a keyed machine with no store to unset VOYAGE_API_KEY and run
boost update --shards, which downloads the published keyless vectors for free.
boost doctor and boost search read dense.fix_hint("no-store")
instead, which says build it: boost reindex --dense. With a key in force,
that embeds the whole catalogue through the paid API.
That leaves two commands giving the same user contradictory advice, which is the thing
fix_hint exists to prevent. Fix direction: when the reason is
no-store and a key outranks the local model, have fix_hint answer with
shards.remedy() (or the same table row), so doctor, search,
quickstart and update --shards give one answer. Keep the manifest fetch off
the search path: search must not touch the network to word a hint.
A rule or workflow row for a disabled agent makes boost sync claim the same repair on every run, and doctor never goes healthy
Found while verifying unwritable-rule-or-workflow-dir-crashes-install. A rule
or workflow keeps one materialization row per agent. sync_plan reads every row,
but the repair it runs is an install, and an install writes only to
agents.materializing_agents(). A row for an agent that is no longer enabled is never
written and never cleared, so it is "missing" again on the next run.
Measured in a disposable HOME. Install a rule with ~/.cursor/rules at mode 500,
so the Cursor row is recorded as refused, then set agents.cursor.enabled to false. That
is a natural response when the dir was locked on purpose. boost sync then prints
re-materialized rule team-conventions on every run, and boost doctor stays
at rc 1 with "rule team-conventions was not written for cursor … boost sync writes it
once it is", a remedy that cannot work. This is not new with that change. On main, install the
rule, delete the Cursor file, disable Cursor, and run sync twice: it loops the same way for
any missing file of a disabled agent.
Fix sketch: have sync_plan and doctor skip rows for agents outside
materializing_agents() for that row's scope (and say once that the row belongs to a
disabled agent, with boost uninstall or re-enabling as the next step), or have
sync_apply claim a repair only when the rows it meant to fix are actually clean
afterwards. A test should run sync twice and assert the second run is "everything in sync".
The same fix must also cover store.unwritable_agent_dirs(), which reads the same
rows: a refused row of a disabled agent keeps its locked dir in doctor's issues and in sync's
warnings, with a boost sync remedy that never writes there.
Engine & command internals
// concrete file:line findings from the code scanSemantic search is gated behind an API key it does not need
Every dense search re-scanned all 3.08 GB of vectors — vec0 has no ANN index
Memoize config.load() in-process
Atomic skill install (temp-dir swap)
One shared atomic-write helper
Unify _tilde() — two copies have a boundary bug
Cache the catalog entry-set across RAG queries
Write-up · cache-the-catalog-entry-set-across-rag-queries.md
Stop re-serializing entry meta on every search
Write-up · stop-re-serializing-entry-meta-on-every-search.md
Prune ignored dirs during scan_dir walk
Single tech-stack prober
Single imperative-rule extractor
Split oversized command modules
Robust tag argument parsing
Localize the stored BM25 snippet
Frontmatter scalar over-coercion
Rule install — materialize rules into each agent's native format
Workflow install — drop commands/subagents into each agent's native dir
Workspace scope — boost install --local into the project
boost list shows installed rules and workflows
Teach the rest of the CLI about project scope
boost update refreshes installed rules and workflows
Scan and sync rules/workflows like skills
boost install --scope user|project for rules/workflows
Ambiguous tap short-name resolution silently picks the wrong tap
sync --apply deletes any broken symlink, not just boost's own
Dense search's empty result skips the BM25 fallback
Journal rotation has a lost-update race between concurrent processes
boost uninstall has no confirmation prompt
update/reinstall silently widen a skill's agent scope
lint --tap mis-scores rule/workflow entries as broken
sync relinks a narrowed skill into every agent
Dependabot raises every toolchain bump twice
Write-up · dependabot-root-pip-entry-duplicates-requirements.md
boost onboard silently overwrites existing generated files
Write-up · onboard-overwrites-generated-files-without-confirm.md
Dependabot cannot regenerate the hash-pinned locks
Negative -n silently inverts log/pulse output
The toolchain lock has no proactive update path any more
boost log --crashes listing branch has no non-empty test
boost serve's own path-traversal guards are untested
Diagnostic log has no structured/JSON output mode
AI bridge swallows failures with zero diagnostic trail
The eval gate reports a perfect score on a corpus 4× smaller than a real user's
boost info rejects the tap-qualified name its own error tells you to type
The axe-core sweep intermittently fails color-contrast on a page the PR never touched
The eval gate would not pass on the catalogue its own users have
Write-up · eval-corpus-is-96x-smaller-than-a-real-install.md
The golden set grades by name, and 35 of 53 names are ambiguous
The “pinned” eval corpus pinned names, not commits
The eval de-duplicated its ranked list by name, so homonyms shared a rank
62% of the required gate's corpus is a single third-party repository
Nothing refreshes the eval corpus pins, so the gate measures one frozen day
agents recorded the request, not what was linked
Write-up · agents-field-records-the-request-not-the-links.md
the Python floor moves from 3.9 to 3.12
the typing.List → list sweep the floor now allows
audit the 16 zip() calls the 3.12 floor made checkable
boost search never noticed a tap added after the first search
boost update told you to run a flag it then rejected
one deleted upstream stopped boost update for every other tap
The update path skipped the scan the install path runs
heal removed symlinks boost never created
bring scripts/ under the ruff gate
install --dry-run predicted an install that never happens
boost_search never said which ranking produced its answer
est_items counted one skill fourteen times once registries went multi-agent
The catalog was missing the two most-starred token-efficiency registries
Dependabot splits one action repo into three unmergeable PRs
The shards workflow has never once produced a shard
The repair command could not repair the thing two commands sent you to it for
Taps download and check out the 84% of a repo that boost never opens
A catalog entry knew where it came from, never what it was
a convention that said "verify the repo is real" verified nothing
Write-up · the-catalogue-advertised-repos-that-no-longer-exist.md
Path.exists() looks total, and is not
a publisher that could not publish, and an alert that could not stand down
the shard job that had never once finished, and the timeout that could not be raised
the scheduled re-pin that refreshed the twenty rows it must not touch, and none of the hundred and sixty-five it existed to pin
--category marketing matched nothing, while four marketing registries sat in the catalog under other names
reindex --dense runs for hours behind a bare spinner, and a cancel discards all of it
Dense reuse is per tap, so one changed file re-embeds the whole registry
The dense store keeps one vector per copy, not one per distinct text
adapt renders every sibling of a flat agents/-dir workflow as a 138-agent crew
Write-up · audit-adapt-renders-every-sibling-of-a-flat-agents-dir-workflow-as.md
reinstall, sync repair and audit resolve by name+tap, ignoring the lock's source path
Write-up · audit-reinstall-sync-repair-and-audit-resolve-by-name-tap-ignoring.md
Pinned taps silently moved to HEAD by update re-clone and compact --reclone, pin left stale
Write-up · audit-pinned-taps-silently-moved-to-head-by-update-re-clone-and-co.md
focus/profile sideline by unlinking without recording it; list lies, doctor exits 1, and its own remedy (sync) undoes the switch
Write-up · audit-focus-profile-sideline-by-unlinking-without-recording-it-lis.md
snapshot restore replaces the lock wholesale, orphaning newer rules' CLAUDE.md blocks
Write-up · audit-snapshot-restore-replaces-the-lock-wholesale-orphaning-newer.md
boost create: CLI audit findings (2026-08)
boost reindex: CLI audit findings (2026-08)
quickstart reruns re-download every shard; shards.sync never asks what is built
Two concurrent rag.build() runs delete each other's temp index
Write-up · concurrent-rag-builds-delete-each-others-temp-index.md
compact --reclone left a clone on HEAD when it could not reach the pin
Write-up · reclone-leaves-a-clone-on-head-when-it-cannot-reach-the-pin.md
compact <tap> answers a question about one tap with a global all-clear
Write-up · compact-answers-a-named-tap-with-a-global-all-clear.md
boost taps vouches for a clone that is not there
reindex reported a completeness share it could not compute
Write-up · reindex-reported-a-completeness-share-it-could-not-compute.md
A scan that stops the bare-int count flag coming back
Code health & security
// planned · free tooling to catch vulns, smells & bugsSecurity linting — bandit via ruff S rules
Dependency CVE gate — pip-audit
Retrieval-quality eval harness (Tier 1 + rerank lift)
Supply-chain posture — OpenSSF Scorecard
Grow & diversify the golden set for statistical power
OpenSSF Best Practices — all 67 passing criteria answered
Secret scanning — gitleaks + push protection
Complexity & dead-code radar — xenon · vulture
OSPS Baseline — three levels, audited rather than assumed
Significance-tracked engine comparison (ranx monitor)
Quality dashboard — SonarCloud (free for OSS)
OpenSSF silver — reachable solo, and mostly already true
Property-based tests — hypothesis on the parsers
Write-up · property-based-tests-hypothesis-on-the-parsers.md
Coverage-guided fuzzing — atheris / OSS-Fuzz
OpenSSF gold — how far it goes without a second human
Fork-safe network layer — explicit ProxyHandler
The README read like a machine wrote it — measurably
main went red because "require branches up to date" was never actually on
Log timestamps are local time mislabeled Z
mypy's default mode skips untyped command bodies
ruff 0.16 widens the default rule set — 83 new errors on a version bump
The lock invariant can't parse name[extra]==version, so a valid pin fails the gate
boost doctor checks installed rules and workflows
rel_time tests race the wall clock and flake on a loaded runner
Gemini logged a skill conflict every session and boost doctor called the machine healthy
A machine-readable VEX feed, sourced from findings that already existed
A missing or corrupt lock file makes doctor prescribe boost sync/boost heal, and both delete every live agent symlink of an intact install
Write-up · doctor-prescribes-sync-that-deletes-live-links.md
import --all skips the injection/secret scans and the per-skill report single import runs
Write-up · audit-import-all-skips-the-injection-secret-scans-and-the-per-skil.md
verify/drift say 'nothing installed' (exit 0) when the lock file is missing or corrupt
Write-up · audit-verify-drift-say-nothing-installed-exit-0-and-doctor-says-lo.md
Quarantine/pin state invisible: list/info/doctor report links and materializations that were removed
Write-up · audit-quarantine-pin-state-invisible-list-info-doctor-report-links.md
boost audit: CLI audit findings (2026-08)
boost doctor: CLI audit findings (2026-08)
boost drift: CLI audit findings (2026-08)
boost fingerprint: CLI audit findings (2026-08)
boost health: CLI audit findings (2026-08)
boost lint: CLI audit findings (2026-08)
boost policy: CLI audit findings (2026-08)
boost quarantine --release: CLI audit findings (2026-08)
boost test: CLI audit findings (2026-08)
boost verify: CLI audit findings (2026-08)
Three commands that denied what was on the disk in front of them
The lock kept advertising links it had just removed
Pipeline & supply-chain integrity
// planned · free tooling to secure the CI/CD path itselfPrebuilt vectors are published where no new user can reach them
Workflow SAST — zizmor
Every weekly shard run re-embedded the whole catalogue
Workflow linting — actionlint
Build provenance — SLSA attestations
SBOM on every release — CycloneDX / Syft
SBOM-aware scanning — osv-scanner
Second type checker — pyright
Widen the ruff rule surface — B·SIM·C4·PERF·RUF
Patch-coverage gate — diff-cover
publish.yml ignores the pip-audit / metadata gates
No timeout-minutes on any CI job
Dependabot's pip entry misses pyproject.toml's extras
License-compliance scanning of the dependency closure
The pytest tmpdir CVE — unfixable, then closed by the floor move
OpenSSF Scorecard's findings, triaged into three piles
main has no branch protection, so the release rules are honour-system
adapter-conformance's LangGraph leg never passed — a quoted matrix value
The required-check gate could not see paths:, so it green-lit a list that deadlocks every PR
Write-up · required-checks-can-declare-a-check-that-deadlocks-prs.md
ci.yml's job summary could exit 1 on its own, under the always() it was given
post-deploy.yml's always() destroyed the second signal it existed to preserve
markdownlint linted the fuzzer's corpus, so shipping a crash reproducer would redden a prose gate
Enabling a merge queue would deadlock every required check except ci.yml's
Write-up · merge-queue-would-deadlock-most-required-checks.md
The mutation gate was CI — 26 minutes, three times the next-longest job
The mutation gate's floor is a single file — shard 0 is store.py
A third of CI job time is spent waiting for a runner, not running
The required lint job pins zizmor==1.27.0 — a yanked release
The release verifies one commit and ships another
demo.yml still fails on every push — and the fix is not in the workflow
68 of the repo's 77 branches are merged loop/* branches nobody deletes
One commit can cut two releases, and the naive guard against it breaks retries
Near-duplicate items consume the top-10, and it gets worse with every tap
The BM25 index is one JSON blob, and it stops working between 10k and 50k items
Semantic search for users who will never set an API key
Dense retrieval today needs the [rag] extra and a
VOYAGE_API_KEY/OPENAI_API_KEY and a built
store. Most users will do none of that, so the default experience is BM25
forever. The keyless path is a local static embedding model —
potion-retrieval-32M class, MIT, model2vec family — which is not a
transformer: the entire weight file is one lookup table, so inference is
tokenize → gather rows → mean-pool → L2-normalize.
Measured locally, pure stdlib: ~1 ms to embed a query (mmap + bisect over
sorted keys), 12.8 MB of int8 vectors for 50k items at 256-d, and ~20 ms to
rerank BM25's top-200. No numpy, no sqlite-vec, no ANN index,
no new runtime dependency. import numpy alone costs 180–390 ms
cold, which disqualifies it from a one-shot CLI query path; the BM25 prefilter is
what makes the stdlib version viable, since a full 50k brute-force scan is 1.5 s
in pure Python. Pool depth is justified by measurement: BM25 recall saturates at
0.890 by depth 200 and gains nothing at 400, so reranking the top-200 gives up
essentially nothing versus scanning everything.
Why this and not a shipped Voyage index. A precomputed Voyage index is
inert without a Voyage query vector, and the only keyless way to get one
is a maintainer-run anonymous embedding endpoint — an unauthenticated free
embeddings API backed by the maintainer's card, which also ships every user query
off-machine and breaks offline. A local model is deterministic, so the artifact
becomes a cache rather than a correctness dependency: a newly tapped
repo can be embedded on the user's own machine. Doc-side is the asymmetry worth
shipping for — 29 ms/doc in pure Python is ~24 min for 50k single-core, versus
seconds in CI with numpy.
Do not ship this before the eval and dedup items. The headline claim
(+11.0 recall / +15.9 hit@1) did not survive verification: its baseline
used the kind oracle the real search path lacks, and both the blend weight
(w_dense=0.7) and the pool depth were argmax'd on the same 82
queries they were reported on, by 2-query margins. On a binary metric at n=82
the smallest net win reaching p<0.05 is 6 queries; hit@1 (+13 net
queries) holds up, recall (+9) sits at the resolution floor. And the structural
risk is real: the name is only ~10.5% of a mean-pooled surface vector while 106
description clusters are shared across 270 distinct names, so the lift may
shrink toward 50k rather than hold. Sequence: fix the gate, dedup, fix
the index format, then re-measure with McNemar and a held-out blend weight,
leading with hit@1. Test entry-level dense alone before the
blend, ship 512-d not 256-d (the whole case for 256-d was one query), keep
Voyage/OpenAI as the opt-in ceiling, and keep BM25 as the floor. The model table
cannot go in the default wheel — the shipped runtime is 0.79 MB and every merge
to main cuts a release, so +17.4 MB × ~24 releases/day exhausts PyPI's 10 GB
project quota in under a month; it needs a separate, rarely-released data
package behind an extra.
Related, and partly overtaken: [[keyless-semantic-search-for-everyone]] shipped a keyless
path using a transformer (BGE via ONNX Runtime, in the [rag] extra) while this
item was open. That does not settle the question this card asks — a static lookup table is
still far cheaper, and this card's discipline about not shipping a retrieval claim before the eval
still stands. What it does change is the baseline: "keyless" is no longer the differentiator, so the
case for a static model now rests on cost (~1 ms and no runtime dependency, against a
measured 233 ms cold and 34 MB of wheels) rather than on availability. Status left alone
deliberately — this is another loop's item to own.
Unblocked, and the case for it got stronger. This card says “do not ship this before the
eval and dedup items”. Both have now landed: the eval gate floors four metrics over a
realistic-sized corpus with baselines keyed to their query set, and content-hash dedup has merged.
The new evidence is a timing measurement. Building the shipped ONNX keyless store over 743
entries (3,740 chunks, bge-small-en-v1.5 on CPU) took 4,431 s — 74 minutes,
about 1.2 s per chunk. This card's static-embedding proposal claims ~29 ms/doc in pure Python. If
that holds it is a difference of more than an order of magnitude on the doc side, which is exactly
the cost that makes prebuilt shards mandatory today. Worth measuring the model2vec path directly
before committing — but the gap it claims to close is now a measured number rather than an
estimate.
Spike done — the prerequisites this card set have all shipped, so the measurement it asked
for was finally runnable. It says “do not ship before the eval and dedup items”;
dedup landed in #370, the index format in #367/#371, the
published eval in #373. What follows is potion-retrieval-32M (MIT, 63,091
× 512 F32 lookup table — confirmed a single tensor, no transformer) driven by a
hand-written pure-stdlib loader: WordPiece → gather rows → mean-pool → L2, mmap'd,
no numpy.
The two unverified performance claims were not just right, they were conservative. Query
embedding measured 0.16 ms against the card's ~1 ms. Document
embedding measured 1.34 ms on a synthetic 105-token doc and 3.27 ms on 300
real catalogue entries (median 61 tokens), against the card's ~29 ms.
That reverses one of this card's design arguments. The doc-side cost was the reason prebuilt
artifacts looked mandatory: “29 ms/doc in pure Python is ~24 min for 50k
single-core”. At the measured 3.27 ms it is 2.7 min — roughly the time
a first boost tap --defaults already takes. Local embedding is therefore viable on the
user's own machine, and shipped shards become a genuine optimisation rather than a requirement. (Not
to be confused with the ONNX bge-small path measured at ~1.2 s/chunk in
keyless-semantic-search-for-everyone; that number stands, and the gap between them
is the case for the static model.)
But reranking bought nothing at real scale, which is the result that matters. Over the 50
natural-language golden queries against a real 71,655-entry catalogue, reranking BM25's
top-200 by cosine scored hit@1 2/50 — identical to BM25's own
2/50, a net change of +0 queries where this card's own statistics note says 6 net
queries is the smallest win reaching p<0.05. On two hand-checked pairs the ordering was right but
the margin was thin (related 0.154 vs unrelated 0.097).
Stated limits, because this does not settle the question. The document vector was built from
name + description truncated to 1,500 characters, not the full body the real dense path
indexes, so this measures a weaker representation than the one being proposed. No blend was tried
— pure rerank, no w_dense — and this card explicitly asks for a held-out
blend weight and McNemar. What it does establish is that the cheap version of the idea does
not pay for itself, so the remaining work is representation and blending, not inference speed.
An unrelated finding fell out of it, and it is the more important one. BM25 scored
hit@1 0.040 here against the 0.340 published in #373. Both
are correct: the published figure is measured over the pinned 6-tap eval corpus of 743
entries, and this run used a real 77-tap install — 96× larger. Golden targets are
all present and rank 7th, 8th, 38th, 163rd rather than 1st. The eval corpus is not a scale model of
a real install, and the gate's floors describe a catalogue two orders of magnitude smaller than the
one users have. Tracked separately in [[eval-corpus-is-96x-smaller-than-a-real-install]].
Declined on measurement, after a second model was tried specifically to avoid declining on one
data point. The card's premise is that a local static model buys keyless semantic
search. Tested against the 50 natural-language golden queries over the pinned 20-tap corpus (3,843
entries as those registries stand today), with BM25 at hit@1 0.260 as the
baseline in every run:
potion-retrieval-32M (retrieval-tuned, 63,091×512 F32) scored dense 0.220,
hybrid RRF 0.260. potion-code-16M-v2 (code-domain, 63,457×256 F16 —
chosen because this catalogue is coding-agent skills, which is the strongest hypothesis for
why a general retrieval model would underperform here) scored dense 0.240, hybrid
0.260. A third representation — name+description only, over the full 71,655-entry
catalogue — reranked BM25's top-200 to +0 net queries.
So: two models, three representations, no measurable gain, and fusion never beats BM25 alone.
The code model is one query better than the retrieval model, which at n=50 is inside the noise
(±0.02 per query) and should not be read as a trend.
The contrast is what makes this a decline rather than a shrug. The published eval measures a
real embedding model at hybrid 0.440 against BM25 0.340 — a genuine
+0.100. Static embeddings reproduce the cost profile that made the keyless tier attractive
(0.16 ms/query, 3.3 ms/doc, no dependency) but not the quality that made it worth
having. Cheap and no better than what ships today is not a tier; it is a second code path to
maintain for nothing.
What survives. The performance findings stand on their own and are already recorded above:
doc-side embedding is ~24× faster than the card assumed, which is why prebuilt shards are an
optimisation rather than a requirement for the real models in
[[keyless-semantic-search-for-everyone]]. The pure-stdlib loader (WordPiece → gather →
mean-pool → L2, mmap'd, F32 and F16) is proven workable if a future model justifies it.
What would reopen this. A static model that actually separates on this task — the bar
is beating 0.260 as a reranker, not merely producing plausible cosines. The two hand
pairs looked fine for both models (0.224 related vs -0.001 unrelated for the code model), which is
exactly why plausible similarity was not accepted as evidence.
boost chat — ask about skills in plain language, grounded in retrieval
One global concurrency group let any PR cancel any other PR's check
The fuzzer found a real crash and nobody was listening
The published metrics could never be published
A line-anchored suppression drifted, and make lint never ran the tool
Write-up · zizmor-ignores-drift-and-make-lint-never-ran-it.md
The sweep gate died at browser launch, not at a page
A cold search materialises 71,600 entries to print five
boost search brainstorm finds nothing, and brainstorming finds it
Reproducible release builds — the sdist half nobody's setuptools does for you
The weekly republish reached the machines that had never been set up
A query made only of characters tokenize drops returns zero results, and the documented catalog.search fallback is unreachable — boost search "C++" finds nothing on a machine holding …
Write-up · bm25-empty-tokenization-kills-catalog-fallback.md
boost search 'C++' returns zero and blames the catalogue: tokenize drops every 1-char token, and nothing ever says a term was discarded
Mutation shards re-run every mutant on every push of the same PR
The sweep gate died at a page, not at launch — and took six unrun checks with it
A new card filed as shipped always 404s its own write-up link
Write-up · a-new-shipped-card-always-404s-its-own-write-up-link.md
Developer experience & maintainability
// planned · free tooling to catch issues earlier & keep the code legiblePromote nav / footer into the shared style system
Write-up · promote-nav-footer-into-the-shared-style-system.md
Shift-left gate — pre-commit + pre-commit.ci
Layering guard — import-linter
Typo detection — codespell
Docstring coverage — interrogate
Performance-regression gate — pytest-benchmark
Modernization smells — refurb + pyupgrade
Coverage dashboard — Codecov (free for OSS)
Pin the lint toolchain so a release can't redden the gate
Runner egress monitoring — StepSecurity Harden-Runner
Write-up · runner-egress-monitoring-stepsecurity-harden-run.md
Required-status-checks list is prose-only, and already stale
No issue/PR templates or a code of conduct
make boost mcp the whole setup, and put all three kinds behind it
New BDD suite has zero CI wiring
A project-scoped install registers its MCP servers machine-wide
boost bmad needed Node and a per-project install before it did anything
boost adapt — render a skill as another framework's agent source
boost adapt --to langgraph — third framework renderer
boost adapt — multi-agent skills → crews/graphs, not one Agent
boost run — search → adapt → a live agent doing the task, in one command
MCP — make agents search boost before reinventing a skill
boost install resolves a skill's requires: closure
MCP-aware skills — declare and wire an .mcp.json on install
roadmap.html goes stale on every rebase, so a card and a merge race redden the whole matrix
make lint reports success when actionlint fails — and says it wasn't installed
MCP — check for a skill when a task starts, not only when authoring one
The second silent skip — actionlint runs, and checks no run: block at all
boost chat cites its sources by a number it never prints
Write-up · chat-cites-sources-by-a-number-it-never-prints.md
MCP — one benefit, one observable trigger (and stop routing through boost_info)
MCP — answer the veto that overruled the trigger ("a skill already matched")
boost-first — the one rule boost authors, offered opt-in at boost mcp register
The MCP instructions understated what a search costs by 100x
install dead-ends on a registry that vendors its own skills
boost completions completes command names and nothing else
install refused an ambiguous name and offered no way to answer it
boost discover <query> asks GitHub, instead of filtering whatever boost index happened to sample
Share the catalogue instead of making everyone re-tap it
boost-first carried the trigger that had already fired and lost — and could never be updated
boost serve becomes a searchable, faceted catalogue with a graph of the taps
completions --install could delete the config between its own markers
browse could not search for two words, and the fix reshaped the whole browser
One design system across search and browse
The smart rerank pays the LLM again for a search it already answered
The box drew 108 columns into an 80-column pane, and --help never asked how wide the pane was
The hints still run past the pane, and the worst one is pinned by six test files
MCP has no way to read a skill before installing it
clean counts failed removals as cleaned, journals the inflated count, and exits 0
Write-up · audit-clean-counts-failed-removals-as-cleaned-journals-the-inflate.md
config/policy set store type-unchecked values; consumers crash exit 70 and pin_only no freezes installs
Write-up · audit-config-policy-set-store-type-unchecked-values-consumers-cras.md
Corrupt settings/config/state JSON silently read as empty, then clobbered on the next write
Write-up · audit-corrupt-settings-config-state-json-silently-read-as-empty-th.md
Five exit-70 crashes on bad paths/data: catalog --export, serve --port, count, replay, infer -o
Write-up · audit-five-exit-70-crashes-on-bad-paths-data-catalog-export-serve.md
distill's heuristic merge drops repeated ``` fences/braces, writing a structurally corrupt SKILL.md
Write-up · audit-distill-s-heuristic-merge-drops-repeated-fences-braces-writi.md
create/distill/infer/absorb --install silently replace an installed (unpinned) skill and flip its lock provenance to local
Write-up · audit-create-distill-infer-absorb-install-silently-replace-an-inst.md
hooks remove -n cannot find hooks boost itself added (unknown events skipped, embedded # boost: mangles the name)
Write-up · audit-hooks-remove-n-cannot-find-hooks-boost-itself-added-unknown.md
install --dry-run promises agents the real install never writes (antigravity-cli copy, antigravity materialize) and omits the MCP plan
Write-up · audit-install-dry-run-promises-agents-the-real-install-never-write.md
Rules/workflows reported as 'skills': uninstall claims a store dir that never existed
Write-up · audit-rules-workflows-reported-as-skills-uninstall-claims-a-store.md
install --local writes the repo's .mcp.json silently — the 'recorded N servers' report never runs
Write-up · audit-install-local-writes-empties-the-repo-s-mcp-json-silently-of.md
mcp register's boost-first consent names one file but writes every agent
Write-up · audit-mcp-register-s-boost-first-consent-names-only-the-registered.md
catalog.resolve_one's duplicate-name hint advertises --path to commands that reject it
Write-up · audit-catalog-resolve-one-s-duplicate-name-hint-advertises-path-to.md
adapt/run/stats/edit/tag/export reject the tap:name qualifier that info/install accept and adapt's own hint recommends
Write-up · audit-adapt-run-stats-edit-tag-export-reject-the-tap-name-qualifie.md
tap --dry-run is silently ignored outside --catalog: SPEC and --defaults clone for real
Write-up · audit-tap-dry-run-is-silently-ignored-outside-catalog-spec-and-def.md
out.warn defaults to stdout: infer/absorb corrupt > SKILL.md; search/explain/context warnings pollute piped stdout
Write-up · audit-out-warn-defaults-to-stdout-infer-absorb-corrupt-skill-md-se.md
AI degrade note blames PATH/API keys regardless of cause; several commands fall back with no note at all
Write-up · audit-ai-degrade-note-blames-path-api-keys-regardless-of-cause-sev.md
Declined confirms never name -y/BOOST_ASSUME_YES; snapshot, clean, infer/distill/absorb and sync reject --yes
Write-up · audit-declined-confirms-never-name-y-boost-assume-yes-snapshot-cle.md
Dry-runs disagree with the real run: compact, heal and onboard previews mispredict
Write-up · audit-dry-runs-disagree-with-the-real-run-compact-counts-bytes-it.md
Name slugging is inconsistent: distill -o accepts what import rejects; create/profile slug silently
Write-up · audit-skill-profile-name-slugging-is-inconsistent-distill-o-accept.md
git ops never set GIT_TERMINAL_PROMPT=0, so a 404/private repo prompts for credentials
Write-up · audit-git-operations-never-set-git-terminal-prompt-0-so-a-404-priv.md
cli.py COMMANDS summaries and parser help contradict behavior across ~11 commands
Write-up · audit-cli-py-commands-summaries-and-parser-help-contradict-behavio.md
info/stats/explain render a smaller shape for rules and workflows than for skills
Write-up · audit-info-stats-explain-render-a-different-smaller-shape-for-rule.md
taps/outdated/decay/policy/snapshot --json emit display strings as machine fields
Write-up · audit-taps-outdated-decay-policy-snapshot-json-emit-display-string.md
--json accepted but ignored: cohort/config/policy set, focus, profile, replay rollback, who empty state
Write-up · audit-json-accepted-but-ignored-on-many-branches-cohort-config-pol.md
Missing --json on doctor, test, health, changelog, trending, log, hooks list and friends; bundle install lacks --dry-run
Write-up · audit-missing-json-on-doctor-test-health-changelog-trending-log-ho.md
Name-miss errors: unknown tap qualifier never named, no close-match hint, tap tokens pollute suggestions
Write-up · audit-name-miss-errors-unknown-tap-qualifier-unnamed-no-close-matc.md
Six count flags (tap/chat/absorb/lint/changelog/hooks) accept 0 and negatives
Write-up · audit-six-count-flags-tap-chat-absorb-lint-changelog-hooks-skip-ut.md
Project scope seams: uninstall/verify/list/info/reinstall disagree with what install --local wrote
The project-scope-across-every-command item shipped, and the 2026-08 CLI audit found its seams: the
writers and readers resolve "the project" differently. install --local uses
scopes.resolve_base, which falls back to the cwd
(scopes.py:83-104), while uninstall's project fallback
(store.py:1249) and verify/doctor/list all go
through scopes.project_root, which requires a VCS marker (scopes.py:45).
So from a plain directory, install anthropics/skills:pdf --local writes
.boost/skill-lock.json and .claude/skills/pdf and reports success —
then verify pdf answers “Error: not installed: pdf”, plain
uninstall answers “brainstorming is not installed”, and
doctor/list show no project row. After mkdir .git the same
commands find everything. All six findings reproduced.
Four more seams, each confirmed in source. The already-installed error hints
“boost reinstall brainstorming --local to force” — a flag
reinstall does not have; following the hint exits 2 with
“unrecognized arguments: --local” (store.py:615-617).
verify <project-only name> ignores the filter and grades every user-scope item
— cmd_verify already passes [] for project-only names
(safety.py:355-356) but _iter_installed_all treats [] as
“everything” (_common.py:66, if names:), so the run can
fail on a rule the user never named. list --local --kind rule prints
“○ no rules installed” although project scope holds skills only. And
info on a project-scoped skill shows the not-installed card — no version,
installed date, commit or agents rows — though the project lock records them all and
--json returns them under project.
The verified fix, one follow-up card: make _iter_installed_all treat []
as nothing (_common.py:66) · change the hint to
boost install NAME --local --force (matching README ~328) or add
--local to reinstall · unify the uninstall fallback on
resolve_base or hint --local (store.py:1249) · have
install --local warn outside a VCS root, or teach project_root to accept
.boost/skill-lock.json as a marker · refuse
list --local --kind rule|workflow the way the existing --tag guard does
(info.py:274-281) · render the plock identity rows in cmd_info.
Docs: README ~304–328 (uninstall/reinstall routes), a follow-up note on
docs/roadmap/items/project-scope-across-every-command.md, and regenerate
docs/commands.html if reinstall gains --local. Found by the 2026-08 CLI
audit (cluster project-scope-readers); repro in the audit log.
export -o, cohort create and profile save silently overwrite existing outputs and still say created/saved
Write-up · audit-export-o-cohort-create-and-profile-save-silently-overwrite-e.md
Stray positionals and inapplicable flags silently ignored across import, config, policy, trust, log, schedule, hooks, snapshot
Write-up · audit-stray-positionals-and-inapplicable-flags-silently-ignored-ac.md
out.table clips data columns to an assumed 80 columns when stdout is a pipe; narrow TTYs clip IDs/hashes
Write-up · audit-out-table-clips-data-columns-to-an-assumed-80-columns-when-s.md
output._wrap_tokens splits punctuation off backtick spans, inserting a stray space
Write-up · audit-output-wrap-tokens-splits-punctuation-off-backtick-spans-ins.md
Sweep: positionals/--json lack help strings and no command help shows examples (~30 cmds)
Across roughly thirty commands the help screens end at the options table with undocumented arguments.
Observed verbatim: help cohort prints {list,create,delete,status,apply} with no help
string for the action; test --help shows positional arguments: then NAME
with nothing after it; conflict --help and attest --help each list --json
with an empty help line; edit/explain/home give name no text
(so docs/commands.html renders <code>name</code><span></span>).
No help screen in the audit shows an Examples block. The gaps hide real contracts: run never
mentions its SDK/key prerequisites (only the runtime error does), discover --help never says a
query hits GitHub live while bare/--local read the cache, conflict's exit-1-on-findings
is undocumented, cohort status is an unadvertised alias of list, and policy's
11 valid keys appear only in the error hint after a wrong set.
Verification confirmed this is omission, not style: the mechanism works and is used exactly once —
the only epilog= in all of boost_cli is cohort's membership-hash paragraph
(team.py:74), and team.py:77 adds the action positional with no help=.
Nothing in CLAUDE.md declares terse help deliberate, and the content gap is unchanged at
COLUMNS=60 under a TTY, so it is not a rendering artifact.
Fix as one sweep PR: add help= to every bare positional (action choices, NAME,
--json) and an Examples epilog per parser — cliparse.parser forwards
**kwargs to argparse, so no plumbing is needed. Extend
scripts/build_command_reference.py to render parser.epilog (lines 119/137 render
description and per-arg help but currently drop the epilog) and fail --check on empty help
strings, then regenerate docs/commands.html. Document the defaults (cohort/profile default action
= list) and the cohort status alias while there; the list summary in
cli.py COMMANDS should also say "skills, rules and workflows" to match its own description.
Found by the 2026-08 CLI audit (cluster help-examples-sweep); repro in the audit log.
Singular/plural misses (“1 skills”, “1 issue need”, “1 skill pass”) across six commands
Write-up · audit-singular-plural-misses-1-skills-1-issue-need-1-skill-pass-ac.md
log/simulate/changelog headings echo the raw qualified argument, printing the tap twice
Write-up · audit-log-simulate-changelog-headings-echo-the-raw-qualified-argum.md
Stale prose after shipped changes: catalog --export, live discover, 464 count, Apache-2.0
Write-up · audit-stale-prose-after-shipped-changes-catalog-export-live-discov.md
boost ROOT: CLI audit findings (2026-08)
boost absorb: CLI audit findings (2026-08)
boost adapt: CLI audit findings (2026-08)
A subagent named after a declared tool renders modules that compile but cannot run. With a
subagent grep and tool Grep: crewai emits @tool("grep") def
grep then grep = Agent(...), so reviewer_1 = Agent(tools=[read,
grep]) hands the Agent where a tool belongs (stub run: TypeError); langgraph assigns
grep = create_react_agent(...) inside build_mycrew, making it local, so the
earlier tools=[read, grep] raises UnboundLocalError. _unique_idents
(core/adapters.py:188-202) dedups only among agent specs and _unique_tools
(:205-212) allocates stub names independently. Fix: pass the tool ident set into
_unique_idents as pre-reserved names (or prefix stubs tool_<name>) in
render_crew/render_graph, plus a golden test that executes a colliding
render against stubs.
docs/commands.html brackets required options as optional. Line 369 shows
boost adapt [--to FRAMEWORK] … while adapt --help prints an
unbracketed --to FRAMEWORK and omitting it exits 2 — and the verify pass found it
is broader than adapt: evolve's required --feedback and catalog's required
mutually-exclusive group render all-optional too. Pure generator bug:
scripts/build_command_reference.py:122-126 brackets every option unconditionally. Emit
required options unbracketed (a required group as (--a | --b)), prefer the short flag
like argparse, then make generate — the --check gate holds it after
that.
Colon-form model ids get double-prefixed for the LiteLLM targets.
--model anthropic:claude-x — the form langgraph accepts and emits —
renders llm=LLM(model="anthropic/anthropic:claude-x") for crewai and the same for
agents-sdk; multi-agent crews inherit it via adapters.py:376.
_litellm_model's docstring says a provider-qualified value passes through, but the code
checks only /. Fix: replace the first : with / before deciding
to prefix (mirror of _langchain_model); document accepted syntaxes in
docs/adapters.html's --model paragraph (~line 344, slash form only today),
and regenerate docs/commands.html only if the argparse help changes.
adapt -o and run --print -o write generated source mode
0600 — and re-rendering over an existing 0644 file silently downgrades it, unlike a shell
redirect (-rw------- vs -rw-r--r-- under umask 022).
util.atomic_write_text (core/util.py:91-116) inherits mkstemp's 0600, right
for the lock/config it was written for, wrong for source the user asked boost to write. Fix: add an
optional mode parameter (fchmod the temp fd before os.replace), keep 0600
the default, and have cmd_adapt (pkg.py:1753-1761) and cmd_run
(run.py:62) pass the umask default. Still open — an implementation of exactly this
shape (path-based os.chmod, not os.fchmod, learned the hard way: the latter
raises on Windows) shipped and then was reverted from PR #728 after three rounds of Windows-only
windows-latest CI failures in the "unit + functional (90% coverage gate)" step that
neither pytest-cov nor a temporary diagnostic artifact-upload commit could surface a cause for —
the session driving that PR could not read Windows job logs (capped and consumed by
harden-runner's own diagnostic noise) or download the diagnostic artifact
(productionresultssa*.blob.core.windows.net blocked by that session's network egress
policy) to see the actual failure. The other three findings landed clean on every platform. Whoever
picks this back up needs either a session with working Windows CI log access, or to reproduce
locally on a real Windows box.
Found by the 2026-08 CLI audit (clusters adapt-ident-collision,
docs-required-flag-synopsis, adapt-model-id-syntax,
generated-file-mode); repro in the audit log.
boost bmad: CLI audit findings (2026-08)
boost browse: CLI audit findings (2026-08)
boost bundle: CLI audit findings (2026-08)
boost catalog: CLI audit findings (2026-08)
boost changelog: CLI audit findings (2026-08)
boost chat: CLI audit findings (2026-08)
boost cohort: CLI audit findings (2026-08)
boost completions: CLI audit findings (2026-08)
boost config: CLI audit findings (2026-08)
boost context: CLI audit findings (2026-08)
boost deps: CLI audit findings (2026-08)
boost discover: CLI audit findings (2026-08)
boost edit: CLI audit findings (2026-08)
boost evolve: CLI audit findings (2026-08)
evolve accepts empty --feedback and has no stdin/file form.
evolve brainstorming --feedback "" exits 0 and diffs in
+## Feedback (2026-08-31) followed by nothing, plus a bump to
version: 0.0.1 — with --apply that empty section lands in the store
and the lock. --feedback - becomes the literal bullet +- -. and
--feedback @/dev/null becomes +- @/dev/null.. In cmd_evolve
(intelligence.py:663-713) raise BoostError when
args.feedback.strip() is empty before calling the AI or heuristic; treat
- as read-from-stdin and @path as read-from-file, documented in
--help (then regenerate docs/commands.html).
evolve --apply leaves the revision unpinned, so a later
boost update can silently overwrite it. After --apply the lock holds
the evolved sha and pinned: false, and evolve prints only
“✓ evolved brainstorming”; pkg.py's update loop
(pkg.py:1035-1039) skips only pinned/quarantined/local entries, so once the tap moves,
store.install(entry, force=True) (pkg.py:1063-1067) replaces the revision
with no warning. On --apply set entry["pinned"] = True (or a
local_revision flag the update loop honours) — at minimum print
“boost pin <name> to keep this across boost update” after the success line.
After evolve, info claims an update to a lower version while
outdated says up to date. With the lock at 0.0.1 and the tap at 0.0.0,
info prints “[update available] … version 0.0.1 / latest 0.0.0 (update
available)” — info.py:477-479 and 498-501 test
latest != inst_v (string inequality) where cmd_outdated
(taps.py:285, 340) correctly uses util.semver_gt for the same
decision. Replace both checks with util.semver_gt(latest, inst_v) and label the
locally-ahead case (e.g. local revision, tap has an older 0.0.0).
Found by the 2026-08 CLI audit (clusters evolve-feedback-input, evolve-revision-unpinned,
naive-version-comparison); repro in the audit log.
boost explain: CLI audit findings (2026-08)
boost export: CLI audit findings (2026-08)
boost import: CLI audit findings (2026-08)
boost index: CLI audit findings (2026-08)
boost install: CLI audit findings (2026-08)
Rule/workflow lock entries are name-keyed across scopes (med). With the
benchmarking rule at user scope, install benchmarking --local in a project fails
“Error: benchmarking is already installed / hint: boost reinstall benchmarking
to force” — the project has no copy, and the hint would reinstall the user one. The
reverse direction blocks too, and skills coexist fine (separate project lock). Worse, the
--force escape overwrites the user-scope lock entry with the project one, orphaning the
user materializations so uninstall can no longer clean them. _install_rule
(store.py:836-839) and _install_workflow (store.py:1074) gate
on a name-only lookup with no scope/base comparison. Fix: key entries by scope (or compare
existing scope/base before raising), word the error “already installed at user
scope”, and refuse a cross-scope --force overwrite without cleanup. Docs:
README's install-scope section (~301-328) and
docs/roadmap/items/install-scope-user-or-project.md.
(Cluster cross-scope-name-block.)
--path says “under path” but matches suffix-only
(low). --path plugins/tdd/skills is refused while the error's own hint lists
plugins/tdd/skills/test-driven-development — a path that is under it.
Suffix matching is the shipped design (install-path-disambiguation, PR 483); the wording
is the defect. Reword the raise in catalog.py:~502 to “no copy of X whose path ends
with Y” and hint “pass a trailing segment of one of: …”.
(Cluster install-path-prefix-match.)
The MCP offer never shows the runnable command (low). The server row prints
only demo-echo npx though the sidecar declares npx -y
@example/demo-echo-mcp plus env, and on decline the hint is a literal elided
claude mcp add …; the full argv only prints when the host CLI is
missing. _offer_mcp renders how from spec['command'] alone
(pkg.py:161-164) and mcpdecl.register_argv already exists
(pkg.py:201) — render command+args, print the joined argv on decline, and indent
the confirm prompt to match its neighbours. (Cluster mcp-offer-command-detail.)
The typosquat warning prints three times (low).
install NeoLabHQ/context-engineering-kit:test-driven-development --dry-run prints the
identical “closely resembles test-driven-development
(sickn33/antigravity-awesome-skills)” warning 3×, one per mirror copy in the
look-alike tap. De-duplicate find_confusions on (name.lower(), tap)
(typosquat.py:79-87) so the [:3] slice in _warn_confusions
covers three distinct look-alikes. Found by the 2026-08 CLI audit (cluster
typosquat-warning-dupes); repro in the audit log.
Status (2026-09). Three of the four clusters shipped as described above:
install-path-prefix-match (catalog.py wording), mcp-offer-command-detail
(mcpdecl.command_line renders the full command+args, the decline path prints the real
argv), and typosquat-warning-dupes (find_confusions dedupes on
(name.lower(), tap)). cross-scope-name-block got the narrower of the
fix's own two options: store._check_scope_conflict now refuses a rule/workflow install
whose name collides with an existing lock entry recorded under a different scope/base —
naming the real location ("already installed at user scope") and refusing even under
--force, which closes the silent-corruption half of the bug (a forced cross-scope
install used to overwrite the other scope's lock entry, orphaning its materializations). What is
still missing is the other half: rules and workflows still cannot coexist across scopes the
way skills do, because they share one lock keyed by bare name with no per-location table — skills
got a separate projectlock.py when project scope was added, rules/workflows never did.
Giving them the same treatment (a rules/workflows section in
projectlock.py, wiring _install_rule/_install_workflow and
their uninstall/sync counterparts through it for project scope) is real coexistence but is its own,
larger change, and belongs in its own card rather than folded into a bugfix PR.
boost log: CLI audit findings (2026-08)
boost mcp: CLI audit findings (2026-08)
boost onboard: CLI audit findings (2026-08)
boost outdated: CLI audit findings (2026-08)
boost preview: CLI audit findings (2026-08)
boost profile use: CLI audit findings (2026-08)
boost protocol: CLI audit findings (2026-08)
boost pulse: CLI audit findings (2026-08)
boost quickstart: CLI audit findings (2026-08)
Without the [rag] extra, quickstart taps unpinned at HEAD — and the rerun it promises cannot fix it. cmd_quickstart only fetches the manifest (the source of pins) when want_vectors is true (boost_cli/commands/quickstart.py:145-152), so on a machine without a dense backend the six new taps land with pin: null while the output ends “…install the extra…, then boost quickstart again”. The second run prints <tap> already tapped for all seven (registry.add_many skips existing taps, never re-pins), and once the extra is present shards.sync refuses every mismatched commit: refused (tap is at X, shard is for Y). That contradicts the module's own docstring — “Pinning is the whole point”. Fix: fetch the manifest and pin regardless of dense.have_backend() (pinning is a network-and-config operation, not an embedding one), and on rerun retarget already-tapped registries via shards.ingest instead of skipping them. Update README.md (quickstart section, ~line 145) and docs/semantic-search.md (~line 63).
The [rag] install hint has three different wordings. quickstart says pipx inject boost-skill-cli "boost-skill-cli[rag]" (hard-coded at quickstart.py:175-177 and 202-204); reindex's embed.fallback_note (boost_cli/core/embed.py:170-179) says unquoted pip install boost-skill-cli[rag], which fails in zsh (no matches found); doctor and search say quoted pip install 'boost-skill-cli[rag]' via dense.fix_hint(). CLAUDE.md's rule is that doctor and search read one table so they cannot contradict — these two surfaces bypass it. Fix: have embed.fallback_note() and both quickstart paths call dense.fix_hint(); if pipx wording is wanted, put install-method detection inside fix_hint so every caller inherits it. docs/semantic-search.md is already quoted — keep it as the reference.
Found by the 2026-08 CLI audit (clusters quickstart-pinning, rag-hint-drift); repro in the audit log.
boost recommend: CLI audit findings (2026-08)
boost replay: CLI audit findings (2026-08)
boost run: CLI audit findings (2026-08)
boost schedule: CLI audit findings (2026-08)
boost search: CLI audit findings (2026-08)
boost simulate: CLI audit findings (2026-08)
boost sync: CLI audit findings (2026-08)
boost tag: CLI audit findings (2026-08)
boost tag swallows unknown flags and misreads them as operands. tag brainstorming --verbose prints the current tags and exits 0 — the flag is consumed as a removal of the tag -verbose; tag --verbose gives "Error: --verbose is not installed" (the flag becomes a skill name); verification found a third hole: tag brainstorming --list silently discards the skill-name operand and lists all tags. Cause: cmd_tag's manual split (boost_cli/commands/info.py:988-993) whitelists only --list/--json/-h/--help; every other --x token falls through as an operand. Every sibling command rejects unknown options with "unrecognized arguments" exit 2.
And the mutation path has no before/after check. tag brainstorming -nosuch removes a tag that was never present — silent, exit 0; tag brainstorming +x -x prints ✓ and writes the lock plus a journal event for a net no-op (changed is set per-token at info.py:1027-1041, never compared to the before set); "+with space" is accepted as #with space; +Design and #design coexist. The shipped roadmap item robust-tag-argument-parsing (PR 94) built this manual split — these are residual holes in it, not a duplicate.
Fix in cmd_tag: hand any token starting with -- (or -letter that is not a tag operand) to argparse so it errors; compute changed = sorted(tags) != sorted(before); print a one-line notice for removing an absent tag; reject whitespace in tags; document or fold case; error when a name is given with --list. Regenerate docs/commands.html if the help text gains the tag grammar.
Found by the 2026-08 CLI audit (cluster tag-arg-parsing); repro in the audit log.
Partly landed — PR 735. The correctness half shipped: any unrecognized
-- token now reaches argparse (unrecognized arguments, exit 2),
--list with a skill name is a named error, whitespace in a tag is rejected, and
changed is a before/after set comparison in the new
lockfile.apply_tag_mods, so +x -x no longer writes the lock and a
journal event for a net no-op. Still open, and why this card stays
inflight: the one-line notice when -tag removes a tag that was
never present (the remove branch is still a silent no-op), and documenting or folding tag
case (+Design and #design still coexist). Both are UX asks rather
than correctness bugs, which is why the PR left them.
boost tap: CLI audit findings (2026-08)
boost taps: CLI audit findings (2026-08)
boost unpin: CLI audit findings (2026-08)
boost untap: CLI audit findings (2026-08)
boost update: CLI audit findings (2026-08)
boost who: CLI audit findings (2026-08)
August 2026 full-CLI audit: every one of the 81 boost commands exercised and verified
Per-item categories in search/browse/info — not just a ★ curated bool
From a user request: “proper categories for skills (can't have all of them listed as just
curated)”. They are right about the item level: the only taxonomy a catalog entry carries is
a boolean. A boost search row shows name, kind, tap, description and at most a
★; boost recommend's no-match fallback is literally headed
“curated picks”; boost info prints no category at all. Across a
real install of tens of thousands of items, “starred or not” is the entire
classification a user can see or filter by.
What the code confirms. catalog._make_entry stamps "curated": curated
onto every entry (boost_cli/core/catalog.py:119, signature at 105–106) — and that
bool is per-tap, from Tap.curated (core/registry.py:23), set by
tap --defaults or by anyone passing --curated
(commands/taps.py:126) — a trust star, not a classification. Category-like data does
exist, but only per tap: data/registries.json rows carry one (487 registries, 21
values; general alone covers 127), and exactly two surfaces read it —
browse's row badge via _tap_categories
(commands/discovery.py:936–941, whose own docstring says “catalog
entries themselves carry no category, only their tap does”; badge appended last in
_row_badges, discovery.py:961–963, so narrow panes drop it first, and taps
outside the bundled 487 get none) — and boost serve's web facets
(core/serve.py:65). cmd_search renders only the star
(discovery.py:179) and takes no filter flag; info shows frontmatter tags when present
(commands/info.py, the meta.get("tags") kv) but no category, and its
--json has no such field. An item's own frontmatter category/tags
ride along invisibly in entry["meta"] and the substring search_blob
(catalog.py:131, 621–627), so they can match a query yet can never be displayed or filtered.
Proposed fix. Stamp a first-class category on each entry at scan time in
_make_entry (catalog.py:105–132): the item's frontmatter category
(or first tag) when declared, else inherited from its tap's registry category — and bump
catalog.CACHE_FORMAT so hundreds of existing tap caches backfill without a re-tap, per
the versioned-cache rule. Then surface it where a category would live: a badge in
search rows and a --category filter on
search/browse/recommend, a kv row plus JSON field in
info, and browse's existing badge switched from tap-level to the entry
field (which also gives un-bundled taps' items a label for the first time). ★ keeps meaning
curation/trust only. Consumers must degrade cleanly when category is absent (old
caches, synthesised entries), same as the content digest rule.
Docs: regenerate docs/commands.html for the new flags; no other doc names categories.
Found by the 2026-08 CLI audit (cluster catalog-categories-beyond-curated, filed from
the user's request); repro in the audit log. Verified against source 2026-08-31.
Status (2026-09-01). Landed: the category stamp at scan time
(catalog._entry_category, own frontmatter category → first
tags entry → tap's registry category), CACHE_FORMAT bumped to 2 so
existing caches backfill on next scan, a --category filter on
search/browse/recommend
(catalog.matches_category/filter_by_category), info's kv row
and --json field, and browse's row badge switched from the tap-level
lookup to the entry's own field (falling back to the tap lookup for a cache not yet rescanned).
Not done: the badge in plain boost search rows. That row's column widths
(out.search_layout/format_search_row) are a tuned, heavily-pinned budget
system (drop order, per-cap name shrinking, a reserved curated tail) — working it out safely needs
its own pass rather than a bolt-on inside this PR. Left inflight rather than
shipped for that reason; the next claim on this item is scoped to exactly that piece.
The last command that blamed boost for the user's typo
One incidental keyword decides the boost bmad track
The BMAD router reads each prompt alone, so a pasted log, an “ok update both and rerun” and a yes/no question all get a banner
boost bmad gives every track the build contract, so a review is told to finish a change
bmad route never checks autopilot state, so any hook off missed keeps routing
The autopilot routes docs at a skill BMAD 6.12 no longer installs, and no test would notice
On Gemini, the bmad on briefing reaches the user and never the model
Persona descriptions say Use PROACTIVELY, so the router's silence governs only the banner
out.err() judges colour by stdout while writing to stderr
Every Error: line asks stdout whether to colour a line it writes to stderr.
out.err (boost_cli/core/output.py:267 and :271) paints with
c(), which takes no stream and calls use_color(), so the answer is
sys.stdout.isatty(). Every BoostError reaches the user through it
(cli.py:398), as do the unknown-command and unknown-option errors.
Measured through a real pty (Python pty.fork, BOOST_COLOR /
NO_COLOR / CLICOLOR_FORCE unset), boost bundle install nosuch on
loop/bundle-audit (err() is unchanged from origin/main). With stdout on
the terminal and 2>log, the log holds
\x1b[31m\x1b[1mError: \x1b[0mno Boostfile at …/nosuch\n\x1b[2m hint: create one with `boost bundle dump Boostfile`\x1b[0m\n
— five escape sequences written into a file. With stdout to a file and stderr on the
terminal (>out), the terminal gets Error: no Boostfile at …/nosuch with
no colour at all.
Each case gets what the other one should. out.warn(stream=…) had the same bug; the
bundle audit fixed it by passing the stream through to role(), and left
err out of scope. A grep for c( on a file=sys.stderr line
in boost_cli finds only these two.
Fix: give c() a stream= keyword forwarded to
use_color, and pass sys.stderr from both calls in err. Test it
the way tests/unit/test_output.py::TestWarnColourFollowsItsStream tests
warn: with stdout a TTY and stderr a plain buffer, no \x1b[ in stderr;
with the two swapped, the Error: prefix is coloured.
Docs-site & content quality
// planned · free tooling for the Pages site, README & proseMake the roadmaps discoverable
Lighthouse CI on the Pages site
Accessibility audit — pa11y-ci / axe-core
HTML validation — html-validate
Broken-link & anchor checking — lychee
The OpenSSF badge playbook
Prose & terminology linting — vale
Markdown consistency — markdownlint-cli2
Theme-asset linting — stylelint + eslint
Post-deploy smoke — headless load check
Command reference documentation site
Surface every docs/*.html page from the main page
The engine had no architecture diagram — and the one written rule was documented backwards
Fix overflowing node text in the RAG diagram (mcp-hub.html)
Docsite audit — stale counts, dev-noise footers, and a nav that breaks on mobile
A GIF carousel touring one flagship command per group
demo.yml has failed every run since it landed — vhs-action cannot install ffmpeg
No install doc ever said how to upgrade, so users guessed install --upgrade — which no-ops
Nothing tells a user semantic search is off — not the README, not search, not /mcp
roadmap.html grew 36% in one session and nothing bounds it
The Lighthouse budget passes on noise, not on margin
an explainer page for the LangChain / LangGraph / LangSmith integration
The performance gate was measuring a page nobody is served
Expanded card bodies overflow the roadmap board sideways
The performance gate flips on byte-identical input
Compatibility & install integrity
// planned · free tooling to prove boost installs & runs everywhere it claimsWindows in the CI matrix
Clean-env install smoke — pip & pipx
Lowest-version resolution — uv --resolution lowest-direct
Write-up · lowest-version-resolution-uv-resolution-lowest-d.md
Pre-release Python canary — 3.14t free-threaded
Startup & import-time budget — -X importtime
Package-metadata validation — twine check + friends
Write-up · package-metadata-validation-twine-check-friends.md
Hash-pinned, reproducible toolchain — requirements/*.txt
What Gemini actually receives from boost, audited
One command, every env — nox
boost hooks learns a second host — and finds two bugs upstream
Harden boost mcp launch against macOS Obj-C fork aborts
Self-harden every boost process against the macOS fork-safety abort
self-update is non-functional for pip/pipx installs
self-update said "already up to date" without asking PyPI
sync reported success for a link it had just refused
Two crashes that should have been messages
Order the server name before -e flags in `boost mcp register`
Gemini CLI as a first-class agent target (skills, rules, workflows, MCP)
production-ready LangChain / LangGraph / LangSmith integration
ship the LangChain integration inside the wheel, behind a [langchain] extra
sanitize agent frontmatter for Gemini instead of copying it verbatim
BoostRetriever advertised a source that does not open, and k=0 returned nothing forever
garrytan/gstack — tap it first, then learn to coexist with it
Skill-content trust & safety
// planned · boost's core threat model — the third-party skills it installs run inside an agentPrompt-injection scanning of skill Markdown
Integrity verification — boost verify
The MCP boost_install tool skipped the injection scan the CLI runs
Crash reports carried API keys in cleartext
Tap signing & provenance — Sigstore / minisign
Runtime hallucination guardrail for boost explain
Typosquat & name-confusion detection
Update-diff before apply
Project scope — refuse to write through an escaping symlink
Secret & PII scanning of installed skills
Lockfile enforcement & commit pinning
Capability manifest & least-privilege policy
Write-up · capability-manifest-and-least-privilege-policy.md
install_from_path bypasses pin & policy checks
Write-up · install-from-path-bypasses-policy-and-pin-checks.md
Path traversal via unsanitized rule/workflow name
A CodeQL job rename silently blocked every merge
boost audit --skills — a trust/staleness report for installed skills
sbom.yml has never run — it waits for an event GITHUB_TOKEN cannot emit
The main ruleset is inert — its ref pattern is refs/heads/"main", quotes included
The release trigger was reachable from a fork — branches: filters head_branch, not the event
The code_scanning ruleset rule can go back on — but only scoped to CodeQL
The SBOM can declare a different version than the release it is attached to
rules and workflows install, then cannot be governed
boost serve echoed the request path back into its 404 body
denied_capabilities policy never applied to rule/workflow installs
trust verify labels a manifest tampered after signing by a TRUSTED key 'untrusted'; sweep exits 0
Write-up · audit-trust-verify-labels-a-manifest-tampered-after-signing-by-a-t.md