Chronological log of work sessions on this project. Newest entries at the bottom. Append-only — never edit or reorder past entries.
Entry format:
- What was done / tried
- What happened (concrete results, errors, numbers — not a diff restatement)
- Decision or next step
- Repo created today (
hurwitzlab/nextflow_modules, scaffolded withREADME.md,LICENSE,.gitignore) as the lab's single shared home for Nextflow tool modules, so every pipeline reuses the same module files instead of each keeping its own copy. - Moved (not copied) all 9 existing
.nfmodule files plusCLAUDE.mdout ofviral_inference_benchmark/nextflow_pipeline/modules/intomodules/here:bbmap.nf,checkv.nf,deepvirfinder.nf,genomad.nf,marvel.nf,samtools.nf,vibrant.nf,virsorter2.nf, andfilter_viral_genomad.nf(still WIP — empty shell blocks). Updated everyincludeinviral_inference_benchmarkto the new relative path. - Added
modules/TEMPLATE.nf, a fill-in-the-blank module skeleton matching every rule inCLAUDE.md. GeneralizedCLAUDE.mditself: it no longer assumes one specific pipeline's directory/config layout (label-first-then-container convention, and modules treated as a contract ofparams.*names the consuming pipeline must define, not something modules own). - Wrote
README.md: the sibling-clone requirement for consuming pipelines, whyincludepaths can't be config-driven (Nextflow resolves them as compile-time literals), a pinning caveat (clone tracksmainunless you deliberatelygit checkouta tag/commit), theparams.*contract, a module table, and how to add a new module. - Separately, another operator ran a large legacy-tool migration into
modules/dev/today (~87 files converted from/Users/blhurwit/work/brc/nf/tools, a staging step with nothing promoted tomodules/pending human review). That operator's record originally lived in its ownLAB_NOTEBOOK.mdat the repo root; folding it in below (headers demoted one level) since a repo gets one chronological notebook, not two —LAB_NOTEBOOK.mditself has been deleted.
Operator: Claude Code, on behalf of blhurwit@ncsu.edu
Migrate legacy Nextflow process definitions from /Users/blhurwit/work/brc/nf/tools
(the old, per-pipeline "tools" directory, LSF/ad hoc-config era) into the shared
nextflow_modules repo's module conventions, as a staging step before promotion
out of modules/dev. The source directory was not modified — every file
below was read-only in nf/tools and copied+rewritten into
nextflow_modules/modules/dev/.
nf/tools contains 107 top-level .nf files (excluding backup/ and template
files). An agent-assisted read of every file classified each as:
- TOOL — a
processblock that thinly wraps a real, independently-named bioinformatics CLI tool (e.g.blastn,samtools,checkm2). - CUSTOM — a bespoke, project-specific glue/report/analysis script with no independent tool identity (e.g. QC aggregators, report generators, ad hoc Biopython scripts).
- MIXED — a file containing both TOOL and CUSTOM processes side by side.
- UNCLEAR/placeholder — files with a container constant but no
processblock at all.
| Verdict | Count | Disposition |
|---|---|---|
| TOOL | 80 | Migrated |
| MIXED | 7 | Migrated (all processes, per decision below) |
| CUSTOM | 17 | Skipped — not a bioinformatics tool wrapper |
| UNCLEAR/placeholder | 3 | Skipped — no process block to convert |
87 files were migrated (TOOL + MIXED). Decisions confirmed with the user before any editing began:
- Scope = TOOL + MIXED (not TOOL-only).
- For MIXED files, convert every process in the file (both the tool-wrapping ones and the custom glue ones living alongside them), per the target repo's "one file per tool, multiple processes" convention.
- Do the full migration in one pass (no batch-by-batch checkpoints), using parallel subagents to manage the volume.
- This notebook lives at the repo root.
ampliconcounting.nf, ampsaexp.nf, ani_analysis.nf, best_genome.nf,
biopython.nf, clustervariants.nf, failurereport.nf, fbesm.nf,
flowcellqcsummary.nf, kmercounting.nf, qc_threshold.nf, qcsummary.nf,
rearrange.nf, scaffoldmetrics.nf, sha256.nf, stockpurityqc.nf,
variantcaller.nf
ncbi-blast.nf, phageterm.nf, smrtlink.nf
Full rules: modules/CLAUDE.md. Summary of the mechanical
transforms applied to every migrated file:
- Shebang fixed to
#!/usr/bin/env nextflow(source files often carried a garbled-e #!/usr/local/apps/nextflow/nextflow). nextflow.enable.dsl=2line removed (belongs in the consuming pipeline, not the module).- One-line
//plain-English comment added above everyprocessblock. - Process names normalized to lowercase
snake_case. label "process_<tier>"added as the first line in every process block, sized to the tool's actual cpu/memory footprint from the source file.container "${params.container__<tool>}"replaces the hardcodedCONTAINER = "..."constant.publishDir "${params.<tool>_outdir}", mode: 'copy'added for terminal/final outputs only; omitted for purely intermediate steps.- Inputs converted to
tuple val(sampleid), path(...)for per-sample data; reference databases/shared resources kept as separatepath(...)inputs; genericstageAs:-renamed inputs given descriptive variable names instead. - Every output given an explicit, descriptive
emit:name;sampleidpreserved through per-sample output tuples. script:+"""..."""(${...}interpolation) converted toshell:+'''...'''(!{...}interpolation); backslash-escaped bash locals (\$VAR) un-escaped since shell blocks don't need it.- Thread counts switched to
!{task.cpus}throughout. - No
#comments left on backslash-continued shell lines. - Brace balance verified on every process block.
- Source processes:
call_somatic_variants,filter_somatic_variants,annotate_vcf_with_bam_depth,compare_references(4 processes) - Converted processes: same names (already snake_case)
- Label(s):
process_singlefor all 4 (source usedcpus=1) - Container param:
container__gatk - publishDir:
filter_somatic_variants,annotate_vcf_with_bam_depth,compare_references— not oncall_somatic_variants(feeds the filter step) - Outputs:
variant_calls,filtered_variant_calls,depth_annotated_variants,comparison_stats,comparison_variants,comparison_variants_index - Params referenced:
container__gatk,gatk_outdir - Notes: Kept
stageAs: "ref.fasta"/"refcomp.fasta"incompare_referencesonly, because GATKCompareReferencesderives its output VCF name from the two input basenames — a deliberate exception to the "no stageAs" default. Needs human review: whetherfilter_somatic_variants/annotate_vcf_with_bam_depthare both genuinely meant to bepublishDirterminal points is a pipeline-semantics call the agent made without full context.
- Source processes:
amrfinder(1) - Label:
process_single - Container param:
container__amrfinder - publishDir: yes (
resistance_genes) - Params referenced:
container__amrfinder,amrfinder_outdir,amrfinder_organism - Notes: Behavior change flagged — promoted the hardcoded
-O Escherichiaflag toparams.amrfinder_organism. Consuming pipeline config must now set this param (e.g. to"Escherichia") to preserve old behavior, or the process will fail on an undefined param.
- Source processes:
annotate_bacterial(1, from the OLD source — different/more outputs than the pre-existingmodules/bakta.nf) - Label:
process_medium(borderline — 8 cpus/30GB mem,process_highalso defensible) - Container param:
container__bakta - publishDir: yes, all 13 emits
- Outputs:
annotation_table,annotation_gff3,annotation_genbank,annotation_embl,annotated_contigs,annotated_genes,annotated_proteins,hypothetical_proteins_table,hypothetical_proteins,summary,summary_plot_png,summary_plot_svg,annotation_json - Params referenced:
container__bakta,bakta_outdir - Notes: Intentional behavior addition — added
--prefix !{sampleid}(a real Bakta flag) so per-sample output naming stays deterministic once the input is a naturalpath(contigs)instead of a fixedstageAsfilename; not present in the original script. The pre-existingmodules/bakta.nfuses a different, more collapsed I/O shape (single output dir) — this dev/ version intentionally preserves the OLD source's full 13-output granularity instead of matching it; a human should decide whether to reconcile the two eventually.
- Source processes:
visualize_assembly(1) - Label:
process_single - Container param:
container__bandage - publishDir: yes (
assembly_graph_image) - Notes: Source output was unnamed; added
emit: assembly_graph_imageand renamed file to${sampleid}.svg.
- Source processes:
merge_fastqs(1) - Label:
process_single - Container param:
container__bbmerge(note: source's hardcoded image tag was actuallybbnorm:20201228_235829, shared with the bbnorm package — param still follows the file/tool name per rule 6) - publishDir: yes, all 5 emits
- Outputs:
merged_reads,unmerged_r1,unmerged_r2,detected_adapters,log - Params referenced:
container__bbmerge,bbmerge_outdir - Notes: All output filenames given a
${sampleid}.prefix to preserve sample association.
- Source processes:
downsample_paired_end_bacterial,downsample_paired_end_phage,downsample_paired_end(3) - Label:
process_highfor all 3 (source: cpus=16, mem=24GB fixed) - Container param:
container__bbnorm - publishDir: yes, all 3 (independent alternatives, not intermediates)
- Outputs:
r1_normalized,r2_normalized(per process) - Params referenced:
container__bbnorm,bbnorm_outdir,bbnorm_kmer_cov_bacterial,bbnorm_min_depth_bacterial,bbnorm_kmer_cov_phage,bbnorm_min_depth_phage,bbnorm_target - Notes: New params required — promoted 5 previously-hardcoded Groovy constants (
KMER_COV_BACTERIAL,MIN_DEPTH_BACTERIAL,KMER_COV_PHAGE,MIN_DEPTH_PHAGE,KMERX) toparams.bbnorm_*; consuming pipeline config must now define all 5 (previously not configurable at all). Also replaced the hardcodedMAXMEMheap constant with a dynamicMath.round(task.memory.toGiga() * 0.83)expression tied to actual task memory allocation, mirroringsamtools.nf's pattern.
- Source processes:
demux,demux_samplesheet(2) - Label:
process_highfor both (source: cpus=32, mem=64GB) - Container param:
container__bcl2fastq - publishDir: yes, both (standalone terminal runs)
- Outputs:
demuxed_reads(both) - Params referenced:
container__bcl2fastq,bcl2fastq_outdir - Notes: No natural sampleid (demultiplexes an entire flowcell into many samples at once) — kept plain
path()inputs, no tuple. Dropped source's redundanttype: "dir"output qualifier (style-only,pathauto-detects directories).
- Source processes:
generate_asvs(1) - Label:
process_low - Container param:
container__mothur - publishDir: yes (
taxonomy,count_table,log_file) - Params referenced:
container__mothur,mothur_outdir - Notes: Kept
stageAsfor reads and both reference DBs because the process shells out to an opaquerun_mothur.shthat (per the source's own comment) checks the environment/expects files at fixed locations — could not inspect that script to confirm; flagging for review.
- Source processes:
mafft_linsi(1) - Label:
process_low - Container param:
container__mafft - publishDir: yes (
aligned_fasta) - Notes: No natural sampleid — MAFFT aligns an arbitrary multi-sequence fasta; kept plain
path()input. Human should confirm this pipeline's actual usage isn't per-sample.
- Source processes:
binContigs,binContigsSingleEnd(2) → renamedmaxbin2_bin_contigs,maxbin2_bin_contigs_single_end - Label:
process_single(both) - Container param:
container__maxbin2 - publishDir: yes, both (
fasta_bins,bin_manifest,summary,log_file) - Notes: Renamed away from generic
binContigsto avoid colliding withmetadecoder.nf's process of the same source name. No thread flag invented (source never passed one torun_MaxBin.pl, even though the tool supports-thread— adding it would be a behavior change beyond the source, left alone).
- Source processes:
medaka_polish_assembly(1) - Label:
process_high - Container param:
container__medaka - publishDir: yes (
polished_assembly,results) - Params referenced:
container__medaka,medaka_outdir,medaka_basecall_model - Notes: New param required — promoted hardcoded
BASECALL_MODEL = "r941_min_sup_g507"toparams.medaka_basecall_model(source's own comment says this varies by pore/basecaller version); consuming pipeline must now set it to preserve old behavior. Also changed output dir from fixedmedaka_outto!{sampleid}— deliberate deviation from literal translation, low risk but worth a second look.
- Source processes:
assemble,assemble_single_end(2, from the OLD source — different from the pre-existingmodules/megahit.nf) → renamedmegahit_paired_end,megahit_single_end - Label:
process_high(both) - Container param:
container__megahit(reused existing param name for consistency withmodules/megahit.nf) - publishDir: yes, both (
contigs,log_file,options) - Notes: Kept this source's own hardcoded flags (
--min-contig-len 200,--min-count 2,--low-local-ratio 0.2,--no-mercy) literal rather than reconciling with the different flag set (--presets) used in the pre-existingmodules/megahit.nf— the two megahit modules are intentionally not option-identical; a human should decide whether to reconcile them. Kept defaultmegahit_outoutput-dir name (did not switch to per-sample dir naming, unlike the medaka change above) — flagged as an inconsistency worth aligning later.
- Source processes:
bin(1) → renamedmetabat2_bin - Label:
process_medium - Container param:
container__metabat2 - publishDir: yes (
fasta_bins,bin_manifest) - Notes: Kept
stageAs: "assembly.fasta"deliberately — the script greps for the literal stringassembly.fasta.metabat(MetaBAT2 derives its output dir name from the actual input filename); without the fixed stage name this would silently break.
- Source processes:
binContigs(1, name collided with maxbin2.nf's source process) → renamedmetadecoder_bin_contigs - Label:
process_medium - Container param:
container__metadecoder - publishDir: yes (
fasta_bins,bin_manifest,coverage,seed,dpgmm,kmers) - Notes: Kept
stageAs: "assembly.fasta"for the same reason as metabat2.nf — MetaDecoder's own output filenames (assembly.fasta.2500.metadecoder.dpgmm/.kmers) are derived from the input basename.
- Source processes:
add_uncalled_variants,normalize_variants,add_variants_to_fasta,filter_variants,set_genotype,concat_vcfs(6) - Label:
process_single(all 6) - Container param:
container__bcftools - publishDir:
add_variants_to_fasta(edited_genome),concat_vcfs(final_variants/final_variants_tabix) - Outputs:
merged_variants(+tabix),normalized_variants(+tabix),edited_genome,filtered_variants(+tabix),regenotyped_variants(+tabix),final_variants(+tabix) - Notes: No natural sampleid anywhere — this reads as a single genome-editing workflow (compare called vs. expected variants, normalize/filter, concat). Human should double-check bcftools/tabix
.tbiname-matching still holds sincestageAsfixed names were dropped in favor of natural variable names — verify any consuming pipeline supplies VCF/.tbipairs with matching basenames. publishDir placement on 2 of 6 steps is a judgment call.
- Source processes:
bam_to_fastq,get_fasta(2) - Label:
process_single(both) - Container param:
container__bedtools - publishDir: none — both intermediate
- Notes:
get_fasta's sampleid-ness is ambiguous (looks like it may belong to the same CRISPR-editing workflow as bcftools.nf) — kept as plainpathinputs; flagged for human confirmation.
- Source processes:
estimate_abundance(1) → renamedbracken(matches the already-establishedmodules/bracken.nf) - Label:
process_high(mem 32GB despite cpus=1) - Container param:
container__bracken - publishDir: yes (
bracken_stats,bracken_kreport) - Notes: Kept
kreportstaged under a fixed name becauseest_abundance.pyderives a secondary output filename from the input report's basename. Added an explicitmvafterward to rename the per-sample output so it doesn't collide with other samples under a flatpublishDir— a deliberate behavior-preserving addition worth double-checking.
- Source processes:
busco_assess(1) - Label:
process_high - Container param:
container__busco - publishDir: yes (
json_report,txt_report,busco_results) - Notes: Changed
-ofrom fixedbusco_outto!{sampleid}_busco_out, which changes BUSCO's derived summary filename accordingly (reflected in emit paths) — human should verify this naming pattern matches the actual BUSCO output convention in the pinned container version.bacteria_odb10lineage left hardcoded (baked into both command and expected output filename).
- Source processes:
align,align_single_end,align_to_index(3, from OLD source — materially different from the pre-existingmodules/bwa.nf, which only hasbwa_index+bwa_memon an already-indexed reference) → renamedbwa_align,bwa_align_single_end,bwa_align_to_index - Label:
process_high(all 3) - Container param:
container__bwa(kept consistent with existing module) - publishDir: none — all three intermediate
.samfeeding a downstream samtools step - Notes: This source re-indexes the reference inline on every call and supports single-end/pre-built-index variants that the existing
modules/bwa.nfdoesn't have. Not reconciled with the existing module — flagging for a human decision on whether to consolidate.
- Source processes:
readContamination(1) → renamedread_contamination - Label:
process_high(mem 64GB) - Container param:
container__centrifuge - publishDir: yes (
classified_reads,classification_report,kraken_report) - Notes: Kept
databaseasval(centrifuge_db)(notpath) exactly as source — it's passed to a customfile_handlerscript rather than staged by Nextflow, looks deliberate (likely an S3 path resolved at runtime).
- Source processes:
checkm_lineage,checkMLineageBins(2) → renamedcheckm_lineage,checkm_lineage_bins - Label:
process_high(both, mem 40GB) - Container param:
container__checkm - publishDir: yes, both (
quality_assessment) - Notes: Kept
stageAs: "bins/contigs.fna"oncheckm_lineage's contigs input — CheckM requires a directory of bin fasta files as its positional arg, a genuine tool-structure requirement, not just a filename preference.
- Source processes:
fastqToSam,estimateLibraryComplexity,markDuplicates,collectInsertSizeMetrics,collectGcBiasMetrics,collectAlignmentSummaryMetrics,collectQualityYieldMetrics,collectBaseDistributionByCycle,collectWgsMetrics,collectVariantCallingMetrics(10 processes; source also had 2workflowblocks) → renamed all to snake_case - Label:
process_single(all 10) - Container param:
container__picard - publishDir: all except
fastq_to_sam(intermediate) - Notes: Dropped the 2
workflowblocks entirely — module files in this repo hold onlyprocessblocks; workflow wiring belongs to the consuming pipeline. Fixed a source bug:fastqToSamdeclared two inputs both namedr1— corrected to a proper paired tuple.markDuplicates's illegal-in-shelldef maxmem = ...Groovy assignment replaced with the same dynamicXmxpattern used inbbnorm.nf. DroppederrorStrategy 'ignore'(not used elsewhere in this repo's modules — should live in pipeline config if still wanted). Renamed hardcoded placeholderSM=foo RG=footoSM=!{sampleid} RG=!{sampleid}— a behavior improvement now that a real sampleid exists; flag in case the fixed value was intentional.
- Source processes:
checkMLineageBins(1) → renamedcheckm2 - Label:
process_medium - Container param:
container__checkm2 - publishDir: yes (
quality_report) - Notes: Dropped
stageAsper source's own comment that it "only renames files" and doesn't handle a directory of multiple files; made--output-directoryper-sample instead of fixedresults.
- Source processes:
checkv_end_to_end(1, from OLD source — a different, more granular implementation than the pre-existingmodules/checkv.nf, which emits one directory) - Label:
process_high - Container param:
container__checkv(reused existing param name) - publishDir: yes, all 6 outputs
- Notes: Flagging a real conflict — reused
checkv_outdir/container__checkvparam names for consistency with the siblingmodules/checkv.nf, but if both processes are ever used in the same pipeline they'd write to the same output dir. A human should decide whether to rename this dev/ version's params or reconcile the two checkv modules.
- Source processes:
identify_reference_genome,find_candidate_references(2) - Label:
identify_reference_genome→process_low;find_candidate_references→process_high - Container param:
container__comparem - publishDir: yes, both
- Notes:
find_candidate_referencescompares many genomes against many candidate references (both directories) — no sampleid, kept plainpathinputs, mirrorsgatk.nf'scompare_referencespattern. EliminatedstageAseverywhere in favor of building the required directory structure in the shell body itself.
- Source processes:
binContigs(1) → renamedbin_contigs - Label:
process_medium(judgment call — cpus=8 but mem trivial) - Container param:
container__concoct - publishDir: yes, all outputs
- Notes: Rewrote outputs to live under a per-sample
!{sampleid}/directory instead of the fixedconcoct_output/to avoidpublishDircollisions across samples.
- Source processes:
predict_arrays(1, unchanged name) - Label:
process_single - Container param:
container__crispredict - publishDir: yes, both outputs
- Notes: Renamed fixed output dir
out/to!{sampleid}/for the same collision-avoidance reason as concoct.nf.
Cross-file note from this batch's agent: dropped errorStrategy 'ignore' (picard, concoct) since no existing module in the repo uses it — if that behavior is still wanted it should be reintroduced via the consuming pipeline's config, not the module.
- Source processes:
virulenceFactors(1) → renamedidentify_virulence_factors - Label:
process_high - Container param:
container__shortbred - publishDir: yes, all emits
- Notes: Preserved
sampleidthrough every output tuple even though the original had no sample identity at all (purepathoutputs) — worth a human check this doesn't break a downstream consumer expecting a bare path.
- Source processes:
assemble(1, unchanged) - Label:
process_high - Container param:
container__shovill - publishDir: yes, all emits
- Notes: Renamed misleading emit labels to match what the files actually are (
assembly_graphinstead offastgfor a.gfafile — looked like a copy-paste labeling mistake in the source).
- Source processes:
trim_paired_end(1, unchanged) - Label:
process_single - Container param:
container__sickle - publishDir: none — all intermediate
- Notes: No
task.cpususage added — original tool invocation never threaded either.
- Source processes:
spaced_seed_sketch_single_read(1, unchanged) - Label:
process_low - Container param:
container__sketch - publishDir: yes, all 4 CSV emits
- Notes: Flag for review — these CSVs might be intermediate inputs to a downstream aggregation step outside this file rather than a true terminal output; published them since nothing in-file consumes them.
- Source processes:
snippy_from_reads,snippy_from_contigs(2, unchanged) - Label:
process_medium(both) - Container param:
container__snippy - publishDir: yes, both, all emits
- Source processes:
sourmash_hash(1, unchanged) - Label:
process_single - Container param:
container__sourmash - publishDir: yes
- Notes: Flag for review — a
.sigfile is often just fed into a latersourmash compare/searchstep; chose to publish since signatures are typically reused/cheap to keep, but droppublishDirif this pipeline treats it as purely intermediate.
- Source processes:
assemble,assemble_metagenome,assemble_metagenome_single_end(3, unchanged) - Label:
process_high(all 3) - Container param:
container__spades - publishDir: yes, all 3
- Notes: Important flag — the two source processes used two different hardcoded container versions (
spades:20201216_235053vsspades:20220721_001047); collapsed to a singleparams.container__spadesper house style, so the consuming pipeline's config now controls the version and loses the original version distinction. A human should confirm the two SPAdes versions were actually interchangeable, or split into two container params if not. Kept tool-specificerrorStrategydirectives (real retry logic tied to a specific SPAdes exit code, not a resource-tier concern).
- Source processes:
strainge_run_straingst_paired_end,strainge_run_straingst_single_end(2, unchanged) - Label:
process_high(both) - Container param:
container__strainge - publishDir: yes, both
- Source processes:
mlst(1) → renamed tostringmlst - Label:
process_medium(ambiguous tier: cpus=1 but mem=16GB) - Container param:
container__stringmlst - publishDir: yes
- Notes: Renamed away from
mlstspecifically because a realmlst.nfmodule also exists in this migration batch (batch 8) — a process literally namedmlsthere would collide/confuse in a shared modules repo. Preserved the source's--fastq1 !{r2} --fastq2 !{r1}reversed argument order exactly as-is (possibly a source bug, possibly intentional) — did not attempt to "fix" tool logic, only interpolation syntax.
- Source processes:
blastn_reference_fasta,megablast_reference_database,blastn_pairwise_all_vs_all,summarize_blastn_all_pairwise,visualize_blastn_all_pairwise,parse_blast_xml,refine_and_filter_hits(7, all converted per the MIXED-file decision, no renames needed) - Label:
process_single/process_high/process_mediumper-process (mixed — see notes) - Container param:
container__blast(reused existing param name — seemodules/blast.nfoverlap note in Scope section) - publishDir:
reference_blast_hits,megablast_hits,pairwise_blast_summary,pairwise_dotplots,guide_seeds— not onpairwise_blast_results/parsed_hits(feed the next process in-file) - Notes:
parse_blast_xml/refine_and_filter_hitsare part of a distinct guide-editing "campaign" workflow unrelated to the vector/megablast flows in the same file — no sampleid semantics apparent. Flag:process_mediumlabel calls for those two (1 cpu, 8–16GB mem) didn't cleanly fit the cpu/mem tier buckets, worth a second look. KeptstageAs: "blast_db/reference.fasta"onblastn_reference_fasta's reference input since the script doescd blast_db && makeblastdb— load-bearing.
- Source processes:
trim_paired_end,trim_single_end(2, unchanged) - Label:
process_medium(both) - Container param:
container__fastp - publishDir: yes, both
- Notes: Flag — the original script never passes a thread/CPU flag to
fastpat all despite reserving 8 cpus; preserved that exactly (no invented--threadflag) rather than "fixing" it — a human should decide if--thread !{task.cpus}should be added.
- Source processes:
fastqc,fastqc_single_reads,fastqc_unpaired_reads(3, unchanged) - Label:
process_single/process_single/process_low - Container param:
container__fastqc - publishDir: yes, all 3
- Notes: Kept
stageAson all three read inputs deliberately — FastQC names its output files from the input's basename, a genuine fixed-filename requirement. Flag — dropped the source's dynamicmemory { N.GB * task.attempt }/30mtimeout directives in favor of the label system; the source comment explained the 30m timeout existed specifically to catch a known silent-OOM issue — a human should confirm the consuming pipeline's label tiers provide equivalent retry/memory-escalation behavior.
- Source processes:
filtlong_qc_downsample_phage,filtlong_light_qc,filter_ont_reads_by_length(3, unchanged) - Label:
process_single(all 3) - Container param:
container__filtlong - publishDir: none — all 3 feed a downstream assembler, no
_outdirparam introduced since nothing is published from this file (would need to be added if a pipeline wants filtered reads published directly)
- Source processes:
flye_assembly,meta_assembly(2, unchanged) - Label:
process_high(both) - Container param:
container__flye - publishDir: yes, both
- Notes: Preserved
errorStrategy 'ignore'onmeta_assemblyexactly as source — tool-specific resiliency behavior, not a resource directive, kept outside the label system.
- Source processes:
gtdbtk_classify_genome(1, unchanged) - Label:
process_high - Container param:
container__gtdbtk - publishDir: yes, all outputs
- Notes: Kept
stageAs: "genome.fna"— GTDB-Tk's default genome-directory extension expectation is.fnaand the source never passes--extension, so the fixed name is load-bearing exactly as originally written.
- Source processes:
merge_fastqs(1, unchanged) - Label:
process_single - Container param:
container__pandaseq - publishDir: none — intermediate (feeds downstream amplicon processing)
- Source processes:
lifestyle(1, unchanged) - Label:
process_single - Container param:
container__phacts - publishDir: yes (
lifestyle_prediction)
- Source processes:
refine_assembly(1, unchanged) - Label:
process_high - Container param:
container__pilon - publishDir: yes (
refined_contigs) - Notes: Dropped source's
maxRetries = 3directive, consistent with samtools.nf/checkv.nf leaving retry policy to the pipeline'swithLabel:config.
- Source processes:
build_pangenome(1, unchanged) - Label:
process_high(cpus=96, mem 128GB) - Container param:
container__pirate - publishDir: yes, all 12 emits
- Notes: Flag for careful review — this file had the densest bash-escaping in the batch (
check_error/traplogic, sed/awk locus-translation block); the agent called out this specific area as highest-risk for transcription error during the\$→$conversion.
- Source processes:
plasmidfinder(1, unchanged) - Label:
process_single - Container param:
container__plasmidfinder - publishDir: yes (
plasmid_predictions)
- Source processes:
count_unique_amplicons,denoise_amplicons,detect_chimeras_de_novo,count_denoised_amplicons,detect_chimeras_from_reads,match_amplicons(6, unchanged) - Label:
process_low(all 6) - Container param:
container__vsearch - publishDir:
count_denoised_amplicons,detect_chimeras_from_reads,match_amplicons— not on the 3 earlier intermediate steps - Notes: Flag for careful review —
detect_chimeras_from_readsandmatch_ampliconshad the densest awk/escaping in this batch (field refs, tab-separator literals, WT/non-WT join logic); worth a close second read.
- Source processes:
cluster_proteins(1, unchanged) - Label:
process_high - Container param:
container__mmseqs - publishDir: yes, all 4 emits
- Notes: Flag — added
--threads !{task.cpus}to theeasy-clustercall even though the original never passed a thread flag (only declaredcpus=64on the process) — very likely a legacy oversight, adding it preserves evident intent, but a human should confirm.
- Source processes:
find_engineered_snvs,align,align_to_index,ont_read_accuracy(4, unchanged) - Label:
process_single/process_low/process_low/process_medium - Container param:
container__minimap2 - publishDir:
find_engineered_snvs,ont_read_accuracyonly —align/align_to_indexare intermediate
- Source processes:
mlst(1, unchanged) - Label:
process_single - Container param:
container__mlst - publishDir: yes (
mlst_calls)
- Source processes:
find_engineered_snvs(1, unchanged) - Label:
process_single - Container param:
container__mummer - publishDir: yes (
variant_calls,variant_calls_index) - Notes: Shares its process name
find_engineered_snvswith the one inminimap2.nf— harmless since each lives in its own file, but flag in case a consuming workflow ever imports both without aliasing.
- Source processes:
generate_multiple_sequence_alignment_dir(1, unchanged) - Label:
process_high - Container param:
container__muscle - publishDir: yes (
aligned_sequences_archive) - Notes: Input is a tarball of many fastas across potentially many samples — no sampleid, matches rule 8's "merging across many samples" exception.
- Source processes:
nanoplot_qc(1, unchanged) - Label:
process_low - Container param:
container__nanoplot - publishDir: yes (
html_report,report_dir) - Notes: Kept source's
errorStrategy 'ignore'(QC failure on one sample shouldn't halt the run) — not covered by CLAUDE.md rules but clearly intentional; flag for a human to confirm this is still wanted in house style.
- Source processes:
nanosim_read_analysis,alignment_analysis(2, unchanged) - Label:
process_medium/process_high - Container param:
container__nanosim - publishDir: yes, both
- Notes: Behavior change flagged — the original
alignment_analysisawk command referenced a legacyparams.sample_idglobal (with what looks like a pre-existing stray-backslash bug,\\${params.sample_id}) to identify "the current sample"; replaced with the process's own!{sampleid}from the new tuple input — more correct and idiomatic, but an intentional behavioral change from a literal translation. KepterrorStrategy 'ignore'on both, same rationale as nanoplot.nf.
- Source processes:
trycycler_long_read_assembly(1, unchanged) - Label:
process_high - Container param:
container__trycycler - publishDir: yes (
consensus_contig,partitioned_reads,assembly_results) - Params referenced also:
params.expected_phage_genome_size(left unprefixed — pre-existing global option, not tool-specific)
- Source processes:
unicycler_short_read_assembly,unicycler_long_read_assembly,unicycler_hybrid_assembly,unicycler_hybrid_assembly_bacterial(4, unchanged) - Label:
process_high(all 4) - Container param:
container__unicycler - publishDir: yes, all 4
- Source processes:
uniqsketch_build_index_stock,uniqsketch_query_stock_sample(2, unchanged) - Label:
process_high(both — chosen by cpu count even though the query process only needs 4GB mem) - Container param:
container__uniqsketch - publishDir: yes, both
- Notes: Flag — in the awk line, a two-layer escaping case was resolved (Groovy-only
\\"→\",\$1→$1, but a single backslash on\"was preserved since awk itself needs it to emit literal quotes in JSON output) — worth a human double-check.
- Source processes:
binContigs(1) → renamedbin_contigs - Label:
process_medium(source had no explicit cpus, defaulted per rule 5) - Container param:
container__vamb - publishDir: yes (
genome_bins,bin_name_map,cluster_assignments,run_log)
- Source processes:
vibrant(1, from the OLD source — a materially different code path than the pre-existingmodules/vibrant.nf: this one extractsdatabases.tar.gz/files.tar.gzarchives and uses-d/-mflags, vs. the existing module's direct-f/-l/-oinvocation with no db archives) - Label:
process_high - Container param:
container__vibrant - publishDir: yes, all 4 emits
- Notes: Risk flag — process name
vibrantis identical to the process name in the already-existingmodules/vibrant.nf; if a pipeline ever includes both files, Nextflow will collide on process name. Left as-is per the "stay consistent with existing module naming" instruction, but a human should confirm these two are never included together without anasalias.
- Source processes:
find_phages_sensitive,find_phages_specific(2, from the OLD source — which, unlike the pre-existingmodules/virsorter2.nf, takes no explicitvirsorter2_dbpath input at all) - Label:
process_high(both) - Container param:
container__virsorter2 - publishDir:
find_phages_specificonly (pass 2 final output) — pass 1 feeds pass 2 in-file - Params referenced also:
params.viralinference_minlength(reused unprefixed shared param, matching the existingmodules/virsorter2.nfconvention) - Notes: Functional gap flagged — did not add a
virsorter2_dbinput since the old source truly never had one; this dev/ version may be functionally behind the currentmodules/virsorter2.nf— human should check whether this old source is even still relevant/current.
- Source processes:
virulencefinder(1, unchanged) - Label:
process_single - Container param:
container__virulencefinder - publishDir: yes (
virulence_factors)
- Source processes:
readContamination,singleEndReadClassification,pairedEndReadSelector,pairedEndReadExcluder,singleEndReadSelector,singleEndReadExcluder,kreportToJson(7, all converted per MIXED-file decision) → renamed to snake_case - Label:
process_high(classification processes, mem 92GB) /process_single(selector/excluder/json processes) - Container param:
container__kraken - publishDir: classification +
kreport_to_jsonoutputs — not on the 4 selector/excluder processes (intermediate) - Notes:
kreportToJson's inline python (originally invoked via a#!/usr/bin/env python3shebang trick unique toscript:blocks) had to be restructured as apython3 <<'PYEOF' ... PYEOFheredoc inside theshell:block sinceshell:always executes via bash. Flag:databasekept asval(kraken_db)(notpath) since it pairs with a customfile_handlercommand (likely an S3-fetch helper) — human should confirmfile_handlerstill works under the new param contract.
- Source processes:
jackhmmer_seq,jackhmmer_dir,hmmbuild(3, unchanged) - Label:
process_high/process_high/process_single - Container param:
container__hmmer - publishDir:
jackhmmer_dir,hmmbuild— notjackhmmer_seq(feeds a downstream hmmbuild call)
- Source processes:
createMockCommunityReadsAbundanceDistribution,createMockCommunityReadsAbundanceFile,createAbundanceFile(3) → renamed to snake_case - Label:
process_medium/process_medium/process_single - Container param:
container__insilicoseq - publishDir: both
iss generateprocesses — notcreate_abundance_file(feeds the abundance-file process) - Notes: No natural sampleid anywhere — these simulate a synthetic mock community from a set of input genomes. Source's
create_abundance_fileoddly mixedshell:with a double-quoted"""body and un-escaped$— fully rewritten to the standardshell:+'''...'''+!{...}form.
- Source processes:
illumina_interop_qc(1, unchanged) - Label:
process_single - Container param:
container__interop - publishDir: yes, all 15 emits
- Notes: This source was already close to house style. Kept all
stageAsdirectives since the script hardcodes tests against exact literal paths (e.g.if [ -f bcl_convert_unknowns.csv ]) — a legitimate fixed-filename exception. Flag — preserved a pre-existing apparent logic quirk verbatim (anawk 'NR!=1 {print}' | awk -F","...pipeline where the first awk has no input source) — did not attempt to fix source logic bugs, only syntax conversion; a human should double check this.
- Source processes:
annotate(1) → renamedinterproscan(matches genomad/checkv tool-naming convention) - Label:
process_high - Container param:
container__interproscan - publishDir: yes (
functional_annotation_gff3,functional_annotation_tsv) - Notes: Outputs declared via
path("${proteins}.gff3")/path("${proteins}.tsv")referencing the input variable directly in theoutput:block (standard Nextflow, not!{...}shell interpolation) since interproscan.sh derives output filenames by appending extensions to the given input filename — human should confirm this resolves correctly at runtime.
- Source processes:
findGroupSpecificKmers(1) → renamedfind_group_specific_kmers - Label:
process_high - Container param:
container__kmergo - publishDir: yes, all 14 emits (renamed from terse
A_ace/B_ace-style names for clarity) - Notes: No sampleid — compares two arbitrary groups (A vs. B) of genomes/metagenomes against each other, matches rule 8's comparison-input carve-out.
- Source processes:
annotate_variants_no_index,annotate_variants,annotate_expected_variants,variant_report(4, all converted per MIXED-file decision, unchanged names) - Label:
process_single(all 4) - Container param:
container__snpeff - publishDir:
annotate_expected_variants,variant_report— not the other two (intermediate;annotate_variantsis also marked DEPRECATED in source) - Notes: Flag —
annotate_variants_no_index'sanncommand reads a literalvariants.vcf(no.gz) even though the staged input isvariants.vcf.gzwith no interveninggunzip— looks like a pre-existing source bug, preserved verbatim rather than silently "fixed."
- Source processes:
build_viral_ortholog_clusters,build_proteins_fp(2, both converted per MIXED-file decision, unchanged names) - Label:
process_high/process_single - Container param:
container__vcontact2for the clustering process;container__shellbasicforbuild_proteins_fp - publishDir:
build_viral_ortholog_clustersonly - Notes: Deliberate rule-6 exception, flag for review —
build_proteins_fpused a genuinely different container image in the source (shellbasic:..., a plain shell/awk utility image) than the clustering process (vcontact2:...); forcing both tocontainer__vcontact2would silently change runtime behavior, so kept the distinct image as its owncontainer__shellbasicparam — confirm this matches the project's actual naming convention.
- Source processes:
index,to_bam,mapped_reads_to_bam,unmapped_reads_to_bam,sort_to_bam,sort_by_name_to_bam,fasta_index,bam_to_fastq,bam_to_fastq_single_end(9, from OLD source — different/more processes than the pre-existingmodules/samtools.nf, unchanged names) - Label:
process_single(all 9) - Container param:
container__samtools - publishDir:
index,fasta_indexonly — mirrors the precedent in the existingmodules/samtools.nfwhere only the terminal indexing step publishes - Notes: Flag — added
-@ !{task.cpus}to view/sort/index/fastq calls even where the source hardcodedcpus=1and no thread flag at all; matches the parent module's style but is a deliberate addition beyond literal 1:1 port. Also treated the originalindexprocess's hardcodedinput.bam.baioutput (regardless of whether input was actually BAM/SAM/CRAM) as a source bug and derived the index name from the real input filename instead.
- Source processes:
binContigs(1) → renamedbin_contigs - Label:
process_medium(no explicit cpus in source) - Container param:
container__semibin - publishDir: yes (
fasta_bins,bin_names_json) - Notes: Flag — added
--threads !{task.cpus}to the SemiBin call even though the original had no thread flag at all; without it theprocess_mediumlabel's CPU allocation would go unused, but this is a deliberate addition beyond literal translation.
- Source processes:
seqtk_remove_duplicate(1, source name referenced the wrong tool — "seqtk" vs. the actually-invokedseqkit) → renameddeduplicate_sequences - Label:
process_medium - Container param:
container__seqkit - publishDir: none — treated as intermediate, genuinely ambiguous, flag for review
- Notes: Added
-j !{task.cpus}to both seqkit calls even though source had no thread flag — same deliberate-addition caveat as semibin.nf.
- Source processes:
serotypefinder(1, unchanged) - Label:
process_single - Container param:
container__serotypefinder - publishDir: yes (
serotype_calls)
- Source processes:
count_unique_amplicons,denoise_amplicons,count_denoised_amplicons,create_uniques_count_table,create_denoised_count_table(5, all converted per MIXED-file decision, unchanged names) - Label:
process_single(all 5) - Container param:
container__usearch - publishDir: the two count-table processes only
- Notes: No sampleid anywhere — classic USEARCH/UNOISE3 amplicon workflows dereplicate/denoise pooled study-level reads (all samples concatenated, sample labels embedded in headers), not one sample at a time. Human should confirm this pooled assumption matches how the pipeline actually invokes this file.
- Source processes:
annotate_bacterial,annotate_phage,annotate_phage_with_proteins,annotateMetagenome,annotate_custom_phage,annotate_named_phage(6) →annotateMetagenomerenamedannotate_metagenome - Label:
process_medium(all 6) - Container param:
container__prokka - publishDir: yes, all 6
- Notes: Fixed a real bug — source had
—-compliant(em dash + hyphen) instead of--compliantin all 6 processes, which would have broken the actual Prokka CLI call; corrected to--compliant, flagging for human sign-off since this changes literal script text, not just style. KeptstageAs: "contigs.fasta"in 5 of 6 processes because the shell's owncheck_error()function greps prokka's output for the literal stringcontigs.fasta— a genuine fixed-filename dependency. Preserved an odd double-timestamp naming pattern (${phage}.${timestamp}.${timestamp}) from the original rather than "fixing" it.
- Source processes:
pycoqc_qc(1, unchanged) - Label:
process_medium - Container param:
container__pycoqc - publishDir: yes (
qc_report_html,qc_report_json) - Notes: No sampleid — source comment explicitly says "flowcell level metrics"; one ONT flowcell's summary can span multiple multiplexed samples.
- Source processes:
evaluate_assembly,evaluate_assembly_with_reference,evaluate_long_read_assembly,evaluate_ont_phage_assembly,evaluate_hybrid_assembly,evaluate_illumina_metagenome_assembly_paired_end,evaluate_illumina_metagenome_assembly_single_end,evaluate_ont_metagenome_assembly,evaluate_contigs(9, unchanged) - Label:
process_high(all 9) - Container param:
container__quast - publishDir: yes, all 9
- Notes: Standardized
--output-dirto!{sampleid}/quast_results(was a fixedquast_results/for every sample) to avoid publishDir collisions across samples.
- Source processes:
resfinder(1, unchanged) - Label:
process_single - Container param:
container__resfinder - publishDir: yes (
resistance_genes,log)
- Source processes:
quantify_transcripts_aligned,quantify_transcripts_mapping_mode(2, unchanged) - Label:
process_medium(both) - Container param:
container__salmon - publishDir: yes, both
- Notes: Replaced in-place
gunzip r1.fq.gz(which relied on the droppedstageAsfixed filename) with explicitgunzip -c !{r1_fq_gz} > r1.fq— preserves a deterministic local filename without depending on a forced input name; human should double-check this behaves identically to the original.
- Source processes:
hhblits,hhblits_seq,ffindex_get,reformat_msa,download_pdb_structure,hhmake,convert_jackhammer(7, unchanged names) - Label:
process_high(hhblits, hhblits_seq) /process_single(ffindex_get, reformat_msa, download_pdb_structure, hhmake) /process_medium(convert_jackhammer, no resources declared in source) - Container param:
container__hhsuite - publishDir:
hhblits,hhblits_seqonly - Notes: Flag —
ffindex_get's finaltar -czvf records.tar.gz -C recordsis missing a trailing.(compare tohhblits's correct form) — looks like a source bug, preserved verbatim rather than silently fixed; a human should confirm/fix. KeptstageAs: "input.a3m"onhhmake's input since hh-suite tools often rely on the.a3mextension for format detection — unverified assumption.
- Source processes:
parse_hhr_dir,join_clusters_to_hhr(2, both converted per MIXED-file decision, unchanged names) - Label:
process_low(both) - Container param:
container__hhsuiteparse(kept distinct fromcontainer__hhsuiteper rule 6) - publishDir:
join_clusters_to_hhronly (parse_hhr_dirfeeds it directly, in-file) - Notes:
join_clusters_to_hhr's script is Python under a#!/usr/bin/env python3shebang inside the shell block — kept as-is (Nextflow honors the shebang).
- Source processes:
trim_merged_fastq,trim_single_fastq,trim_amplicon_reads(3, unchanged) - Label:
process_single(all 3) - Container param:
container__cutadapt - publishDir: yes, all 3
- Notes: Fixed a source bug —
trim_amplicon_readsdeclaredcpus = CPUS(=1) but hardcoded--cores 4in the script; both now correctly derive from!{task.cpus}— flagging this behavior change for human confirmation. Subtle escaping intrim_merged_fastq's awk print statement (quadruple-escaped\\\$reduced to single\$) — worth a sanity-check against a real run.
- Source processes:
find_defenses(1, unchanged) - Label:
process_low - Container param:
container__defensefinder - publishDir: yes (
hmmer_hits,defense_systems,defense_genes)
- Source processes:
diamond_makedb,diamond_blastp,diamond_blastx,diamond_test,diamond_similarity_search(5, unchanged) - Label:
process_low/process_high/process_high/process_single/process_high - Container param:
container__diamond - publishDir:
diamond_blastp,diamond_blastx,diamond_similarity_search— notdiamond_makedb(infrastructure) ordiamond_test(no output at all)
- Source processes:
ezclermont(1, unchanged) - Label:
process_single - Container param:
container__ezclermont - publishDir: yes (
phylogroup,log)
- Source processes:
visualize_16s,make_checkfile(2, both converted per MIXED-file decision, unchanged names) - Label:
process_low(both) - Container param:
container__visualize_16s - publishDir: yes, both
- Notes: No sampleid — both operate on whole-cohort taxonomy/feature tables, not one sample's data. Preserved the source's own apparent inconsistency verbatim: the script passes plain filenames (
asv_table.tsv,genus_breakdown.png) toasv_table_viz.py, but the process'soutput:block declares them under avisualize_output/prefix — almost certainly because the script itself creates and writes into that subdirectory internally. Did not "fix" this; a straight syntax conversion only. This file was originally reported converted by the batch-13 subagent but was never actually written to disk — caught by an independent verification pass and created directly afterward.
An independent automated pass (not just each subagent's self-report) was run across all 87 output files in modules/dev/:
- File count: all 87 target files present (one,
visualize_16s.nf, was initially missing despite being reported done — see note above; created directly to close the gap). - Brace balance: all 87 files balance (
{count ==}count). (Two pre-existing, unrelatedmodules/dev/files —deepmicroclass.nfandvirfinder.nf, not part of this migration — are unbalanced; out of scope, left untouched. Two other pre-existing files,phamb.nfandviralverify.nf, are empty placeholders, also out of scope.) - Shebang: all 87 files start with exactly
#!/usr/bin/env nextflow. - No leftover
nextflow.enable.dsl=2in any of the 87 files. - No leftover
script:blocks — all converted toshell:. - Source directory untouched:
/Users/blhurwit/work/brc/nf/toolswas confirmed read-only throughout (every subagent explicitly checked this); no source.nffile was modified.
Collected across all batches — search this notebook for "Flag"/"flag" for full context on each:
- Container-version ambiguity (
spades.nf): two SPAdes source processes used different hardcoded container versions, collapsed to onecontainer__spadesparam — confirm the versions were interchangeable. - Container mismatch, deliberate exception (
vcontact2.nf):build_proteins_fpneeded its owncontainer__shellbasicparam since its source container differed from the tool's own image. - Param-naming collision risk (
checkv.nf, indev/): reusedcontainer__checkv/checkv_outdirfrom the pre-existingmodules/checkv.nf— would collide if both are ever used in the same pipeline. - Process-name collision risk (
vibrant.nfindev/): process namedvibrant, identical to the pre-existingmodules/vibrant.nf's process name. - Behavior/default changes requiring new pipeline config:
amrfinder.nf(organism flag → param),bbnorm.nf(5 thresholds → params),medaka.nf(basecall model → param) — each needs the consuming pipeline's config updated to preserve prior behavior. - Fixed source bugs (preserved-vs-fixed calls made explicit per file):
prokka.nf's—-compliantem-dash typo (fixed),cutadapt.nf's cpus/--coresmismatch (fixed),picard.nf's duplicater1input bug (fixed) — vs. bugs preserved verbatim:snpeff.nf's ungzippedvariants.vcfreference,hhsuite.nf's missingtartrailing dot,interop.nf's headerless awk pipeline,stringmlst.nf's reversed--fastq1/--fastq2args. - Deliberate new additions beyond literal translation (thread flags added where the source never had one):
mmseqs.nf,semibin.nf,seqkit.nf,samtools.nf— flagged individually as these change behavior (better CPU utilization) but aren't 1:1 ports. - Ambiguous
publishDirplacement (genuinely unclear whether output is terminal or intermediate):bedtools.nf'sget_fasta,sketch.nf,sourmash.nf,seqkit.nf,mafft.nf(also ambiguous on sampleid),mothur.nf(unverifiedrun_mothur.shinternals). - Two parallel, non-reconciled implementations now exist for the same tool in several cases (
bakta,blast,bwa,checkv,megahit,samtools,vibrant,virsorter2) — the pre-existingmodules/<tool>.nfand this migration'smodules/dev/<tool>.nfcover different/overlapping process sets and were deliberately not merged. A human should decide whether/how to consolidate before promoting anything out ofdev/.
Nothing in modules/dev/ was promoted to the parent modules/ directory — per the task, everything stops at dev/ for human review before promotion.
- Checked
modules/vsmodules/dev/for duplicate tool coverage (asked to look into this after the migration landed). Exact-filename overlap in both dirs:bakta.nf,blast.nf,bracken.nf,bwa.nf,checkv.nf,megahit.nf,samtools.nf,vibrant.nf,virsorter2.nf(9). Near-duplicates under a different name:kraken2.nf(root) /dev/kraken.nf,mmseqs2.nf(root) /dev/mmseqs.nf,deepmicroclass2.nf(root) /dev/deepmicroclass.nf. Verified viadiff: all of these cover genuinely different or non-overlapping process sets (different process names/counts, e.g. rootbwa.nfhasbwa_index+bwa_memvs. dev'sbwa_align/bwa_align_single_end/bwa_align_to_index) exceptbrackenandvibrant, which use the same process name in both files — a real Nextflow name collision if a pipeline ever includes both without anasalias. Also found a straymodules/bbmap.nf copy(content differs frombbmap.nf) — looks like an accidental Finder duplicate, not a real module. - Decision / next step: nothing in
modules/dev/gets promoted tomodules/until a human reconciles the 9 (+3 near) parallel tool implementations — the migration record's own scope note above already says the same.bracken/vibrant's process-name collision needs resolving before both could ever be included in the same pipeline.modules/bbmap.nf copyshould be deleted or explained. - Acted on the review above: for the 9 non-colliding tool pairs (
bakta,blast,bwa,checkv,megahit,samtools,virsorter2, plus the near-duplicateskraken2/dev/krakenandmmseqs2/dev/mmseqs), merged eachmodules/dev/*.nf's process(es) into the matching rootmodules/*.nfas additional process blocks in the same file, then deleted thedev/source. Verified brace balance and process counts on every merged file before deleting anything (e.g.blast.nfwent from 2 to 9 processes,samtools.nffrom 3 to 12) — all balanced, all end in}. - One planned merge was aborted:
modules/dev/deepmicroclass.nfis missing its closing}entirely (theprocess deepmicroclassrblock just never closes) — this is the same pre-existing, unrelated brokenness the migration record above already flagged as out of scope for its migration. Reverteddeepmicroclass2.nfback to its committed state viagit checkoutrather than merge broken content into it;dev/deepmicroclass.nfneeds a human fix before it can be merged or promoted. - For the 2 colliding tools (
bracken,vibrant— same process name in bothmodules/andmodules/dev/), deleted themodules/dev/copy without merging, per instruction — the two implementations aren't reconcilable into one file under Nextflow's per-file process-name uniqueness, so the existing root version stands as the only one. - Flagged in-file:
checkv.nf's two processes (checkv,checkv_end_to_end) now sharecontainer__checkv/checkv_outdir— added aNOTE:comment abovecheckv_end_to_endsince using both in the same pipeline run would collide on output directory; not renamed, left for a human decision. modules/dev/now holds only files untouched by this pass (never had a root-level duplicate) plus the one flagged exception (deepmicroclass.nf).modules/bbmap.nf copy(the stray accidental-duplicate file noted earlier) is still there, still unaddressed.- Consolidated this repo's two notebooks into one, per lab convention (one chronological,
append-only
NOTEBOOK.mdper repo, not a separate ad hoc report): mergedLAB_NOTEBOOK.md's content into this file's entry above and deleted the standalone file. - Checked in the
notebookskill itself as a project-scoped skill (.claude/skills/notebook/SKILL.md, copied verbatim from the user-level one at~/.claude/skills/notebook/), so anyone who clones this repo and uses Claude Code gets the same/notebookdated-entry convention automatically, without a per-user setup step. Documented the convention (and the checked-in skill) in a new README section. - Expanded the README's notebook section into a "Working on this repo" section:
states the expectation up front (record changes in
NOTEBOOK.md, not just the commit message, since the notebook is for the why/outcome a diff can't carry) and adds a copy-pasteable prompt template for anyone recording a session manually or via a non-Claude-Code assistant, alongside the existing/notebookmention for Claude Code users. - Noticed (before making any more README changes) that
modules/dev/had gone from ~80 files down to 3 since our last check — a separate commit,fcacaf3"resolved conflicts between tools" (same user, different session), had promoted every remaining non-collidingdev/module to root and deleted the brokendev/deepmicroclass.nfoutright rather than fixing it. Root module count is now 105. - Updated
README.mdaccordingly: added a "Repo layout: modules/ vs modules/dev/" section (what each directory is for, the promotion decision tree); replaced the now-9-rows-vs-105-actual-files module table with als/greppointer instead of a table, since a hand-maintained list at this scale and edit pace goes stale within a day; added a "Contributing" section on checking for name collisions before adding a module (referencing the realvibrantcollision from earlier as the concrete reason); added a "Known issues" section documenting the still-straymodules/bbmap.nf copyand the 3 remainingdev/files verified just now:phamb.nf/viralverify.nfare both literally empty (0 bytes),virfinder.nfhas unbalanced braces (8{vs. 7}).
- Established a
publishDirconvention that lets a consuming pipeline opt into per-sample, manifest-driven output paths without changing behavior for anyone else:publishDir { params.<tool>_outdir instanceof Closure ? params.<tool>_outdir(sampleid) : params.<tool>_outdir }, mode: 'copy'. A module still only ever assumes theparams.<tool>_outdircontract — a pipeline opts in by setting that param to asampleid-taking Closure instead of a plain string; every other pipeline is unaffected. Made this a required rule inCLAUDE.md(a plain-stringpublishDiris now a violation, not just an older style), including an exception only found after applying it repo-wide: a process with nosampleidin itsinput:(index-building, cross-sample merge/compare, whole-batch/run steps) must keep the plain-string form — forcing the Closure there is a hard compile-time error (sampleid is not defined), not a style issue.TEMPLATE.nfupdated to show the required pattern.modules/dev/deliberately excluded from all of the below — still a pre-promotion staging area per the 2026-09-08 entry above, with its own unresolved issues; not touched. - Ran a full compliance sweep (4 parallel passes, ~24 files each) across all 96
real module files / ~225
processblocks: converted every existing staticpublishDirto the Closure form (~90 processes), correctly left ~20 as plain strings (nosampleidin scope, each annotated inline with why), and audited every process against every otherCLAUDE.mdrule. Missinglabel "process_<tier>"lines were filled in by matching a comparable already-labeled tool rather than defaulting toprocess_medium— e.g.bwa_mem→high (matches labeledbwa_align*siblings),eggnogmapper/iphop→high (matchesinterproscan/genomad, database-driven),metabuli/metaphlan→high (matchescentrifuge/kraken2),viralm/virrep→medium (matchesdeepvirfinder, GPU classifiers).samestr.nf's 6 processes got medium/medium/low/low/high/low with no repo precedent to match — a judgment call from each stage's computational shape, worth a second look. - The stray duplicate file flagged unaddressed since 2026-09-08
(
modules/bbmap.nf copy) is resolved: itsbbduk/bbstatsprocesses were merged intobbmap.nf(by the repo owner, mid-session) and brought into full compliance (labels, ClosurepublishDir) along with everything else. In the siblingviral_inference_benchmarkrepo,s_i_mg_readqc.nfwas updated toinclude { bbduk }frombbmap.nf(it previously pointed at abbduk.nfthat never existed) and a real emit-name mismatch (bbduk.out.trimmed_readsshould have been.clean_reads) was fixed — that mismatch predated this session and had never been exercised against a real module until now. - Sweep surfaced 5 pre-existing bugs, all confirmed via
git diffto predate this session:mmseqs2.nf(cluster_proteins) — fixed. Root cause: the Groovy triple-single-quoted shell-block string was itself decoding the nested awk script's escape sequences —\(/\)are invalid Groovy escapes and hard-fail the parse (Unexpected character: ''');\047/\057are valid octal escapes and were silently turned into literal'//characters instead; a"\n"further down became a real embedded newline instead of a two-character escape. Fixed by doubling every backslash so Groovy passes them through unmodified to awk. Verified the mechanism with isolated minimal repros before touching the real file, then confirmed the generated.command.shmatches the original script's intent byte-for-byte.kraken2.nf— fixed. 7 of its 8 processes usedcontainer__kraken/kraken_outdirinstead ofcontainer__kraken2/kraken2_outdir, mismatched against the file's own name and its first process. Renamed all 7 to match. This is a breaking param-name change for any pipeline currently setting the old names for this module — none found in a repo-wide grep, but this repo has no visibility into every consumer.gatk.nf'scompare_references— investigated, turned out NOT to be a bug. Thepath(reference), stageAs: "ref.fasta"comma placement looked wrong on read; a direct empirical test (invoking the process with real files) showed both inputs stage under exactly the intended names. Left untouched.pirate.nf'sbuild_pangenome— same class of bug asmmseqs2.nf(unescaped quotes breaking the Groovy shell-block parse). Found, not fixed — out of scope this session.hhsuiteparse.nf— separate, unrelated compile error (path("results", type: 'dir')syntax). Found, not fixed — out of scope this session.
- Also flagged, not touched:
filter_viral_genomad.nfis an unfinished stub — both processes have empty shell bodies, and one looks copy-pasted frombbmap.nfwith the wrong container/params/output names. Needs a real implementation from whoever owns that tool. - Next steps for whoever picks this up:
pirate.nfandhhsuiteparse.nfneed the same treatmentmmseqs2.nfgot before they'll compile at all;filter_viral_genomad.nfneeds an actual implementation;samestr.nf's label tiers are a judgment call worth a second opinion; none of this session's ~87 changed files are committed yet. - Implemented
filter_viral_genomad.nf's two processes (the stub flagged above), requested for a geNomad post-processing SLURM job:filter_viral_contigsrunsgenomad_filterviral.r(filters geNomad calls using checkVquality_summary.tsv- a coverage file, writes
<sample>_selection1.csv);extract_fasta_viral_selectionrunsgenomad_getselectionviral.py(extracts provirus/viral FASTA + ID-mapping CSVs from that selection). Both call the script by bare name, relying on Nextflow's auto-added pipelinebin/onPATH, matching the convention already used byquast.nf/concoct.nf/etc.
- a coverage file, writes
- The actual bin scripts weren't in this repo — traced them from
viral_inference_benchmark/nextflow_pipeline/bin/, where their real filenames aregenomad_filterviral.randgenomad_getselectionviral.py(the SLURM job that prompted this referenced.Randgetviralselection.py— stale/incorrect names). Used the real filenames in the module. - New params a consuming pipeline must define:
container__genomad_filterviral,genomad_filterviral_outdir,container__genomad_viralselection,genomad_viralselection_outdir. Checked againstmodules/CLAUDE.md(balanced braces, ClosurepublishDiron both sincesampleidis in scope, namedemit:s,shell:/!{...}throughout) — clean, nothing to fix. - Replaced the per-process in-module
publishDirconvention with a centralized, label-based one: a process now carries a second label —publish_intermediateorpublish_final— alongside its resource label, with nopublishDirdirective of its own. The actualpublishDir(path andmode) is owned by whichever pipeline runs the process, viawithLabel:publish_intermediate/withLabel:publish_finalblocks in that pipeline's own config —viral_inference_benchmark/nextflow_pipeline/conf/base.configis the reference implementation (see that repo's own notebook for the config-side details). Verified the mechanism empirically before trusting it, in isolated scratch tests: apublishDirclosure defined in a configwithLabel:block can see a process's ownsampleid, even defined in a different file;task.processresolves fully-qualified with its subworkflow path (e.g."s_i_mg_readqc:trimmomatic"), not the bare name, so dynamic per-tool lookup strips it (task.process.tokenize(':').last()); a config file can't define a top-leveldef function(){}(same restriction class asif/exitnot being valid top-level config statements) nor a multi-statement closure body (def x = ...then a second line usingxfails with "x is not defined") — the dispatch logic had to be one Closure, one expression. - Scoped this first to the 6 processes
viral_inference_benchmarkactually uses (bbduk,trimmomatic,megahit,genomad,checkv,pileup), with each process'spublish_intermediate/publish_finaltier confirmed by the user per-process, not guessed (bbduk=intermediate; the other 5=final). This surfaced a real naming inconsistency:pileup's outdir param wasbbmap_outdir(file-name-based) while every other process used<processname>_outdir— renamed topileup_outdir(same class of fix as thekraken2.nfcontainer__kraken/kraken_outdirrename earlier this session). - Updated
modules/CLAUDE.mdto make the label-based pattern the standard going forward (not just an alternative), keeping the older in-module Closure-ternary form as a documented, required exception for any process with nosampleidin scope (index builds, cross-sample merges, whole-batch/run steps — referencingsampleidin the centralized dispatch is a hard compile error for these). UpdatedTEMPLATE.nfto match. - Before extending this repo-wide, flagged a real concern rather than proceeding
unilaterally: this repo has no pipeline/config of its own, so stripping
publishDirfrom every remaining process in favor of just a label would mean any consumer that hasn't adopted the samewithLabel:convention gets nothing published at all, silently. Checked empirically: no other repo on this machine currently consumesnextflow_modulesat all. The user clarifiedviral_inference_benchmarkis deliberately the reference architecture every future pipeline in the lab will replicate, which resolved the concern. - Ran a 4-way parallel sweep (subagents) across every remaining eligible file:
70 files, 130 processes total now carry
publish_intermediate(11) orpublish_final(119) — the original 6 plus 124 more. Confirmed via repo-wide grep that zeroinstanceof Closureoccurrences remain anywhere inmodules/*.nf(every eligible process converted); ~27 files' processes correctly kept the old in-module form (nosampleidin scope) and were left untouched;modules/dev/untouched throughout. Tier classification used a stated heuristic (would a researcher want this file independent of the pipeline, or does it only feed the next same-file process) with precedent-matching against the first 6 and against comparable tools already classified elsewhere — flagged a handful of thinner-reasoning calls rather than presenting everything as equally confident:cutadapt.nfandsourmash.nf's hash step (intermediate, reasoned by analogy without a confirmed downstream consumer),kraken2.nf'skreport_to_json(final, but it's a re-export of already-published data),nanosim.nf'snanosim_read_analysis(final, but its own comment says the real downstream consumer doesn't exist yet),bbmerge.nf/fastp.nf(depend on whether a future pipeline chains them further),pilon.nf(final, correct only if it's the pipeline's last polishing step),hmmer.nf'shmmbuildandgatk.nf's filter/annotate split (no visible channel wiring in this repo to confirm against). - Full verification: all 88 modified files brace-balanced; every touched file
passed a clean
nextflow run <file>.nfstandalone syntax check;git statusconfirmed exactly 88Mentries, zero deletions, zero untracked files, zero leftover test artifacts (one sharedmodules/workdir needed a second removal attempt after all forks finished). - Next: the handful of thinner-reasoning tier calls listed above are worth a second look once real pipelines start using those tools. None of this session's changes are committed yet.