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
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
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 …
Write-up · eval-corpus-counted-by-the-method-the-repo-disavows.md
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%
Write-up · eval-floor-calibration-stale-after-pin-refresh.md
The exemplar mechanism was applied to the ungated set only: golden.jsonl is 0/91 pinned, and 10 of its 44 hit@1 credits are on names the metric cannot adjudicate
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
Research: how boost install could tell that a skill needs something else installed first
A locally built vector store displaced by a new API key is told to re-embed through the paid provider, where unsetting the key is free
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
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.
Partly landed, and un-claimed — 2026-10-06. The interning half shipped:
terms(id, term, df) with postings(term_id, doc, tf) is on
main (rag.py _write_postings), and
stem_expansions reads the precomputed df column instead of
GROUP BY-ing the whole postings table. It arrived as #691
(64ac398d), not as this card's pr: 688 — that PR's head moved after
the train was cut, so GitHub never labelled it merged even though the diff against
main is empty.
What did NOT land is this card's actual deliverable: the measured comparison — raw vs
interned vs delta+varint, each × none/gzip/zstd, with import-side decode time beside each.
Without that table publish-the-keyword-index still has
no size answer, which is why it is held. Claim dropped because branch
loop/shrink-postings-index no longer exists; take it by setting
owner again.
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
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
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 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.
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
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
Write-up · quickstart-shard-download-invisible-in-preview-and-run.md
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
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
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.
Write-up · identifier-columns-shrink-to-a-name-no-command-accepts.md
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
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
Write-up · keyed-no-store-hint-disagrees-with-shard-remedy.md
A rule or workflow row for a disabled agent makes boost sync claim the same repair on every run, and doctor never goes healthy
Agent-dir shapes boost still trips on: a parent with no search bit, a missing dir under a read-only parent, a dir at a rule's file path, a blocked agent dropped from the lock's scope
Found by the third review of read-only-boost-home-with-no-cache-dir. None is a regression.
Each was measured behaving the same on c7dca95c.
A parent with no search bit. With an agent dir's parent at mode 0o600,
store.install copies the skill into the store. It then crashes at exit 70 in
linked_agents, where (adir / name).is_symlink() raises
PermissionError outside link_agents' guard. The store dir is left
unrecorded. Treating an OSError there as "not linked" would close it.
A missing skills dir under a read-only parent. The install names a chmod
target that does not exist, and doctor and heal disagree about it.
link_agents' PermissionError branch should record the block from
paths.refuses_writes(adir) and word it through link_refusal. doctor's
agent-dir check should use the same function, so that it agrees with heal.
A blocked agent drops out of the scope. After an install skips an agent as blocked, the lock's
agents list leaves it out. preserved_agent_scope replays that list, so a
later install --force never retries the agent and never says so. Only
boost sync reports and repairs it.
Callers that drop the result. boost import (install_from_path),
quarantine --release and the unsideline paths (focus, profile, context, team) drop
res.blocked without a word, as they already drop res.unwritable.
pkg._warn_unwritable(res) is the existing reporter.
Also found by the reconcile review of #933 with #931, and also measured on both parents: With any agent dotdir at mode 600, on Python 3.12 and 3.13 (3.14's Path.exists answers False where they raise), doctor, heal and sync
exit 70 (PermissionError on ~/.cursor/skills). A rule or workflow install in that
shape is fixed on #933's branch: _refused_target names the dir through
paths.refuses_writes, which asks lexists. So is uninstall of a rule or workflow there, through store.refusing_dir, which now treats a path it may not look at as not there. The skill install and these three are not fixed. And the remedy is wrong for this shape: chmod u+w leaves a 600 dir at 600, because the missing bit is search, so the wording should say chmod u+wx when X_OK is what fails.
A directory at a rule's target file path (~/.cursor/rules/house.mdc/) raises
IsADirectoryError. That is not one of the refusal shapes, so install exits 70 after writing
the other agents' copies, with no lock entry.heal --dry-run previews "would re-materialize" a rule whose target dir is still locked or
blocked. The real run then re-materializes nothing, correctly. The exit codes agree (1 and 1), but the
wording does not.
With ~/.agents/skills read-only, every install exits 1, naming it, while
doctor says healthy. doctor could ask paths.refuses_writes(paths.store_dir()).
Six install paths still printed success over an agent dir that refused the link
A reinstall into a locked skills dir says "not linked" over a link that is already there and correct
Write-up · a-correct-link-in-a-locked-dir-reads-as-refused.md
A refusal and its remedy print past the pane, on the one line a user reads before acting
The import-budget gate reports OK when the command it measures never ran
Write-up · import-budget-gate-passes-when-boost-cannot-import.md
The release guard fails open when it cannot read the commit's tags
Write-up · release-guard-fails-open-when-it-cannot-read-tags.md
One failed shard-build job silently drops its whole chunk from the published manifest
Write-up · one-failed-shard-job-drops-registries-from-the-manifest.md
The CI job summary reports the coverage gate as 80% when it is 90%
One valid-JSON message that is not an object kills the whole MCP session
Write-up · mcp-server-dies-on-a-valid-json-message-that-is-not-an-object.md
boost_doctor certifies a machine healthy that boost_search calls untapped
Write-up · mcp-doctor-says-healthy-where-mcp-search-says-nothing-is-tapped.md
MANIFEST.in prunes only the root copy, so dev-local files ship in the sdist
A search that finds nothing echoes the whole query back into the agent's context
A deeply nested JSON line kills the MCP session, because the parse guard names one exception
The model back-off dated its record by the filesystem clock and read it against the process clock
boost mcp registration writes outside a sandboxed HOME
Write-up · mcp-registration-writes-outside-a-sandboxed-home.md
Codex hooks are shaped like Claude's, and the two fields that differ are unverified
Registering a skill's MCP server handed Antigravity the one argv its CLI rejects
wrap() sizes a stderr line by stdout's pane
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
The sandbox fixture sandboxes every env var except the one project scope reads
Write-up · sandbox-fixture-does-not-sandbox-the-working-directory.md
The links job fetches 467 URLs from one host at once and GitHub 503s a random handful
Capping the burst did not stop the 503s — the whole fetch was signal-free
One 30-minute cap for nine matrix cells, and only Windows is anywhere near it
Write-up · the-windows-test-cap-is-five-minutes-from-the-measurement.md
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
One unresolvable agent dir silences the whole `boost doctor` report
Found by the review of loop/codex-agent-target.
paths.expand now raises a BoostError for a ${VAR} with no fallback
and nothing in the environment, rather than resolving it to an empty or literal path — the change that
stopped a mistyped agents.<name>.dir silently installing into
./skills. agents.known_agents calls it for every configured agent, so one bad
row now aborts every command that asks who the agents are: doctor,
heal, clean, install, sync.
For most of those, refusing is the right answer. For doctor it is not: its whole job is to
surface a misconfiguration, and it exits 1 having reported nothing else — no lock check, no store check,
no duplicate-discovery check. The remedy is reachable (boost config set never calls
known_agents, verified in a sandbox), so this is a bad report rather than a lockout.
Fix direction. Catch BoostError around the agent lookups in cmd_doctor
and render it as an issue row, then continue with an empty agent set. The catch has to cover the
indirect callers too — store.duplicate_discovery, store.sync_plan and the
per-agent coverage counts all reach known_agents — so the natural shape is one resolution
step at the top of the command whose failure degrades the agent-dependent sections rather than the run.
An item materialized nowhere reads ok in verify, health and drift
Write-up · an-item-materialized-nowhere-reads-ok-in-verify-health-drift.md
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
Codex is a skills and rules target but not yet an MCP host
The free-threaded canary is red for a missing setuptools, so it warns about nothing
One 30-line class makes registry.py unsplittable, and floors the mutation gate at 95% of its timeout no matter how many shards you add
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 disagreed with what install --local wrote
Write-up · audit-project-scope-seams-uninstall-verify-list-info-reinstall-dis.md
Project scope in an unmarked tree does not walk up, so src/ becomes its own project
Write-up · project-scope-in-an-unmarked-tree-does-not-walk-up.md
A rule or workflow installed --local reads as a user one everywhere but list --local
Rules and workflows installed with --local materialize into the repo but are recorded
in the user lock, tagged scope: project and base: <repo>,
because a project lock holds skills and nothing else
(store._check_scope_conflict). boost list --local now filters them with
scopes.owned_by, and it is the only reader that does.
lockfile.installed_rules() and installed_workflows() have a dozen other
consumers — store, complete, taps,
team, safety, quality, pkg — and none of
them ask whose repo a row belongs to. So boost doctor and boost verify
count another checkout's rule as this machine's, drift and health grade
it, tab-completion offers it, and plain boost list prints it under
“installed rules” with nothing saying it belongs to a directory the user may not be
standing in. The failure is quiet in both directions: a row for a repo that has since been
deleted never goes away, and a row for the repo you are in looks identical to one for a
repo you are not.
Worth settling the design before the sweep, because the honest answer differs per command. A
scope column on plain list's rule and workflow tables is cheap and makes
the ambiguity visible (boost info already prints scope project).
Whether verify and doctor should grade a rule belonging to
another repo is a separate question — they cannot read its materializations from here, so
counting it is arguably the bug and skipping it with a note the honest fix. Split out of
audit-project-scope-seams-uninstall-verify-list-info-reinstall-dis, where the
evidence was gathered.
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)
Write-up · audit-sweep-positionals-json-lack-help-strings-and-no-command-help.md
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)
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)
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)
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)
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 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
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
boost quickstart: a rerun could move already-tapped registries to their vectors
A rerun of quickstart still leaves an already-tapped registry at the commit it was tapped at. registry.add_many skips a configured tap (already tapped), so a registry first tapped before quickstart pinned anything, or overtaken by a weekly republish, stays where it is, and shards.sync then refuses its shard (tap is at X, shard is for Y). The audit-quickstart-findings fix flags that refusal and names boost update --shards, which already moves such taps through shards.ingest (download and verify first, then move the tap). That was the smaller half of the card; its other half was to have quickstart do the move itself. Fix: on a rerun, hand the configured taps the manifest publishes to shards.ingest instead of shards.sync, so the vectors land in one command. Decide first whether quickstart may move a tap the user tapped on purpose at another commit: boost update treats a pin as a promise, and a tap pinned with boost tap --at should keep it. Pin that choice in tests/functional/test_cli_quickstart.py beside the commit_moved tests.
uninstall --local cannot remove a rule or workflow that list --local shows
Write-up · uninstall-local-cannot-remove-a-rule-or-workflow-list-local-shows.md
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
73 reads and writes left the text encoding to the locale, and the Windows jobs were the only thing that noticed
934 text writes leave the line ending to the platform, and five of them are bugs
Write-up · writers-whose-bytes-are-compared-do-not-pin-their-newline.md
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
util.rmtree's retry hook chmods a symlink's target, outside the tree it is deleting
Write-up · util-rmtree-chmods-a-symlink-target-outside-the-tree.md
boost attest: CLI audit findings (2026-08)
boost trust: CLI audit findings (2026-08)
uninstall --local deletes any in-repo directory the committed lock names
Write-up · project-uninstall-deletes-any-in-repo-directory-the-lock-names.md
The add_many concurrency probe measured a race, not the pool
Write-up · concurrency-probe-measured-a-race-not-the-pool.md
install --local writes through a redirected dotdir that uninstall --local will not remove
A repo that commits <repo>/.cursor → config/cursor — an ordinary
dotfile layout, not an attack — gets two different answers from the two halves of the same
command. _install_project_skill gates its target with
scopes.ensure_in_base, which is containment only, so boost install --local
writes config/cursor/skills/<name> and records the row it spelled,
.cursor/skills/<name>. boost uninstall --local then refuses that row,
because scopes.parent_matches_spelling walks the parent for real and finds the
redirect. Boost's own copy is left on disk and the lock entry goes anyway, so a second uninstall
answers not installed in this project.
Reproduced on the commit that introduced the guard: install wrote through the symlink, uninstall
reported the row as redirected, and the directory survived.
The uninstall side is right and is not the thing to change. The benign layout and the attack
are byte-identical on disk: .claude/skills → ../src with a legally-spelled lock row
is the same two objects in the same two places, and nothing in the filesystem carries the intent
behind them. Creating through a redirect is safe; destroying through one hands an attacker with
merge rights an rmtree aimed wherever the symlink points. So the asymmetry stays, and
uninstall now names the row, says a symlink redirects it, and prints an
rm -rf that works — because rm follows the ancestor exactly as the
install did (shipped in #1016).
What is left is the other half: install should not write somewhere uninstall cannot reach.
The options are not equivalent and the choice needs measuring rather than guessing — refuse
the install outright (safe, breaks a layout that works today); record the resolved path in
the lock so uninstall's walk agrees (keeps the layout, but the lock then carries a path the repo
never spells, and a teammate whose clone resolves differently gets a row that matches nothing); or
record both and match on either (most forgiving, widest attack surface to re-audit). Whichever
lands, boost doctor should report a project whose lock rows no longer resolve to where
the install put them, since today nothing notices until an uninstall leaves a directory behind.
Related: project-uninstall-deletes-any-in-repo-directory-the-lock-names, which added
the walk and the redirected reporting this card inherits.
The mutation shard planner is packing for a mutant set that is 14% smaller than the real one
Write-up · mutation-shard-balance-hints-are-a-release-behind.md
Eleven util.rmtree call sites guard with exists() or is_dir(), which a symlink satisfies
Write-up · is-dir-follows-a-symlink-at-eleven-rmtree-call-sites.md
The floors job builds its own venv and never put setuptools in it, so main went red on the weekly cron
test_canary_deps.py needs PyYAML, which the required tests job does not install — so it only runs inside the mutation gate
Mutation shard weights balance perfectly and predict nothing, so a shard can time out and cancel a release
A cancelled ci on main skips the release and alerts nobody, because the alert asks only about failure
Write-up · cancelled-ci-on-main-is-silent-and-skips-the-release.md
The shard model runs through the origin, so plan gets more optimistic with every shard added
The cleanest statement of it needs no weights at all. Going from eight shards to twelve,
a model that divides work by the shard count predicts the median job falls to 0.667 of
itself. It fell to 0.83 — 41.8 min over the 24 jobs of the three eight-shard runs to
34.6 over the 24 jobs of the two twelve-shard runs. Correcting for the one thing the weights
do say — #1032 re-timed the same mutants 0.884× cheaper — moves the prediction
to 0.589 and widens the gap, so the uncorrected figure is the conservative one. No value
of RUNNER_EFFICIENCY produces either, because the error is in the shape and not in the
constant: runner_minutes is weight / (RUNNER_WORKERS ×
RUNNER_EFFICIENCY), linear through the origin, and what is missing is a per-job
fixed cost.
Graded like for like, the two widths disagree by 40%. Each width has to be scored against
weights measured at that width: #1029's 66,708,651 ms total was measured on an eight-shard run,
and #1032's 58,969,820 was re-timed from CI run 37200431822 — which is
58ace415, one of the two twelve-shard runs being graded. On that footing eight
implies an efficiency of 0.832 and twelve implies 0.593. Reading both against one
frozen total hides this, and understates the gap as 24%.
C is large and five routes do not agree on how large. They differ in whether they use
medians or machine-minute sums, and in whether #1032's 0.884x re-timing is read as the work
really getting cheaper between the two widths: 20.2 and 24.2 min a job from the two
p50s, 14.4 and 19.8 from the matrix totals (347.6 machine-minutes at eight over
four runs, 404.9 at twelve over two), and 26.9 from the six-to-eight step of 2026-10-02
— 287.2 → 341.0 machine-minutes two hours apart with the width the only change. So
C is 14 to 27 min a job. A two-point solve is exactly determined and therefore not
falsifiable; fitting C needs a third width, not more runs at twelve.
And C cannot be bolted on beside the efficiency, which is the part that makes this a card
rather than a one-line patch. Subtract any C in that range from the matrix total and the
parallel work left over implies a speedup above four on a four-vCPU runner: C = 14.4 leaves 232.4
min against 1,111.8 weight-minutes, which is 4.78 effective workers, and C = 24.2 leaves
154.0, which is 7.22. An efficiency over 1.0 is a contradiction, not a tight fit, so the
weights overstate real serial work by roughly the same factor and
PLANNED_TOTAL_MS cannot serve as the fixed reference a re-fit is measured against.
A hypothesis, named as one because it has not been measured: mutmut times each mutant while four
of them run at once, so a per-mutant duration may already carry the contention
RUNNER_EFFICIENCY exists to apply.
The naive remedy is worse than the status quo, so it is worth writing down as refuted.
Adding C to the existing conversion with the committed efficiency — C +
runner_minutes(w) at C = 20, each width on its own weights as this card requires —
predicts 62.7 min at eight against an observed 41.8 and 45.2 at twelve against an
observed 34.6: over-predicting by 50% and 31%. The bias is not even, which is the
point — a fixed term added to an unchanged efficiency does not just shift the answer, it
gets the width dependence wrong in the other direction. The tail it produces at twelve is
86.2 min, 115% of the cap: the planner would refuse the width CI
runs green today. C and RUNNER_EFFICIENCY have to move together, and whatever they
do to the predicted median then has to be reconciled with
TAIL_MULTIPLIER, whose over-estimate is currently covering for the median being low.
It is not CI setup, and that is the cheap half to check. Stepping all 24 twelve-shard jobs
through the Actions job API, everything that is not the mutate shard step is a
median 0.5 min — the 1.7% the constant's docstring already measured at six, still
true. So the fixed cost is inside mutmut: a baseline test run (the tests job on
those two runs took 7.8 and 8.6 min) and per-job mutant collection are the candidates, and
neither has been measured. Measuring them would turn the 14-27 range into a mechanism.
Why it matters even though nothing is red. Twelve sits at 74% of the cap empirically and
the gate passes. But plan reported 64% on #1032's pack against that same
measured 74%, and goes on reporting around half the cap at sixteen — 48% on #1032's weights, 50% on
#1038's — where the real floor is C ×
TAIL_MULTIPLIER however many shards are added. So the one question the planner exists to
answer — "is this width safe?" — is answered optimistically for every width above the
fitted one, and the answer gets more optimistic the further you go. Until C is fitted,
test_the_committed_width_has_actually_run refuses a SHARDS nothing has
been observed at, and test_todays_pack_under_predicts_the_worst_twelve_shard_job
pins the 7.8-minute shortfall so it cannot drift unnoticed.