Keep bibliography out of the shared-vocabulary edge weight - #105
Merged
Merged
Conversation
`build_subsets.py` filtered DOI out of an edge with
shared={t for t in shared if t.split(":")[0]!="DOI" or True}
where the `or True` made the condition unconditionally true, so the set was
rebuilt unchanged and no citation prefix was ever dropped (#62).
`roots.CITATION` already states the principle the filter was reaching for:
every Mech cites papers, so counting those alongside the ontologies "would say
only that". `build_data.py` keeps them out of the heatmap's ordering and the
cell indexes below skip them; the edge weight was the one place that still
counted them, which is the single spot where the code contradicted its own
written rule.
The inconsistency was visible on the page. Three lines down, the examples
attached to each edge already excluded DOI by hand, so a chord's weight was
partly bibliography while the evidence offered for that weight deliberately
was not. Both now read `not in CITATION`, so the two agree by construction and
PMID is covered as well — it contributes nothing today, but by accident of the
data rather than by design.
Measured on the pre-change data before deciding, since this moves published
numbers: citation prefixes are 379 of 11,350 total edge weight (3%), touching
9 of 36 edges, and no edge disappears. The largest single shift is
CellStructureMech-ProteinTraitsMech, down 15%.
No committed data changes here. `assets/fleet/` and `fleet_data.json` are
snapshots of the last full pipeline run, so the new numbers appear when the
pipeline is next rerun as a whole — a partial rerun would date the heatmap and
the edges to different scans. That refresh is owed anyway for #84, #85 and #87.
A test asserting no CITATION prefix reaches an edge needs build_subsets.py to
be importable, which is #97.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
realmarcin
added a commit
that referenced
this pull request
Sep 22, 2026
realmarcin
added a commit
that referenced
this pull request
Sep 22, 2026
realmarcin
added a commit
that referenced
this pull request
Sep 22, 2026
`build_subsets.py` resolved every Mech checkout at import — `MECHS` called `mech_root`, which exits when one is missing — and then scanned. `build_data.py` read both JSON inputs at import and rewrote fleet_data.json. Neither could be imported to look at a constant (#97). Both now keep only the pure constants at module level. `build_subsets` grew a `prepare()` that resolves the checkouts and indexes HabitatMech's pages, and everything from the scan onward moved into `main()`; the function bodies are untouched and still refer to the same module-level names, so `MECHS`, `hab_pages` and the rest are declared empty and filled by `prepare()` rather than moved. `build_data` grew `build()` and `main()`, with the same treatment. Importing either is now free and writes nothing. That lets the tests read the lists the pipeline actually uses. They had been recovered from the source text with `ast`, which cannot see a list rebuilt after its literal — the hole #99 closed partly and #101 recorded the rest of. Reading the objects retires the class: all five shapes now fail, including the three the parser could not see. VOC, _X = [...], 1 parser: passed import: FAILED del VOC[0] parser: passed import: FAILED VOC[0] += "X" parser: passed import: FAILED VOC += [...] parser: FAILED import: FAILED VOC.append(...) parser: FAILED import: FAILED `literal()` and its mutator allowlist are gone with the parsing they served. Verified behaviour-preserving by running both scripts before and after the refactor over the same checkouts and comparing every output file. `assets/fleet` and `subsets_summary.json` match semantically: identical per-Mech scan counts, identical edge weights and `by` breakdowns, identical term lists in identical order, every term entry equal. One edge file differs by bytes, and it is not this refactor. `CAS` and `ENVO` are tied at 1 in that edge's `by`, and `Counter.most_common()` breaks ties by insertion order, which comes from iterating a set of strings under per-process hash randomization. Filed as #107, since it means a refresh churns committed assets for no reason and the pipeline cannot be used to verify itself. No committed data changes here; the runs' output was restored. Rebased over #105, carrying its CITATION filter into main(). From the review: main() now calls prepare() itself, so importing the module and calling main() resolves the checkouts instead of failing with a KeyError inside scan() (closes #117); the unused `import ast` left behind by literal()'s removal is gone (closes #116). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
realmarcin
added a commit
that referenced
this pull request
Sep 22, 2026
`build_subsets.py` resolved every Mech checkout at import — `MECHS` called `mech_root`, which exits when one is missing — and then scanned. `build_data.py` read both JSON inputs at import and rewrote fleet_data.json. Neither could be imported to look at a constant (#97). Both now keep only the pure constants at module level. `build_subsets` grew a `prepare()` that resolves the checkouts and indexes HabitatMech's pages, and everything from the scan onward moved into `main()`; the function bodies are untouched and still refer to the same module-level names, so `MECHS`, `hab_pages` and the rest are declared empty and filled by `prepare()` rather than moved. `build_data` grew `build()` and `main()`, with the same treatment. Importing either is now free and writes nothing. That lets the tests read the lists the pipeline actually uses. They had been recovered from the source text with `ast`, which cannot see a list rebuilt after its literal — the hole #99 closed partly and #101 recorded the rest of. Reading the objects retires the class: all five shapes now fail, including the three the parser could not see. VOC, _X = [...], 1 parser: passed import: FAILED del VOC[0] parser: passed import: FAILED VOC[0] += "X" parser: passed import: FAILED VOC += [...] parser: FAILED import: FAILED VOC.append(...) parser: FAILED import: FAILED `literal()` and its mutator allowlist are gone with the parsing they served. Verified behaviour-preserving by running both scripts before and after the refactor over the same checkouts and comparing every output file. `assets/fleet` and `subsets_summary.json` match semantically: identical per-Mech scan counts, identical edge weights and `by` breakdowns, identical term lists in identical order, every term entry equal. One edge file differs by bytes, and it is not this refactor. `CAS` and `ENVO` are tied at 1 in that edge's `by`, and `Counter.most_common()` breaks ties by insertion order, which comes from iterating a set of strings under per-process hash randomization. Filed as #107, since it means a refresh churns committed assets for no reason and the pipeline cannot be used to verify itself. No committed data changes here; the runs' output was restored. Rebased over #105, carrying its CITATION filter into main(). From the review: main() now calls prepare() itself, so importing the module and calling main() resolves the checkouts instead of failing with a KeyError inside scan() (closes #117); the unused `import ast` left behind by literal()'s removal is gone (closes #116). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
realmarcin
added a commit
that referenced
this pull request
Sep 25, 2026
…kill that does it (#120) * Add a skill for refreshing the X-Mech page from the Mechs as they are now There was no procedure for this, only the pipeline reference in _fleet/README.md, and the last refreshes show what that costs: a census taken from one checkout and card stats from another, a census that read a shared checkout's uncommitted files, and figures on five other pages left behind the cards. The skill pins every Mech and CLAW once, builds a sparse shared snapshot at those commits instead of pulling the shared checkouts, runs the pipeline canary-first with the side effects checked after each stage, re-checks every hand-curated claim against the live sites and the pinned repos, propagates the figures to every page that repeats them, rewrites the provenance record, and stops at a reviewed PR. It keeps the methodology fixed; census scope changes (#84, #87) stay out. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Record the revision each count was read at, and make subset output stable The census and the card stats are written by different scripts, and nothing said which checkout either had read. The last refresh put 364 CommunityMech records in the census and 396 in the stats, and its ProteinTraitsMech census counted 770,276 UniProt references where the commit it claimed holds 657,598, because it read a shared checkout's uncommitted files. roots.revision() now names the commit, marked +dirty for a checkout with local changes; prefix_census.py writes it per Mech under _revisions and mech_stats.py writes it as source_revision. The latter field was already in the committed file, added by hand, so rerunning the script would have dropped it. build_subsets.py wrote two orders that came from iterating a set of strings, which changes with each process's hash seed: tied prefixes in each edge's "by" breakdown (#107) and, not in that issue, the order of the per-cell summary, which reaches subsets_summary.json and the page's embedded data. Both now sort. Two runs over the same snapshot under different seeds are byte-identical. Closes #107. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Fold the first run's lessons into the update skill What the 2026-09-24 run hit that the procedure did not yet say: scanning with resource warnings as errors floods the log because the census never closes its files (#118); git grep -E has no \b; three cross-references pointed the wrong way, so direction needs checking as well as existence; HUB text renders after a prefix; and two sites published new records mid-run. For the last, the skill now says to keep the page a consistent snapshot at the pins and record the live figure in the audit, rather than re-pinning one Mech, which would be a partial rerun. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Refresh the X-Mech page from the Mechs as they are on 2026-09-24 Every number was re-derived from one set of revisions: each Mech's GitHub main and CLAW's, pinned once and read from sparse clones of the shared checkouts, never their working trees. Several of those lagged their remotes by up to 121 commits, and ProteinTraitsMech's carried 45,049 uncommitted files. Derived layer. The census, subsets, fleet data and card stats were regenerated at the pins, and the manifest snapshot advanced to CLAW's pin (semantically unchanged). The census now names its revisions, and RefreshProvenanceTests pins the census, the stats and the site audit to the same commits and record counts. On the old data that test fails for exactly the known partial rerun, 364 vs 396 CommunityMech records; each of its four invariants was mutation-tested. The #105 citation filter takes effect: 379 citation matches leave the edge weights and all 36 edges survive. The previous census's ProteinTraitsMech counts had been read from a dirty checkout (770,276 UniProt references where the revision the last audit pinned holds 657,598), so the lower figures are a correction. Hand-curated layer. Twelve read-only auditors checked 533 claims against the live sites and the pinned repositories; an independent skeptic re-derived each proposed change, and a second round verified what the first round had missed. Beyond the moved figures (CommunityMech 422 and 15 categories, TraitMech 763, CellStructureMech 542, MediaIngredientMech 2,953 / 2,611 mapped, CultureMech 6,288, ProteinTraitsMech 429,293): - three cross-references pointed away from the consumer and now point at it; three new ones are implemented upstream (TraitMech into ProteinTraitsMech, TraitMech and HabitatMech into TaxonMech), and the list now matches XREFS; - the kg-microbe ties claimed exports that do not exist: only MediaIngredientMech feeds the graph, CultureMech and CommunityMech have exporters but no release, and TraitMech receives embeddings rather than exporting SSSOM; - "ENVO-grounded", "a superset of TraitMech's node vocabulary", "one node vocabulary", "content-hashed CURIE" and "evidence with PMIDs or DOIs" were fleet-wide generalizations the sources contradict, and now say what holds; - CultureMech's pages/ index is live again, so its Browse link and card cite it. The figures are propagated to every page that repeats them, and the three dedicated pages' permalinks move to the pins (all 33 anchors checked). CellStructureMech (544) and TraitMech (764) published after the pins; their cards keep the pinned figures and site_audit.json records what the sites showed. Closes #85, #113. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Address the adversarial review of the refresh Provenance that can actually tell. roots.revision() judged a checkout clean from git status, which never lists ignored files, hides untracked ones under status.showUntrackedFiles=no, and says nothing about paths a sparse checkout leaves out, while record_paths() globs the disk. It now compares the counted records with the files git tracks under the same globs, scopes status to the record directories and src/ so an unrelated scratch file is not "dirty" (#121), runs git without optional locks in the shared checkouts (#123), and returns None for a directory inside another repository instead of that repository's HEAD (#124). Each scan takes the revision before reading and stops if HEAD moves (#122), reads records in a with block (#135), and stops on an unreadable record instead of skipping it and reporting the full count (#127). Coverage. build_subsets.py records its revisions too; the tests now require them to equal the census's, fleet_data.json to equal build_data.build() over the committed inputs (#126), and the audit's CLAW pin, card figures, merged PRs and repository set to match (#128). RevisionTests covers clean, modified, hidden untracked, ignored, sparse-excluded and nested cases; SubsetDeterminismTests runs build_subsets.py under four hash seeds on a fixture with tied prefixes (#129). Each was mutation-tested: reverting the #107 sort, the tracked-set comparison or the top-level check turns its test red. Data. CommunityMech's four data/isolates records have no published page, so they now get no record link instead of a 404 (#139). Rerun at the same pins: only CommunityMech's record lists changed; the census, stats, manifest and audit are byte-identical. Content. The hub panel names the four Mechs that share only the namespace (#140); the CommunityMech to CultureMech entry cites CultureMech's import and backfill scripts (#141); the resources.md diagram shows only MediaIngredientMech feeding kg-microbe (#142); CommunityMech's 16-category landing tile is explained (#143); the arrow legend covers scope hand-offs (#145). Skill. CLAW is cloned from GitHub at its pin and the manifest refreshed before anything reads it (#130, #138); the shared checkouts get their own variable (#133); the audit fan-out has a canary (#136); audit SHAs come from the pins (#125); the gate wording allows documented post-pin drift (#134). Closes #121. Closes #122. Closes #123. Closes #124. Closes #125. Closes #126. Closes #127. Closes #128. Closes #129. Closes #130. Closes #133. Closes #134. Closes #135. Closes #136. Closes #138. Closes #139. Closes #140. Closes #141. Closes #142. Closes #143. Closes #145. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Hash each audited page at the pin, and derive the byte-identity note site_audit.json's notes said the live page was byte-identical to the one at the pin for NaturalProductMech, CellStructureMech and TraitMech, while the hash beside each note was of a page published after the pin (#155). The notes were typed; the hashes were fetched. The audit now records the committed copy's hash at the pin next to the live one and generates the sentence from the comparison. That also shows MediaIngredientMech's data file changed after the pin, with the same figures. The skill says never to type an identity claim into the audit. Closes #155. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Complete the cross-reference arrows, and measure TaxonMech in the census The graph drew only two arrows at TaxonMech and none leaving it, although taxa tie it to most of the fleet. Two separate gaps. Direct references. The earlier audit searched records, schemas, docs and READMEs; this sweep also read ingest scripts, vendored snapshots, source catalogues, CI workflows and site generators in all ten repositories at the pins, and an independent verifier checked each of 32 candidate pairs. Both lists now hold the 29 confirmed arrows, one per ordered pair: - 16 new, including TaxonMech's scope hand-offs to TraitMech, HabitatMech and CultureMech (its curation rules leave traits, habitats and media to them) and the write gate it ported from CellStructureMech; - the reverse directions that also exist: CommunityMech records cite CultureMech media and MediaIngredientMech ingredients, and NaturalProductMech defers antimicrobial mechanism to AntibioticMech; - adopted practices, e.g. ProteinTraitsMech's merge rule from MediaIngredientMech and AntibioticMech's site generator from HabitatMech; - the 13 kept arrows carry the verifiers' wording, and HabitatMech to CultureMech is removed: HabitatMech grounds that concept itself and hands nothing over. Taxon identity. Most species and strains the other Mechs cite are TaxonMech records, but that is a shared identifier, which the page draws as vocabulary chords, and TaxonMech had never been scanned (#87). It now is: a census row, eight NCBITaxon overlaps (ProteinTraitsMech 7,444 shared taxa, HabitatMech 6,423, NaturalProductMech 1,708, CommunityMech 1,167 with GTDB, TraitMech 274, CellStructureMech 124, AntibioticMech 109, CultureMech 54), and record links to taxon.html?id=. build_subsets.py scans ProteinTraitsMech and TaxonMech last, keeping only terms another Mech cites; the proteins keep every taxon, so their overlap with TaxonMech is exact. The census-coverage wording is one token that reads "all ten Mechs" now. Other rows are unchanged; TaxonMech's labels improve taxon labels across the overlaps. Record-subset assets grow to about 10.5 MB; the two largest overlap files (2.3 and 1.8 MB) load only on click. Rendered in a browser: 29 arrows, 8 TaxonMech chords, no console errors. Closes #87. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Route the arrows so they stay legible, and fix what the review of 102f461 found Geometry. Arcs bowed away from the chord midpoint, which for opposite nodes is the hub: TraitMech's and MediaIngredientMech's arrows collapsed into one straight line through it, and five arcs passed under other nodes (#166). An arc now runs outside the ring, its control point on the bisector of the two node angles and pushed out only as far as the canvas allows until it clears every node; if it cannot, it bows through the interior around the hub. A reverse pair takes a wider lane, or the other side inside. Measured in a browser: every arc clears other nodes by 19 px and the hub by 74 px, none leaves the canvas, and reverse pairs sit 21 px or more apart. Node labels get a halo where arcs pass behind them. The legend now separates dashed record-data arrows from dotted schema and practice ones, and the overview says so (#167). Arrows, now 30. CellStructureMech's scope rule leaves phenotypes to TraitMech, a missing arrow (#158). The helpers AntibioticMech and NaturalProductMech credit to TraitMech match HabitatMech's and AntibioticMech's copies line for line, so the credit goes on those arrows rather than new ones (#159). Fixed: an evidence path that was never committed (#156), "about two-thirds" of recipes, which is 5,696 of 6,288 canonical media (#160), and three skill names that do not exist (#162). _fleet/README.md now states what counts as an arrow. Taxon labels. TaxonMech is now the first NCBITaxon label authority and a taxon it keys a record by takes that record's label, where its one extra vote had split other Mechs' spellings and erased labels (#163): unlabelled overlap terms fall from 3,301 to 1,714. Its chords show species it has records for rather than Bacteria or a genus (#164), and folded YAML labels are read whole (#165). Edge weights and cells are unchanged. Prose. The taxon note says eight of the other nine Mechs (#168); the heat note says species-level and infraspecific and puts taxa in the organism arm (#169); resources.md's Related lines follow the arrows (#170); the partial-coverage wording has a test again and a stale comment is gone (#171). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Cite CultureMech's committed README now that its pages/ index is gone again #120 had pointed CultureMech's Browse link and its card check at culturebotai.github.io/CultureMech/pages/, which was live on 2026-09-24 and stated the canonical 6,288 merged records. It returns 404 again on 2026-09-25 (#175): CultureMech publishes only committed files from main, pages/index.html has been untracked since CultureMech #320, and the copy that was served was a leftover the 16:22Z Pages rebuild removed. It had also been 404 on 2026-09-20. The Browse link goes back to the site root, which serves the app/ browser. The card note cites the README's corpus snapshot at the pin. check_cards.py reads the committed README (it now accepts an absolute URL), and the audit records it as CultureMech's source; the live README is byte-identical to the pinned one. culturemech.md and _fleet/README.md stop saying the index is live. Closes #175. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * State why CultureMech's pages/ comes and goes, and test the README source Review of d0bbb19. The stated cause was wrong (#180): pages/index.html was never committed and is gitignored since April; CultureMech's generate-pages workflow builds pages/ and deploys it through GitHub Actions, and the branch-based Pages build replaces that deployment on other pushes to main. So the index appears and disappears, and the card intro now says it is intermittently published rather than "not currently" (#181). The README's description of the audit names CultureMech's committed README as the one non-Pages source (#179), and the audit's local_date is derived from its own timestamp (#178). check_cards.py gains source_url() for the absolute-URL case, and its text parser ignores Markdown emphasis, so a README that bolds the number alone still reads; tests cover both and the wrapped-line case (#177). Whether a 404 or reworded source should fail rather than warn is #176, handled with #148. Closes #177. Closes #178. Closes #179. Closes #180. Closes #181. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Say that CultureMech's browser data comes and goes too, and pin the tests to SOURCES Round-2 review of the #175 fix. The branch-based Pages build removes CultureMech's app/data.js along with pages/ and the dashboard, so the app/ browser loaded with no records on September 25; _fleet/README.md now says so and names the upstream fix, setting CultureMech's Pages source to GitHub Actions (#182, recorded on #172). The README and the audit scope describe CultureMech's source as its committed README, the one non-Pages source (#185). The card intro drops a bare "(#175)" that read as CultureMech's own issue (#184). The CultureMech test reads the SOURCES entry, so a mistyped URL or selector now fails it; both mutations were checked (#183). Closes #182. Closes #183. Closes #184. Closes #185. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Stop saying CultureMech's published browser serves its records Round-3 review of the #175 fix. CultureMech's recipe browser loads app/data.js, which only its Actions deployment carries, so it opens empty whenever a branch build has replaced that deployment, as on September 25. The graph panel no longer says the published browser serves the 15,878 records, culturemech.md says the browser can open empty, and the review skill's worked example says the same (#188). The embedding maps are unaffected: their data is inline and committed. The CultureMech source test now pins the whole README URL, so a typo in the branch or file name fails too (#189). Closes #188. Closes #189. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Describe what CultureMech's recipe browser shows without its data Round-4 review of the #175 fix: without app/data.js the browser never leaves "Loading recipes...", it does not open empty (#193). Closes #193. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Say CultureMech's browser never finished loading, as culturemech.md does Round-5 review of the #175 fix: the fleet README still described September 25 as the day the browser "loaded with no records"; without app/data.js it never leaves "Loading recipes..." (#204). Closes #204. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Let the README parser survive a wrap inside the label, and name the README exception everywhere From the Codex review of the #175 fix. The text parser collapsed no whitespace, so a formatting-only wrap between "merged" and "records" in CultureMech's README returned None; whitespace now collapses before matching, with a test for a wrap inside the label (#207). _fleet/README.md and the review skill's general descriptions of the card sources now name CultureMech's README exception (#208). Closes #207. Closes #208. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Name CultureMech's README exception in the remaining general statements Verification review of e115229: the fleet README's census paragraph, the check_cards.py docstring and assemble_page.py's fleet_records docstring still said every card follows a published site; the review skill now says the README figure is on the card and in MECHS (#211). Closes #211. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #62.
The
or Truemade the condition unconditionally true, so the comprehension rebuilt the set unchanged and no citation prefix was ever dropped.Why implement it rather than delete it as dead code
roots.CITATIONalready writes down the principle:build_data.pykeeps them out of the heatmap's ordering; the cell indexes skip them. The edge weight was the one place the code contradicted its own rule.And the contradiction was visible on the page: three lines down, the examples attached to each edge already excluded DOI by hand. So a chord's weight was partly bibliography while the evidence offered for that weight deliberately was not. Both now read
not in CITATION, so they agree by construction — and PMID is covered too, which today contributes nothing by accident of the data rather than by design.Measured before deciding, because it moves published numbers
The graph's shape does not change and no chord vanishes.
No data churn in this PR
assets/fleet/andfleet_data.jsonare snapshots of the last full pipeline run, so nothing committed changes here andassemble_page.py --checkstill passes. The new numbers appear when the pipeline is next rerun as a whole — a partial rerun would date the heatmap and the edges to different scans, which is the trap that bit this repo before. That refresh is owed anyway for #84, #85 and #87, and belongs in that PR where the 40-file diff is the point rather than noise.A test asserting no
CITATIONprefix reaches an edge needsbuild_subsets.pyto be importable — that is #97, which is next.27 tests and
assemble_page.py --checkpass; the only changed file isscripts/fleet/build_subsets.py.🤖 Generated with Claude Code