Skip to content

Publication readiness: primary-call matrix, EVE toolchain, vignette suite, hygiene - #7

Merged
mdmanurung merged 60 commits into
mainfrom
claude/pub-readiness-hygiene
Jul 21, 2026
Merged

mdmanurung merged 60 commits into
mainfrom
claude/pub-readiness-hygiene

Conversation

@mdmanurung

Copy link
Copy Markdown
Owner

Prepares ViralScan for publication. 19 commits spanning features, docs, and hygiene; test suite green throughout.

Features

  • Primary-call matrix (feat(detection)): --multimap-primary-call now consistently drives viral numerators across summaries, per-cell rows, plots, and cell-type enrichment via a shared matrix_for_genes() helper, while total-UMI denominators stay on the full matrix. HTML "Infected cells" now counts unique barcodes.
  • EVE annotation toolchain (feat(covid)): annotate_eve.py + hardened slurm_eve_analysis.sh for endogenous-viral-element screening against GRCh38 (Phase A/B/C), env-var-driven and path-clean.

Correctness fixes

  • EVE annotation (fix(covid)): Phase B no longer treats BLAST subject-local coordinates as chromosome coordinates (clone/scaffold subjects flagged); added chromosome-name normalization (chr77) with a namespace-mismatch warning to prevent silent all-intergenic output; SLURM read-extraction surfaces real errors instead of swallowing them.

Docs

  • 8-vignette suite (docs(vignettes)): replaces basic_usage.ipynb; each grounded in a manuscript result (multimapping recovery, cell-calling denominators, enrichment, specificity, QC/evidence, host-response depth control). Six execute in CI; two are [skip-ci] with public-data blocks. No institutional paths.
  • Manuscript honesty (docs(manuscript)): data-availability, runtime, and citation fixes.

Refactor & hygiene

  • refactor(detection): dedupe the primary-call plumbing (resolve_count_matrix()), drop dead code.
  • chore(package) / chore(hygiene): remove institutional abs paths from shipped files; scrub the third-party evonk conda path from docs; version reconciled to 2.6.0; --no-deps Docker/CI; declare anndata.
  • chore(curation): gitignored, regenerable symlink curation view (scripts/build_curation_view.py) for manual paper curation — non-breaking.

Known follow-ups (tracked in todo/TODOLIST.md)

  • ~14 functional SLURM/infra scripts still embed VS_CONDA_ENV=/exports/.../evonk/... as a default — needs per-script env-var-ization or ship-scope exclusion.
  • Complete the reference-strategy benchmark (4/12 rows).
  • Git SSH commit-signing key mismatch (user.signingkey) — recent commits may be unsigned.

🤖 Generated with Claude Code

mdmanurung and others added 24 commits July 15, 2026 19:21
…PLAN SH2.6)

Three new columns in viral_summary.tsv to help distinguish genuine viral
infection from endogenous viral element (EVE) read artifacts, motivated by
COVID ViralScan finding F-005 (anellovirus reads mapping to NALCN/chr13 and
LINC02742/chr11 EVE loci rather than exogenous virus):

- accession_breadth: fraction of indexable gene IDs with ≥1 UMI in any cell.
  Genuine infection spreads across ORF1/ORF2/ORF3; EVE artifacts concentrate
  on 1-2 host-integrated loci. Computed in compute_stats() from viral_matrix.

- host_viral_ambig_fraction: proportion of viral UMI that also mapped
  ambiguously to the host genome, from the existing
  adata.layers["counts_host_viral_ambiguous"] written by multimap.py (was
  never read by detection.py until now). High fraction = reads from host
  genomic regions. Only populated when multimapping is enabled.

- eve_risk: Boolean flag when the virus family/genus appears in EVE_RISK_GENERA
  (new frozenset in constants.py), covering all Anelloviridae genera plus
  Gyrovirus.

All 582 tests pass. No changes to existing column schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Original `viralscan evidence` run (job 25180994) crashed at samtools sort
because combined.fa had duplicate NC_002076.2 headers. A manual hf_align
job (25181135) filled the gap but was never committed, leaving BAM/coverage.tsv
non-reproducible.

This commit closes that gap:
- covid_viralscan/scripts/slurm_evidence_rerun.sh: re-aligns the existing
  viral_reads.fasta (912 MB, produced before the crash) to viral_genome.dedup.fa
  (1 copy of NC_002076.2) via minimap2 -ax sr + samtools sort, then regenerates
  coverage.tsv via viralscan.evidence.coverage_table(). Covers both x213-g and
  x216-g in one job.
- covid_viralscan/RUNBOOK.md: added Stage 5 (Evidence) section documenting the
  crash cause, the dedup fix, and how to re-run.
- PLAN.md: added T5 completion note (closes T5).

To regenerate BAM + coverage.tsv: sbatch covid_viralscan/scripts/slurm_evidence_rerun.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oducibility note

F-005 findings file updated 2026-07-15:
- Mark reproducibility note RESOLVED (slurm_evidence_rerun.sh committed)
- Add CellTypist enrichment (T6 tripwire) result:
  * EBV (HHV4_EBNA-2): 5 positive cells, all epithelial, 0 B cells — absent
  * Anelloviruses enriched in Epithelial cells (OR 3.3–4.2, FDR<1e-40) and
    Plasma cells (OR 3.1, FDR 5e-13) — consistent with EVE-artifact mechanism
    (intronic pre-mRNA from ubiquitous genes expressed in epithelial/B-lineage)
  * HHV-6b, HHV-1: 2–4 cells, below interpretation threshold

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…earning, last-session update

- decisions.md: T5 reproducibility (commit-not-rerun rationale) + T6 negative EBV result
- learnings.md: plasma cell EVE mechanism (high intronic pre-mRNA → EVE reads from expressed loci)
- last-session.md: updated to reflect post-compaction session (SH2.6, T5, T6 closed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ifact (job 25237061)

Phase A/B/C results:
- All 8 detected anellovirus accessions (Alpha/Beta/Gamma/Samek) appear in Phase A
  (multi-chromosomal GRCh38 alignment, depth 67–1503) — accession-level artifact confirmation
- NC_001479.1: Phase B BLAST confirms 100% identity to human intergenic at pos 120-303
- Phase C clean: HHV-1, EBV, HHV-6B, CeHV2, MPXV, Molluscum, SARS-CoV-2 are reference-clean
- Surviving non-EVE signal (1–4 UMI each) below defensible detection threshold
- F-005 closed: no genuine viral infection in these COVID PBMC samples

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
anndata is an eager top-level import in scripts/multimap.py but was only
satisfied transitively via scanpy in pyproject.toml, and was absent from
environment.yml along with scikit-learn. Declare anndata>=0.9 explicitly and
add both anndata and scikit-learn to environment.yml so the documented conda
environment is complete (and so a --no-deps install is safe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every runtime dependency is now satisfied by the conda environment built from
environment.yml, so pip should not re-resolve them. --no-deps mirrors the CI
install and avoids letting pip rebuild snakemake from PyPI, whose
connection_pool transitive dep fails to build with older setuptools.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
STAR_BIN was pinned to an author-specific conda-env absolute path, which broke
the reference-strategy benchmark on any other machine. Default to 'STAR' on
PATH and allow an override via the VIRALSCAN_STAR_BIN environment variable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
results_genomic/ and results_hostfilter/ under covid_viralscan/ are
human-subjects outputs and were untracked, one 'git add .' away from being
committed. Exclude them alongside the existing covid data/results ignores.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Independent 7-dimension review (adversarially verified) + Linus code review of
the package ahead of release. Records the two-track verdict (software days-away,
manuscript venue-gated), the completed pre-tag hygiene fixes, and the remaining
owner-gated items. Updates PLAN 'Next up' and .living/decisions.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Learning behind the anndata fix: a green suite does not validate the declared
install contract when CI installs with --no-deps. Captures the process-gap and
how to gate it (bare-venv wheel import / deptry; keep pyproject and
environment.yml deps in sync).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
reference_strategy.py SLURM template (conda setup, PYTHONPATH), fastq_root
fallback, and default_manifest() paths are now env-driven / neutral placeholders;
emptydrops.R honours VIRALSCAN_R_LIBS / R_LIBS_USER instead of a hardcoded R
library path. Completes the STAR_BIN fix so a pip install no longer ships one
developer's filesystem. No test asserts these values; write_slurm_array still
renders and preflights the required tools.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The function deletes the output directory's contents (os.remove / shutil.rmtree)
after a prompt, but its name and docstring described a read-only inspection.
Rename it and document the destructive behaviour; update the single caller.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hostresponse._detect_and_normalize had a sparse/dense if/else with byte-identical
arms; build_ref_main nested an 'if reference_panel == anellovirus' inside the
identical outer check. Both are no-ops; collapse them. Behaviour unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HEAD had drifted ~20 commits past the 2.5.0 bump with a behavior-changing default
(multimap-method equal -> host-conservative) sitting in [Unreleased], and no tag
existed. Bump __version__ and all version strings to 2.6.0, and close [Unreleased]
as [2.6.0] with the previously-undocumented changes (EVE artifact flags, sibling
cross-mapping warning, EM/multimap performance work) plus the packaging fixes. Tag
+ publish remain user-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Data Availability mislabelled GSE210063 as 'COVID-era clinical' (it is HHV-6B
CAR-T); corrected. Removed the unsupported 'under 2 h on 8 cores' runtime claim
(documented full-depth runs were 3.5-4 h). Added the missing ViralTrack reference
(Bost et al., 2020) and in-text citation. Added an explicit ethics/provenance
placeholder for the unpublished COVID clinical libraries (IRB statement + author
identities still required from the authors). Sync PLAN 'Next up'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
--multimap-primary-call unique-only previously controlled only sample-level
detection; downstream summaries, per-cell rows, plots, and cell-type enrichment
still read viral numerators from adata.X. Add a shared matrix_for_genes() helper
and route all viral numerators through the selected primary-call matrix, while
keeping full-matrix total-UMI denominators for umi_per_10k and viral_fraction.
The HTML report's "Infected cells (any virus)" count now counts unique
virus-positive barcodes rather than cell-virus rows. Documents the policy in
docs/output_reference.md, README.md, and the report template.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
annotate_eve.py annotates endogenous-viral-element analysis outputs against
GRCh38 (Phase A read loci, Phase B NT BLAST hits, Phase C panel-vs-genome PAF)
with a bundled test. slurm_eve_analysis.sh drives the three phases; it takes
machine-specific paths through environment variables (no institutional paths),
validates tools and inputs before long-running phases, and survives zero BLAST
hits under pipefail. covid_viralscan/README.md replaces hardcoded CellRanger
and FASTQ paths with env-var instructions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Supersede basic_usage.ipynb with a task-oriented suite, each grounded in a
manuscript result narrative: quickstart, reference building, multimapping
correction, cell-calling denominators, cell-type enrichment, specificity/
true-negative, QC & read evidence, and host-response with depth control. Six
execute in CI on synthetic/committed data; two are [skip-ci] (index build /
kb count) with public-data download blocks and no institutional paths. Adds a
README index and VIGNETTES_PLAN, wires the Sphinx toctree, and fixes the
enrichment vignette to pass RunConfig (not a dict) to cell_type_enrichment().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Add the mycelium session logs, decisions, learnings, INDEX, and log registry
for the 2026-07-15..20 sessions (code review, primary-call matrix, EVE
toolchain, vignette suite), plus the covid review findings under outputs. Add
the aifi-scrna-pipeline convention pack and reference it from CLAUDE.md and
ACTIVE_CONVENTIONS.yaml. Excludes transient data-lineage hook telemetry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Add a non-breaking curation layer: scripts/build_curation_view.py generates a
gitignored curation/ tree of relative symlinks from scripts/curation_manifest.yaml,
organized both by manuscript result (by-result/) and by artifact type (by-type/).
Originals are never moved; the view is disposable and regenerable (--clean).
Adds /curation/ to .gitignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Two silent-failure bugs in annotate_eve.py:
- Phase B looked up BLAST subject-local coordinates as GRCh38 chromosome
  coordinates, mis-annotating clone/scaffold NT subjects. Gate on
  _is_chromosome_subject() (whole-chromosome RefSeq only); flag others as
  subject_not_chromosome.
- No chromosome-name normalization, so a UCSC-vs-Ensembl (chr7 vs 7) mismatch
  annotated everything intergenic. Add _normalize_chrom() in load_gtf_genes keys
  and annotate_locus queries, plus _warn_namespace() when Phase A/C query
  chromosomes are disjoint from the GTF.

Also harden slurm_eve_analysis.sh read extraction to keep stderr and emit a
visible WARN instead of a silent `|| true`. Adds Phase B parse-path,
normalization, and namespace-warning tests (8 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Non-behavioural cleanup from the review: extract the 5x
`viral_count_matrix if ... else adata.X` guard into utils.resolve_count_matrix();
drop matrix_for_genes' dead non-get_indexer branch (AnnData var_names always has
get_indexer); collapse the double .sum() in the host-viral ambiguity denominator
to a single float(...sum()); reuse _sum_axis1 for the per-cell total-UMI sum.
57 targeted tests pass; ruff clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Replace the third-party `evonk` conda path with a generic `python` /
`$VS_CONDA_ENV` placeholder in PLAN.md and covid_viralscan/RUNBOOK.md. Gitignore
the `.living/log/data-lineage/` hook telemetry (contains abs paths, never
tracked). Add the PLAN.md vignette-suite DONE row (PLAN contract) and refresh
todo/TODOLIST.md.

Note: ~14 functional SLURM/infra scripts and reference_manifest.json still embed
`VS_CONDA_ENV=/exports/.../evonk/...` as a default; scrubbing those needs
per-script env-var-ization or ship-scope exclusion — tracked in TODOLIST.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

mdmanurung and others added 5 commits July 21, 2026 00:06
Session log, decision, and learning (L-24) for the atomic-commits push, the
gitignored symlink curation view, and the "do all" todo pass (EVE fixes,
primary-call cleanup, hygiene). Captures the reusable non-breaking-reorg pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
build_multimap_layers called pd.isna(ec_raw) on every one of the ~100M BUS
records (~5% of the pass, 2M calls / 2.3s on a 2M-record profile). Replace with
a single vectorised `~pd.isna(ec_arr)` mask applied once before the loop.
Byte-identical output; equal 36.5s->33.9s, em 51.8s->50.5s on 2M records; 33
multimapping tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
The 195 src/viralscan/data/*.gtf (7.7 MB) are fetched from Zenodo via
`viralscan data fetch` at runtime (cache dir, not the package dir), nothing
reads the bundled copies, and package-data already excludes them from the wheel.
Untrack them (git rm --cached; local copies kept) and gitignore. Shipped
anellovirus_accessions.tsv is unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Session log, learning L-25 (committed profiling artifacts go stale), and the
verified repo-slim result.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
build_multimap_layers iterated every one of the ~100M BUS records. bustools
emits one record per (barcode, UMI, ec), so a (cell, ec) recurs once per
distinct UMI — 3.18x duplication measured on a real output.bus.txt. Every
emitted share is linear in count for a fixed (cell, ec) and the output matrices
sum duplicate COO entries order-independently, so sum counts once (vectorised
barcode->cell map + groupby(['cell','ec']).sum()) before the loop.

Numerically identical: golden comparison of all 8 layers x 4 methods at
rtol=1e-9 (6x-duplication synthetic) matches; 33 multimapping tests and the full
suite (593) pass. Realistic-duplication benchmark (dup 3.43x): equal 19.2->6.3s
(3.0x), em 28.8->9.1s (3.2x). Stacks on the CSR fast path and pd.isna hoist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
mdmanurung and others added 29 commits July 21, 2026 07:57
Four never-called symbols and one dead branch, net -36 lines, golden-identical
(multimap all-layers rtol=1e-9) and full suite (593) green:

- multimapping._matrix_value + its non-sparse fallback: original_counts is always
  the sparse kb count matrix, so coerce to CSR once and drop the `orig_csr is
  None` special case. One code path, no per-element scipy __getitem__ dispatch.
- detection._encode_image: a base64 helper duplicated by inline code already in
  generate_html_report; never called.
- reference_strategy.command_for_row: never-called wrapper; callers use
  commands_for_row(...)[0] inline.
- menu._config_bool: never called.

Each verified to occur exactly once (definition only) across all tracked .py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
todo/read-start-distribution.md: detailed plan for read-start distribution +
PCR-dup handling in `viralscan evidence` (per-position histogram along the viral
genome; --read-start-profile / --dedup umi|markdup|none). docs/ROADMAP.md:
tiered future-work plan grounded in the manuscript's stated limitations, this
session's review findings, and existing TODOLIST items. Indexed in TODOLIST.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
…admap

docs/IMPLEMENTATION_PLAN.md orders all ~24 roadmap items into dependency-aware
phases (M0 ship → P1 robustness/CI → P2 host-response → P3 evidence positional
features → P4 reference/specificity → P5 validation → P6 perf → M7 release), each
tagged AUTO / COMPUTE / OWNER so the "back to back" scope is honest about what I
can build end-to-end vs what needs a cluster, real data, or a release action.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
…p (D1-D3)

- D1: Detection.preprocessing makes var_names unique up front (warns) so
  duplicate reference accessions no longer crash gene->column lookup;
  matrix_for_genes raises a clear error on non-unique input. New test.
- D2: EVE _is_chromosome_subject accepts legacy gi|...|ref|NC_...| sseqids
  (re.search with a start/pipe boundary), still excludes viral NC_0xxxxx. Test.
- D3: clamp host_viral_ambig_fraction to [0,1] (the ambiguous layer is not a
  strict subset of adata.X viral counts, so the ratio can slightly exceed 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
`viralscan evidence --read-start-profile [--dedup umi|markdup|none] [--bin-size N]`
writes a per-position 5' read-start distribution along each viral reference
(read_start_profile.tsv) — exposes 3' bias, subgenomic-RNA junctions, and
EVE/integration hotspots the aggregate coverage summary cannot.

Pure `_parse_sam_read_starts` (CIGAR ref-span, strand-aware 5' start, UMI dedup
per CB+UMI from the <CB>_<UMI>_<n> read names, flag filtering, binning) is
unit-tested on synthetic SAM with no samtools dependency, mirroring the existing
_parse_coverage_output split; `read_start_distribution` is the samtools wrapper
(markdup mode shells out to samtools markdup -r). 94 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
From the end-of-increment parallel review:
- evidence_run: `--read-start-profile` without `--viral-fasta` was a silent
  no-op; now `_die`s like `--blast`.
- detection.preprocessing: raise a clear error on duplicate var_names instead of
  var_names_make_unique() (renaming silently dropped the duplicate accession's
  counts, since the viral accession list keeps the original name).
- evidence._parse_sam_read_starts: guard int(FLAG)/int(POS) so a stray non-record
  line is skipped, not a crash (matches the sibling parsers); if/else -> ternary.
- annotate_eve._is_chromosome_subject: tighten regex to human chromosomes
  NC_000001..24 (+ NC_012920); NC_0000\d\d also accepted e.g. mouse NC_000067.
- test_multimapping: unused loop var trial -> _trial (ruff B007).

Full suite 602 green; ruff clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
`viralscan evidence --cell-tags` writes viral_reads.tagged.bam with CB:Z/UB:Z
tags parsed from the <CB>_<UMI>_<n> read names, so viral reads can be grouped by
cell in IGV. Pure `add_cell_tags_to_sam` is unit-tested on synthetic SAM;
`write_tagged_bam` is the samtools wrapper. Guarded to require --viral-fasta.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Each run writes results/reference_provenance.json recording the viral reference
used (index / t2g / GTF, technology, multimapping settings) and the viral
accessions both in the reference and detected — so results are traceable to
their annotation (the paper's EBV gene-attribution divergence is annotation-
driven). Pure reference_provenance() is unit-tested.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
From the batch parallel review:
- add_cell_tags_to_sam skips lines without the 11 mandatory SAM fields (like the
  sibling parser's len(f)<6 guard) instead of appending a tag after a
  non-optional field on a malformed record.
- _cb_umi returns None when the CB or UMI is empty (e.g. "_TTTT_1"), avoiding an
  invalid empty CB:Z:/UB:Z: tag value. Also hardens A1's read-start dedup.

G1 provenance reviewed clean. Tests added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
C1 (host-response gene symbols + enrichment) was already implemented — mygene.info
mapping (_map_ensembl_to_symbols, graceful on network failure), _add_symbol_column
on the weights/stability/depth-diagnostics/differential CSVs, and Ensembl->symbol
translation before gget.enrichr. Add the missing network-free unit tests for the
pure helpers (detection, column insert with version-stripping, empty-input).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
- F5: add a CI job that executes the 6 CI-runnable vignettes with nbmake so docs
  can't silently rot (the enrichment vignette once broke under [skip-ci]); the two
  heavy [skip-ci] notebooks are excluded. All 6 verified to run locally.
- G2: non-human host support was already present (ENSEMBL_SPECIES: mouse
  mus_musculus/GRCm39 + 15 species); add a test locking mouse/rat/macaque
  resolution and the unknown-host error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Primary-call matrix consistency, evidence read-start/cell-tags, reference
provenance, host-response gene symbols, EVE toolchain + fixes, non-human hosts,
the vignette suite + nbmake gate, the multimap collapse (~3x faster / ~6x less
peak RSS), and the repo-slimming/hygiene work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Cut 2.7.0: __version__, CITATION.cff, Dockerfile, Singularity.def,
conda-recipe/meta.yaml, and docs/cli_reference.md all bumped 2.6.0 -> 2.7.0;
CHANGELOG [Unreleased] closed as [2.7.0] - 2026-07-21 with compare-links updated
(v2.6.0...v2.7.0). Ready for `git tag v2.7.0`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
The PR was red in CI (never actually green — local runs use py3.14). Fixes:
- hostresponse.py: add `from __future__ import annotations` so the
  `seeds: list | None` parameter annotation no longer evaluates `type | None` at
  import time, which raised TypeError on Python 3.9 and broke collection of all
  hostresponse tests (a declared-supported version).
- test_analysis.py: `test_data_dir_has_gtf_files` now skips when no GTFs are
  present — they are fetched from Zenodo (`viralscan data fetch`), not bundled
  (untracked in this branch), so a fresh checkout has none.
- ruff format: 7 files reformatted to satisfy `ruff format --check` (pre-existing
  format debt in constants.py/reference_strategy.py plus this session's files).

Full suite 613 green; ruff check + format --check clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
kb-python 0.30.x has no `--version` flag: `kb --version` prints help and exits 1,
which under `set -e` aborted the "Validate documented conda environment" job
before `snakemake --version`. Use `kb --help > /dev/null` (reliable exit 0) to
confirm the kb CLI is installed and runnable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
- evidence.py: annotate _parse_sam_read_starts / read_start_distribution
  return type as list[dict[str, object]] and the dedup set as
  set[tuple[object, str]] (was bare dict/set, tripping [type-arg]).
- multimapping.py: rename the EM-allocation loop var genes -> ec_genes so
  it no longer collides with the list[int] genes bound earlier in
  build_multimap_layers (was tripping [assignment]).

Lint job runs mypy src/viralscan; these were the last 4 errors keeping
PR #7's CI red. ruff check + ruff format --check + mypy now all clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
…back

The Lint job's `mypy src/viralscan` failed on installed third-party source:
anndata>=0.13 ships py.typed and uses PEP 695 (`class Foo[T]`) syntax that
mypy rejects while targeting python_version=3.10 ("Type parameter lists are
only supported in Python 3.12+"), aborting before any of our code is checked.

- pyproject: add a mypy override with follow_imports="skip" for anndata.*/
  scanpy.* — treat as Any without parsing, so upstream syntax can't break the
  lint job (preferred over pinning mypy target to 3.12, which would forfeit
  3.10-syntax gating on our own code, or pinning the deps).
- menu.py: with anndata no longer masking downstream checks, mypy flagged a
  real error — the pyfiglet-absent fallback `_figlet_format` had a signature
  incompatible with the real `figlet_format` (conditional variants must match).
  Match pyfiglet's (text, font="standard", **kwargs) -> Any.

Reproduced in a fresh venv with the exact CI pip line (anndata 0.13.2, mypy
2.3.0): ruff check + ruff format --check + mypy src/viralscan all clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
`kb --help` prints usage to stderr and exits non-zero in kb-python 0.30.x (it
wants a subcommand), so it aborted the smoke-test step under `set -e` even with
stdout redirected — the prior `kb --help > /dev/null` fix was wrong. Use
`command -v kb`, which is the reliable check that the kb console script is
installed in the environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZ2gKabckBq5McUgZa7w8s
@mdmanurung
mdmanurung merged commit 2ef70f2 into main Jul 21, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant