Bug sweep — packages/ts
State: open/active · Genre: analysis
A deep, adversarially-validated bug hunt over packages/ts, run one package
at a time, bottom of the dependency stack first.
The earlier attempt ran the whole tier at once in two batches. It did not finish: 12 of 36 territories scanned, every validation agent lost to a session limit, and 314 candidates left unconfirmed. Package-at-a-time replaces it because one package fits inside a single session’s budget, which is what makes the deep rounds reachable at all.
Why bottom-up
Section titled “Why bottom-up”packages/ts is three layers deep with no cycles. Sweeping a leaf first means
its fixes are merged before anything downstream is scanned, so a dependent’s
scan reads the corrected code and does not re-report an upstream defect as its
own. It also front-loads blast radius: a bug in command-seam reaches five
packages, one in markdown-ui reaches none.
Order within a layer is by transitive dependents, then by size.
Per-package procedure
Section titled “Per-package procedure”Each package is one pass through the bug-finder skill, scoped to that package:
- Scan — three altitudes (in-function, in-module, in-library), bomb
rounds, with
knownseeded from what previous runs already found so early rounds do not re-report fixed bugs. - Validate — adversarial refuters per candidate, majority to survive. Findings that skip this step are candidates, not bugs.
- Rate — severity and
reachableToday(yes/only-for-consumers/no). - Fix — one worktree per package, regression test per fix.
- Gate — the package’s own
scripts.testANDscripts.typecheck. A green suite is half the gate. - Land — one PR per package, merged before its dependents are swept.
Status
Section titled “Status”Legend: ☐ not started · ◐ scanned, not validated · ☑ landed
files counts non-test .ts/.vue/.js sources. dep is transitive
dependents inside packages/ts.
L0 — leaves (no packages/ts dependencies)
Section titled “L0 — leaves (no packages/ts dependencies)”| package | files | dep | PR | notes | |
|---|---|---|---|---|---|
| ☐ | command-seam | 3 | 5 | highest blast radius in the tier | |
| ☐ | markdown-util | 1 | 4 | never had a territory in the old plan | |
| ◐ | obsidian-md | 50 | 3 | 37 candidates, unvalidated | |
| ◐ | ui | 53 | 1 | 27 candidates, unvalidated | |
| ◐ | json-canvas-core | 37 | 1 | 34 candidates, unvalidated | |
| ☐ | agent-call | 13 | 1 | ||
| ☐ | intersect | 13 | 1 | shared corpus is include_str!’d by the Rust port — a TS-only fix can break packages/rust/intersect | |
| ☐ | panes | 11 | 1 | ||
| ☐ | system-model | 6 | 1 | ||
| ☑ | node-tree | 5 | 1 | #1838 | 1 fixed: an invalidated in-flight fetch clobbered the retry’s fresh page; per-branch write generation |
| ☐ | flowline | 9 | 0 | ||
| ☑ | fleet | 5 | 0 | #1834 | 0 bugs — nothing survived refutation; 3 doc corrections and one env-injecting test fix |
| ☐ | graph-canvas | 5 | 0 | ||
| ☑ | yaml-splice | 4 | 0 | #1837 | 2 fixed: non-finite numbers silently written as null; assertSingleSpan asserted only tautologies |
| ☐ | dashboard-client | 2 | 0 | never had a territory in the old plan | |
| — | prettier-config | 0 | 0 | config only, nothing to scan | |
| — | tsconfig | 0 | 0 | config only, nothing to scan |
L1 — depend only on L0
Section titled “L1 — depend only on L0”| package | files | dep | PR | notes | |
|---|---|---|---|---|---|
| ☐ | easy-git | 11 | 2 | ||
| ☐ | obsidian-vault | 3 | 2 | ||
| ◐ | obsidian-editor | 51 | 1 | 49 candidates across two shards, unvalidated | |
| ◐ | detect-runners | 44 | 1 | 47 candidates across two shards, unvalidated | |
| ◐ | app-frame | 90 | 0 | 41 candidates across two shards, unvalidated; largest package in the tier | |
| ◐ | json-canvas-vue | 23 | 0 | 27 candidates, unvalidated | |
| ☐ | agent-plugin | 16 | 0 | ||
| ☐ | manifest-graph | 8 | 0 | parallel port of solutions/ontological/crates/manifest-graph; examples/dump.rs diffs the two, and no shared corpus makes a TS-only drift go red |
L2 — depend on L1
Section titled “L2 — depend on L1”| package | files | dep | PR | notes | |
|---|---|---|---|---|---|
| ◐ | markdown-ui | 61 | 0 | 21 candidates, unvalidated | |
| ◐ | obsidian-bases | 20 | 0 | 31 candidates, unvalidated | |
| ☐ | waypost | 17 | 0 | ||
| ☐ | agent-dev | 10 | 0 | never had a territory in the old plan | |
| ☐ | easy-gh | 6 | 0 | ||
| ☐ | vault-graph-core | 6 | 0 |
Calibration — what the two batches actually cost and yielded
Section titled “Calibration — what the two batches actually cost and yielded”Six packages swept package-at-a-time, 47 agents, no session limit hit.
| batch | packages | candidates | confirmed | fixed | agents |
|---|---|---|---|---|---|
| 1 | markdown-util, dashboard-client, command-seam | 21 | 5 | 5 | 24 |
| 2 | yaml-splice, fleet, node-tree | 31 | 11 | 3 | 23 |
Three numbers worth carrying forward.
- Refutation kills roughly three quarters of candidates. 52 raw to 16
confirmed. The earlier tier-wide run’s 306-to-137 was a gentler filter; three
refuters defaulting to
refuted: trueis the stricter and better gate. - The land agent is a second filter, and it earns its place. 16 confirmed
became 8 fixed. Five of the eight declines were bugs already fixed on
mainthat the scan re-reported from stale line numbers, and two did not reproduce when probed — one of those would have added alist()per open branch on every scope change, the other would have hidden legitimate failures. Telling the land agent it may refuse a confirmed finding is what caught them. - A package that was just swept yields little. fleet confirmed nothing. That is the correct result, not a failed run, and the brief has to say so or the agent manufactures work.
Dedupe by file:line:title does not catch one bug reported three ways: three
yaml-splice altitudes each found the non-finite-number defect at the same line
under different titles, and all three survived to the land agent as separate
findings. Dedupe on the failure, not the label.
Carried-over candidates
Section titled “Carried-over candidates”The abandoned tier-wide run left 314 raw candidates over 12 packages,
saved at deep-a.json in the session scratchpad. They are unfiltered: on the
first sweep 306 raw candidates became 137 after refutation, and a later
calibration pass downgraded 18 severities against 2 upgrades. Scanner
self-ratings run optimistic.
When a ◐ package comes up, validate its carried candidates first and seed the scan’s exclusion list with them, rather than re-scanning cold.
Rules that earned their place
Section titled “Rules that earned their place”Each of these came from a defect this sweep shipped and had to repair.
- A green suite is half the gate.
scripts.typecheckis a separate CI job fromscripts.test. A test file that passes at runtime can still failtsc. - A test belongs in the package that owns the code it pins. Mocking a bare specifier the test’s own package does not declare intercepts the module only if both resolve to the same copy — it mocks correctly on a dev machine and silently mocks nothing in CI.
- Wait for the condition, not for a tick count. A fixed number of
nextTick/setTimeoutrounds races a lazy import; a loaded CI runner wins. - Check the territory boundary before fixing. A TS package with a Rust port or a shared fixture corpus can be broken by an edit that keeps its own suite green.
- Never add
viteas a direct devDependency of apackages/ts/*package. It makessolutions/ontological/desktop/bun.locknever reach a fixed point.