Consolidate bulk/single-cell TCR analysis into BULKTCR_ANALYSIS; remove dead cellranger/pseudobulk code - #97
Conversation
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…finding) Update prose comments in workflows/singlecell.nf and subworkflows/local/pseudobulk_qc.nf to use the new ANNOTATE subworkflow name (renamed from ANNOTATE_FROM_CONCAT in commit f08bdc7). - workflows/singlecell.nf: lines 5, 10, 17 (header docblock) - subworkflows/local/pseudobulk_qc.nf: lines 27, 79 (doc comments) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wraps ANNOTATE + gated SAMPLE/PATIENT/COMPARE + gated bulk report rendering into one shared subworkflow so both TCRTOOLKIT and (later) SINGLECELL_WORKFLOW can call it instead of importing the four subworkflows individually. TCRTOOLKIT now delegates its whole main analysis body to BULKTCR_ANALYSIS. CONVERT drops its cellranger branch (PSEUDOBULK_CELLRANGER/PSEUDOBULK_PHENOTYPE_CELLRANGER), since bulk mode no longer supports cellranger input. Adds a negative test asserting bulk mode with input_format=cellranger fails. It currently passes for an adjacent reason to the one it will assert once the schema is tightened in Task 4: the schema still accepts "cellranger" and CONVERT's else-branch treats it as an untyped passthrough, so the raw Adaptive-format fixture reaches ANNOTATE_PROCESS unconverted and errors on missing AIRR columns rather than being rejected at validation time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the individual ANNOTATE/SAMPLE/PATIENT/COMPARE imports and calls in the shared-engine block with a single BULKTCR_ANALYSIS invocation (reports off, since bulk report templates assume samplesheet metadata that single-cell-derived samplesheets don't carry). Updates the CLUSTER_TO_SC_SW call site to source giana_clusters/gliph2_cluster_details/tcrdist_clone_df/ tcrdist_output from BULKTCR_ANALYSIS instead of PATIENT/SAMPLE directly.
Deletes the cellranger/pseudobulk-phenotype code path left physically in place after Task 2 removed it from the convert/tcrtoolkit workflows: bin/pseudobulk.py, bin/create_pheno_samplesheet.py, modules/local/convert/pseudobulk_cellranger.nf, modules/local/convert/pseudobulk_phenotype_cellranger.nf, modules/local/samplesheet/samplesheet_pheno.nf, and subworkflows/local/pseudobulk_phenotype.nf. Tightens nextflow_schema.json to drop `cellranger` from the input_format enum so VALIDATE_PARAMS() rejects it upfront, instead of failing later via an unconverted-columns crash in ANNOTATE_PROCESS. Also drops the now-unused sobject_gex and template_pheno_sc params and their dead withName blocks in conf/modules.config, and updates the README to describe only the airr/adaptive input formats.
Restores tests/main.nf.test to the full 35-sample samplesheet.csv fixture and removes the -resume option, both of which were temporary controller- owned changes for faster iteration during this plan's implementation (tracked in .superpowers/sdd/2026-08-17-bulktcr-analysis-consolidation/ progress.md). Not part of any task's scope. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deletes leftover pre-PR#81 report notebooks (compare_stats_template.qmd, gliph2_report_template.qmd, sample_stats_template.qmd) and the processes that exclusively fed them (COMPARE_PLOT, GLIPH2_PLOT, SAMPLE_PLOT), all confirmed dead: SAMPLE_PLOT was superseded by template_sample.qmd's current reporting, and COMPARE_PLOT/GLIPH2_PLOT lost their only caller when the cellranger pseudobulk-phenotype path was removed earlier. Also removes ANNOTATE_CONCATENATE (wired out of the main ANNOTATE flow in March in favor of Nextflow's own collectFile-based concatenation, then kept alive only by the now-deleted phenotype path) and its backing script, cluster_to_sc.py (superseded by enrich_seurat.R), and an unused sample_meta emit on SAMPLE_CALC. Cleans up every now-orphaned reference: 10 nextflow.config/ nextflow_schema.json params and 4 stale Cirro launch-form fields. Renames bin/test.py to bin/test_tcrdist3_matrix.py to make clear it's a manual, non-CI unit test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…osures,
unused params, single-emit names, and projectDir-in-process
Replaces deprecated `Channel.*` factory calls with lowercase `channel.*`
across 9 files, declares explicit closure parameters in place of implicit
`it` (sc_to_bulk.nf, singlecell.nf), and prefixes unused branch/map closure
parameters with `_` in pseudobulk_qc.nf.
Removes the emit name on three subworkflows with a single output
(COMPARE.shared_cdr3, CONVERT.sample_map_converted, REPORT.rendered_html)
and updates every call site accordingly (bulktcr_analysis.nf, tcrtoolkit.nf).
CONVERT's conditional assignment is restructured using the ternary-in-emit
idiom already established in patient.nf, since its emit couldn't just drop
a name while remaining a plain if/else-assigned variable.
Fixes cluster_to_sc.nf's discouraged `${projectDir}/bin/...` reference by
invoking enrich_seurat.R by bare name instead - it already carries the same
shebang and executable bit as every Python script in bin/, so Nextflow's
automatic bin/-on-PATH staging resolves it exactly like the rest of the
pipeline's scripts, without adding a new process input.
Also drops an unused `def sample_stats_agg =` binding in sample.nf, keeping
the collectFile/storeDir call for its file-publishing side effect.
Warnings: 48 -> 4 (remaining 4 are linter false positives: sample_map/
concat_cdr3 are read via bare `emit:` references, which the linter's
"declared but not used" check doesn't detect as usage).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Distinguishes this Cirro launch profile as the bulk-analysis entry point, ahead of adding single-cell-specific profiles. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…test-data/) Traced to the very first "add nf-test" commit in this repo's history and never referenced by any test or script since - the pipeline's own GLIPH2 step uses the turboGliph R package directly, not this standalone CLI's demo/reference bundle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds .cirro_singlecell_vdjonly/ and .cirro_singlecell_full_gex/, covering the two --mode singlecell routes (VDJ-only and full-SC-with-GEX). Both build sample_sheet.csv the same way: derive each sample's Cell Ranger VDJ outs/ directory from the parent of its per-file S3 path in Cirro's dataset listing (adapted from break-through-cancer/staple's .cirro/preprocess.py, which solves the same file-vs-directory mismatch), falling back to Cirro's per-input directory metadata when there's no flat file listing. Removes .cirro_pseudobulk/, whose only entry point (--input_format cellranger) was deleted from the pipeline in an earlier commit and is now hard-rejected by schema validation. Its capability - analyzing CellRanger- derived pseudobulk data - is superseded by .cirro_singlecell_full_gex's pseudobulk_by_phenotype option, run through the real --mode singlecell pipeline instead of the removed bulk shim. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
algorithm, global_convergence_cutoff, p_depth, and ref_files are leftovers from an earlier Python/CLI-based GLIPH2 invocation, superseded by the R turboGliph package migration - the current gliph2.nf call reads only local_min_pvalue/simulation_depth/kmer_min_depth/local_min_OVE. None of the four is referenced anywhere in the codebase; ref_files pointed at assets/gliph2_files, which doesn't even exist on disk. Also drops the matching stale entries from the Cirro bulk-analysis launch form. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
VDJ_QC hardcoded -P chain_mode="auto", silently ignoring the vdj_chain_mode config param (documented default 'auto', so this was a shadowed no-op rather than a behavior change) - every sibling -P flag in this same call already reads its value from params.vdj_*, this one line was the outlier. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Previously reached into params.local_min_pvalue/simulation_depth/ kmer_min_depth/local_min_OVE directly inside the script block instead of receiving them via input:, following GIANA_CALC's existing call style in this same subworkflow (patient.nf) instead. This also fixes all_aa_interchangeable, which was hardcoded to FALSE and unconfigurable: it's now a real boolean param (default false, preserving current behavior), converted explicitly to R's TRUE/FALSE inside the process since Groovy's boolean stringifies as lowercase true/false, which isn't a valid R symbol. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Consolidates bulk and single-cell TCR analysis into a shared subworkflow while removing obsolete Cell Ranger, plotting, and GLIPH2 code.
Changes:
- Adds shared
BULKTCR_ANALYSISorchestration. - Removes the bulk Cell Ranger pseudobulk route and dead assets.
- Cleans configuration, lint warnings, and adds Cirro single-cell profiles.
Reviewed changes
Copilot reviewed 58 out of 72 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
workflows/tcrtoolkit.nf |
Uses the shared bulk analysis engine. |
workflows/singlecell.nf |
Routes pseudobulk data through the shared engine. |
tests/test_data/gliph2-test-data/test_data/ref_V_CD8.txt |
Removes unused GLIPH2 fixture. |
tests/test_data/gliph2-test-data/test_data/ref_V_CD48.txt |
Removes unused GLIPH2 fixture. |
tests/test_data/gliph2-test-data/test_data/ref_V_CD4.txt |
Removes unused GLIPH2 fixture. |
tests/test_data/gliph2-test-data/test_data/ref_L_CD8.txt |
Removes unused GLIPH2 fixture. |
tests/test_data/gliph2-test-data/test_data/ref_L_CD48.txt |
Removes unused GLIPH2 fixture. |
tests/test_data/gliph2-test-data/test_data/ref_L_CD4.txt |
Removes unused GLIPH2 fixture. |
tests/test_data/gliph2-test-data/test_data/demo.cfg |
Removes unused GLIPH2 configuration. |
tests/test_data/gliph2-test-data/test_data/demo_hla.txt |
Removes unused GLIPH2 HLA data. |
tests/main.nf.test |
Tests rejection of bulk Cell Ranger input. |
subworkflows/scratch/vdj_qc.nf |
Updates channel factory syntax. |
subworkflows/scratch/tcri.nf |
Updates channel factory syntax. |
subworkflows/scratch/tcell_integration.nf |
Updates channel factory syntax. |
subworkflows/scratch/repertoire.nf |
Updates channel factory syntax. |
subworkflows/scratch/master_summary.nf |
Updates channel factory syntax. |
subworkflows/scratch/consensus_clustering.nf |
Updates channel factory syntax. |
subworkflows/scratch/conga.nf |
Updates channel factory syntax. |
subworkflows/local/sample.nf |
Removes obsolete sample plotting. |
subworkflows/local/report.nf |
Simplifies the report output. |
subworkflows/local/pseudobulk_qc.nf |
Applies lint and naming updates. |
subworkflows/local/pseudobulk_phenotype.nf |
Removes obsolete phenotype workflow. |
subworkflows/local/patient.nf |
Passes GLIPH2 tuning parameters explicitly. |
subworkflows/local/convert.nf |
Removes Cell Ranger conversion routes. |
subworkflows/local/compare.nf |
Simplifies the comparison output. |
subworkflows/local/bulktcr_analysis.nf |
Adds shared analysis and reporting orchestration. |
subworkflows/local/annotate.nf |
Separates ingestion from shared annotation. |
subworkflows/bridges/sc_to_bulk.nf |
Makes closure parameters explicit. |
README.md |
Documents supported bulk input formats. |
notebooks/sample_stats_template.qmd |
Removes superseded sample report. |
notebooks/gliph2_report_template.qmd |
Removes obsolete GLIPH2 report. |
notebooks/compare_stats_template.qmd |
Removes obsolete comparison report. |
nextflow.config |
Removes dead parameters and exposes active options. |
nextflow_schema.json |
Tightens input and parameter validation. |
modules/scratch/VDJ_QC/main.nf |
Honors configured VDJ chain mode. |
modules/local/samplesheet/samplesheet_pheno.nf |
Removes obsolete phenotype samplesheet process. |
modules/local/sample/sample_plot.nf |
Removes obsolete sample plotting process. |
modules/local/sample/sample_calc.nf |
Removes an unused output. |
modules/local/report/render_notebook.nf |
Updates bulk phenotype documentation. |
modules/local/convert/pseudobulk_phenotype_cellranger.nf |
Removes phenotype Cell Ranger conversion. |
modules/local/convert/pseudobulk_cellranger.nf |
Removes Cell Ranger pseudobulking. |
modules/local/compare/gliph2.nf |
Adds explicit GLIPH2 inputs and removes plotting. |
modules/local/compare/compare_plot.nf |
Removes obsolete comparison plotting. |
modules/local/compare/compare_calc.nf |
Removes unused comparison calculations. |
modules/local/annotate/main.nf |
Removes orphaned concatenation process. |
modules/bridges/cluster_to_sc.nf |
Invokes the bundled enrichment script via PATH. |
conf/modules.config |
Removes obsolete phenotype publishing rules. |
bin/test_tcrdist3_matrix.py |
Clarifies the renamed manual test. |
bin/pseudobulk.py |
Removes obsolete Cell Ranger implementation. |
bin/create_pheno_samplesheet.py |
Removes obsolete helper script. |
bin/compare_concatenate.py |
Removes unused concatenation script. |
bin/compare_calc.py |
Removes unused comparison script. |
bin/cluster_to_sc.py |
Removes superseded cluster bridge. |
.cirro_singlecell_vdjonly/process-input.json |
Adds VDJ-only Cirro inputs. |
.cirro_singlecell_vdjonly/process-form.json |
Adds the VDJ-only launch form. |
.cirro_singlecell_vdjonly/process-compute.config |
Adds VDJ-only compute configuration. |
.cirro_singlecell_vdjonly/preprocess.py |
Generates VDJ-only samplesheets. |
.cirro_singlecell_full_gex/process-input.json |
Adds full single-cell Cirro inputs. |
.cirro_singlecell_full_gex/process-form.json |
Adds the full single-cell launch form. |
.cirro_singlecell_full_gex/process-compute.config |
Adds full single-cell compute configuration. |
.cirro_singlecell_full_gex/preprocess.py |
Generates full single-cell samplesheets. |
.cirro_pseudobulk/process-input.json |
Removes obsolete pseudobulk profile inputs. |
.cirro_pseudobulk/process-form.json |
Removes obsolete pseudobulk form. |
.cirro_pseudobulk/preprocess.py |
Removes obsolete pseudobulk preprocessing. |
.cirro_bulk_analysis/process-input.json |
Removes dead bulk launch parameters. |
.cirro_bulk_analysis/process-form.json |
Removes dead bulk form controls. |
.cirro_bulk_analysis/process-compute.config |
Retains bulk compute configuration. |
.cirro_bulk_analysis/preprocess.py |
Defines bulk Cirro preprocessing. |
Suppressed comments (2)
.cirro_singlecell_vdjonly/preprocess.py:44
- The fallback does not run when
ds.filesis empty butds.samplesheethas rows:samplesheet_from_filesperforms a left merge, which preserves those rows and leavespathasNaN, sosample_sheet.emptyis false. Check for an all-nullpathcolumn (or checkds.files.emptybefore merging) so directory-shaped datasets actually usesamplesheet_from_params.
sample_sheet = samplesheet_from_files(ds)
if sample_sheet.empty:
ds.logger.warning("No files found in dataset. Preparing sample_sheet from params.")
sample_sheet = samplesheet_from_params(ds)
.cirro_singlecell_full_gex/preprocess.py:44
- The fallback does not run when
ds.filesis empty butds.samplesheethas rows:samplesheet_from_filesperforms a left merge, which preserves those rows and leavespathasNaN, sosample_sheet.emptyis false. Check for an all-nullpathcolumn (or checkds.files.emptybefore merging) so directory-shaped datasets actually usesamplesheet_from_params.
sample_sheet = samplesheet_from_files(ds)
if sample_sheet.empty:
ds.logger.warning("No files found in dataset. Preparing sample_sheet from params.")
sample_sheet = samplesheet_from_params(ds)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (run_reports) { | ||
| ch_reports = channel.empty() | ||
|
|
||
| def sample_stats_agg = SAMPLE.out.sample_csv | ||
| .collectFile(name: "sample_stats.csv", keepHeader: true, skip: 1, sort: true) |
There was a problem hiding this comment.
Confirmed real and reachable (e.g. --workflow_level patient alone in bulk mode). Verified this is pre-existing behavior carried over byte-identical from the old tcrtoolkit.nf, not introduced by this PR. Deliberately not fixed here — tracked as item #5.1 in the outstanding-work list alongside related workflow_level validation gaps (unvalidated values, silent-success misconfiguration) so it can be fixed as one coherent change rather than a partial patch. Leaving this thread open pending that follow-up.
| # Cirro's per-file listing gives one row per file under each sample's | ||
| # directory; the sample's directory is that file's parent. Path() | ||
| # collapses "s3://" to "s3:/", so restore the double slash afterward. | ||
| files['path'] = files['file'].apply(lambda x: str(Path(x).parent).replace('s3:/', 's3://')) | ||
| files = files[['sample', 'path']].drop_duplicates() | ||
|
|
||
| return pd.merge(ds.samplesheet, files, on='sample', how='left') |
There was a problem hiding this comment.
Fixed in f5174ce: added contigs_files/clonotypes_files/metrics_files params (comma-separated, sample_sheet-ordered file lists), a new bin/stage_vdj_files.py helper, and wired both through VDJ_QC/vdj_qc.nf so per-sample files are staged individually instead of relying on a bare directory string. This file has since moved to .cirro/singlecell_vdjonly/preprocess.py as part of the Cirro-profile consolidation (0cb49e3).
| # Cirro's per-file listing gives one row per file under each sample's | ||
| # directory; the sample's directory is that file's parent. Path() | ||
| # collapses "s3://" to "s3:/", so restore the double slash afterward. | ||
| files['path'] = files['file'].apply(lambda x: str(Path(x).parent).replace('s3:/', 's3://')) | ||
| files = files[['sample', 'path']].drop_duplicates() | ||
|
|
||
| return pd.merge(ds.samplesheet, files, on='sample', how='left') |
There was a problem hiding this comment.
Fixed in f5174ce: added contigs_files/clonotypes_files/metrics_files params (comma-separated, sample_sheet-ordered file lists), a new bin/stage_vdj_files.py helper, and wired both through VDJ_QC/vdj_qc.nf so per-sample files are staged individually instead of relying on a bare directory string. This file has since moved to .cirro/singlecell_full_gex/preprocess.py as part of the Cirro-profile consolidation (0cb49e3).
Moves .cirro_bulk_analysis/, .cirro_convert_adaptive/, .cirro_singlecell_vdjonly/, and .cirro_singlecell_full_gex/ into named subdirectories under one .cirro/, matching the pattern WangLab-ComputationalBiology/SCRATCH-QC uses for its own multiple Cirro entry points (.cirro/align/, .cirro/clustering/, .cirro/qc/). Pure rename - no content changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
VDJ_QC read raw path strings from sample_sheet.csv and did a literal
dir.exists() filesystem check - correct for local/on-prem runs (bind-mounted
directories), but silently wrong against Cirro's S3-backed datasets: Nextflow
never staged those directories since they were never declared as process
inputs, and .cirro_singlecell_*'s preprocess.py wrote raw s3://... strings
into the CSV. Both bugs were caught in Copilot's PR review; fixed by adapting
WangLab-ComputationalBiology/SCRATCH-QC's approach (a sibling pipeline
solving the identical problem) of resolving specific files via Cirro's
per-file dataset listing rather than trying to reference whole directories.
- .cirro/singlecell_{vdjonly,full_gex}/preprocess.py: find each sample's
filtered_contig_annotations.csv / clonotypes.csv / metrics_summary.csv via
ds.files and expose them as sample-ordered params. Also fixes a related
bug: the "no per-file listing" fallback checked sample_sheet.empty, but a
left-merge against an empty ds.files still returns every row (with `path`
as NaN), so the fallback never triggered - now checks the path column
directly.
- bin/stage_vdj_files.py (new): reconstructs a local outs/ directory per
sample from Nextflow-staged files and rewrites sample_sheet's path column;
untouched (local-run) rows are left exactly as-is.
- modules/scratch/VDJ_QC/main.nf: 3 new optional staged-file inputs
(contigs_files/clonotypes_files/metrics_files) + the rewrite step.
VDJ_QC_analysis.qmd itself is unchanged - zero risk to local execution.
- subworkflows/scratch/vdj_qc.nf: builds the file lists from the new params,
using the existing NO_FILE sentinel and split(',', -1) to preserve
positional alignment with sample_sheet's rows even when a file is missing
for one sample.
- nextflow.config / nextflow_schema.json: declare the 3 new params.
Verified: full nf-test suite passes, including the VDJ_QC-exercising
single-cell test (confirmed VDJ_QC actually executed, not cached). The
S3-staging mechanism itself is still unverified against a real Cirro
dataset - no infrastructure available to test that end-to-end from here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
workflows/tcrtoolkit.nf -> workflows/tcrtoolkit_bulk.nf (workflow TCRTOOLKIT -> TCRTOOLKIT_BULK), workflows/singlecell.nf -> workflows/tcrtoolkit_sc.nf (workflow SINGLECELL_WORKFLOW -> TCRTOOLKIT_SC). Gives both top-level workflows a consistent, self-describing naming scheme. main.nf's mode dispatch updated to match. Also fixes 5 stale prose references to the old names left behind in comments/docstrings (subworkflows/local/patient.nf, annotate.nf, bulktcr_analysis.nf, tests/main.nf.test) and a user-facing println log message in tcrtoolkit_bulk.nf that still said "Running TCRTOOLKIT workflow...". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The process gained 3 inputs in f5174ce (Cirro S3 file-staging fix) but this module-level test still called it with the old 4-argument signature, breaking CI ("Process VDJ_QC declares 7 inputs but was called with 4 arguments").
Summary
BULKTCR_ANALYSISsubworkflow (ANNOTATE+ gatedSAMPLE/PATIENT/COMPARE+ gated report rendering) that bothTCRTOOLKIT_BULK(bulk) andTCRTOOLKIT_SC(single-cell) now call, replacing duplicated call-graph wiring that previously lived separately in each pipeline.--input_format cellrangercode path entirely (redundant with--mode singlecell's existingpseudobulk_by_phenotype), tighteningnextflow_schema.jsonso it's rejected upfront byVALIDATE_PARAMS().COMPARE_PLOT,GLIPH2_PLOT,SAMPLE_PLOT), an orphaned concatenation process (ANNOTATE_CONCATENATE) and its script, plus every now-unreferenced config param (10 innextflow.config/nextflow_schema.json, 4 in Cirro launch-form config).nextflow lintwarnings (48 → 4; the remaining 4 are confirmed linter false positives).nextflow.config/nextflow_schema.jsonparam audit): removes dead legacy GLIPH2-CLI params and an unused GLIPH2 CLI test fixture, fixes two "shadowed" params that looked configurable but were silently hardcoded, and adds Cirro launch profiles for both single-cell routes.TCRTOOLKIT→TCRTOOLKIT_BULK(workflows/tcrtoolkit.nf→workflows/tcrtoolkit_bulk.nf),SINGLECELL_WORKFLOW→TCRTOOLKIT_SC(workflows/singlecell.nf→workflows/tcrtoolkit_sc.nf)..cirro/root (.cirro/bulk_analysis/,.cirro/convert_adaptive/,.cirro/singlecell_vdjonly/,.cirro/singlecell_full_gex/), matching the convention used by a sibling BTC pipeline.WangLab-ComputationalBiology/SCRATCH-QC's pattern; the coreVDJ_QC_analysis.qmdnotebook is untouched, so on-prem/local runs are unaffected).Details
Consolidation (4 tasks, each independently reviewed):
ANNOTATE_FROM_CONCAT→ANNOTATE(now the universal shared step) and oldANNOTATE→ANNOTATE_INGEST(bulk-only raw-sample ingestion) — pure rename, verified byte-identical bodies.subworkflows/local/bulktcr_analysis.nf, wireTCRTOOLKIT_BULKto it, remove the cellranger branch fromconvert.nf.TCRTOOLKIT_SCto the sameBULKTCR_ANALYSISengine (run_reports: false— SC has its ownREPERTOIRE/MASTER_SUMMARYreports).input_format's schema enum, clean config/README.Dead-code cleanup (found by auditing every process/subworkflow for real callers, not just imports):
compare_calc.py/COMPARE_CALC,compare_concatenate.py/ANNOTATE_CONCATENATE,cluster_to_sc.py(superseded byenrich_seurat.R) — all confirmed zero live callers.COMPARE_PLOT,GLIPH2_PLOT,SAMPLE_PLOTand their notebooks —SAMPLE_PLOTwas superseded bytemplate_sample.qmd's current reporting; the other two lost their only caller when the cellranger pseudobulk-phenotype path was removed.sample_metaemit onSAMPLE_CALC.bin/test.py→bin/test_tcrdist3_matrix.py(it's a manual, non-CI Python unit test, not a stray file).Lint fixes:
Channel.*→channel.*(9 files), implicititclosures → explicit params, unused closure params prefixed with_.COMPARE,CONVERT,REPORT) and updated every call site;CONVERTwas restructured onto the ternary-in-emit idiom already established inpatient.nf.cluster_to_sc.nf's discouraged${projectDir}/bin/...reference —enrich_seurat.Ralready has the same shebang/executable bit as every Python script inbin/, so it's now invoked by bare name like the rest of the pipeline's scripts.Config/param audit follow-up (full sweep of every
.configfile's params against actual usage):algorithm,global_convergence_cutoff,p_depth,ref_files— leftovers from a pre-turboGliphGLIPH2 CLI invocation, referenced nowhere in the codebase (ref_filespointed at anassets/gliph2_filesdirectory that doesn't exist on disk).vdj_chain_mode:VDJ_QChardcoded-P chain_mode="auto", silently ignoring the config param entirely (matching default value, so behavior is unchanged — this was a shadowed no-op, not a live bug).all_aa_interchangeable: was hardcodedFALSEin the R call and unconfigurable; now a real boolean param (defaultfalse, preserving current behavior), converted explicitly to R'sTRUE/FALSEsince Groovy's boolean stringifies lowercase, which isn't valid R.GLIPH2_TURBOGLIPH's 5 tuning params from directparams.*reads inside its script block to declared process inputs, matchingGIANA_CALC's existing call style in the same subworkflow (patient.nf).tests/test_data/gliph2-test-data/— a GLIPH2 CLI reference/demo bundle traced to this repo's very first "add nf-test" commit, never referenced by any test or script since..cirro/→.cirro_bulk_analysis/(it's the bulk entry point) and removed.cirro_pseudobulk/(its only route,--input_format cellranger, is now schema-rejected). Added.cirro_singlecell_vdjonly/and.cirro_singlecell_full_gex/Cirro launch profiles for the two--mode singlecellroutes, adapted from break-through-cancer/staple's approach to deriving per-sample directory paths from Cirro's file listing.Workflow rename:
workflows/tcrtoolkit.nf→workflows/tcrtoolkit_bulk.nf, workflowTCRTOOLKIT→TCRTOOLKIT_BULK.workflows/singlecell.nf→workflows/tcrtoolkit_sc.nf, workflowSINGLECELL_WORKFLOW→TCRTOOLKIT_SC.main.nf's mode dispatch updated to match; every prose reference to the old names in comments/docstrings (subworkflows/local/patient.nf,subworkflows/local/annotate.nf,subworkflows/local/bulktcr_analysis.nf,tests/main.nf.test) updated, plus a user-facingprintlnlog message intcrtoolkit_bulk.nfthat still said "Running TCRTOOLKIT workflow...".Cirro consolidation + automated-review fixes (final round):
.cirro_*directories into one.cirro/root with named subdirectories (bulk_analysis,convert_adaptive,singlecell_vdjonly,singlecell_full_gex), matchingWangLab-ComputationalBiology/SCRATCH-QC's convention. Pure rename, git-tracked as renames.preprocess.py's samplesheet-fallback check in both single-cell Cirro profiles:sample_sheet.emptynever caught the case whereds.filesis empty but the left-merge againstds.samplesheetstill returns every row (just withpathasNaN) — now checkssample_sheet.empty or sample_sheet['path'].isna().all().outs/directory referenced only as a string inside a CSV. Added 3 new optional params (contigs_files,clonotypes_files,metrics_files, comma-separated and sample_sheet-ordered), a newbin/stage_vdj_files.pyhelper that reconstructs localouts/-shaped directories from Nextflow-staged files, and wired both intoVDJ_QC/vdj_qc.nf.VDJ_QC_analysis.qmditself is untouched — zero risk to local/on-prem execution, which never sets these params.Intentionally left alone (flagged, not fixed):
TCRI_SW— a fully scaffolded but never-wired single-cell feature (module + subworkflow + 65KB report notebook +run_tcri=truedefault), added alongside the SC integration but never connected toworkflows/tcrtoolkit_sc.nf.d_family.csv— computed and published bySAMPLE_CALC_PIVOTbut never read back downstream.notebooks/template_pheno_sc.qmd— orphaned; its data-loading logic doesn't match single-cell's current--by-phenotypeoutput shape (needs a design decision, not a mechanical fix).alias,origin,timepoint,patient) are actually treated as required by most bulk report notebooks and crash with unhandled Python errors if omitted (GH notebooks/template_qc.qmd fails without 'alias' column in samplesheet.csv #94 is one instance of this; the audit found the same pattern extends well beyondaliasalone, including on the defaultsample,compareworkflow_level). Single-cell's equivalent metadata columns are robustly guarded. Not addressed in this PR — needs a product decision on the right fix (require vs. gate-per-block vs. drop unused references) before implementation.Test plan
nf-test test tests/main.nf.test --verbose— all 3 cases pass against the full 35-sample fixture: "Minimal example dataset", "Single-cell VDJ-only minimal example", "Bulk mode rejects cellranger input_format" (now rejected upfront by schema validation).nf-test test tests/modules/— 17/17 pass, unaffected by deleted/rewired files.nextflow lint .— 48 → 4 warnings (remaining 4 confirmed false positives).nf-testcases pass after the config/param audit follow-up commits (6-sample fixture), including the patient-levelGIANA/GLIPH2_TURBOGLIPHpath exercising the new input-block wiring.nf-testcases pass after theTCRTOOLKIT_BULK/TCRTOOLKIT_SCworkflow rename (6-sample fixture).nextflow lint .re-run clean after theVDJ_QC/vdj_qc.nfstaging changes (caught and fixed one new strict-syntax error: a workflow-local closure-in-defisn't callable in this Nextflow version, same workaround already used forenabled()intcrtoolkit_sc.nf).nf-testcases pass after the Cirro consolidation + S3-staging-fix commits (6-sample fixture) — the VDJ-only single-cell test exercisesVDJ_QC's new staging inputs with the params left unset (local-run path), confirming no regression to the existing behavior.CLUSTER_TO_SC_SW/enrich_seurat.Rfix is unverified by nf-test — no existing test case exercises the full-SC/GEX route (only VDJ-only is covered). Recommend a manual full-SC run before merge if GEX test data is available.🤖 Generated with Claude Code