Install any skill in seconds. Free to start, no credit card required.
Get Started Free →End-to-end somatic variant calling from tumor-normal paired samples using Mutect2 or Strelka2. Covers preprocessing, variant calling, filtering, and annotation for cancer genomics. Use when calling somatic mutations from tumor-normal pairs.
.claude/skills/bio-workflows-somatic-variant-pipeline/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 473% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 82% | 0% |
| case-17 | ✓→✗ | ▼ Worse | 197% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 100% | 0% |
| case-10 | ✓→✓ | = Same ✓ | 158% | 0% |
<!--
#
#
-->
Complete workflow for calling somatic mutations from tumor-normal paired samples.
Tumor BAM + Normal BAM
│
├── Preprocessing (if needed)
│ └── MarkDuplicates, BQSR
│
├── Variant Calling
│ ├── Mutect2 (GATK) - SNVs + indels
│ └── Strelka2 - SNVs + indels (faster)
│
├── Filtering
│ ├── FilterMutectCalls
│ ├── Contamination estimation
│ └── Orientation bias filtering
│
├── Annotation
│ ├── Funcotator / VEP
│ └── Cancer-specific databases
│
└── Output: Filtered somatic VCFbash# Create PON from multiple normal samples for normal in normal1.bam normal2.bam normal3.bam; do sample=$(basename $normal .bam) gatk Mutect2 \ -R reference.fa \ -I $normal \ --max-mnp-distance 0 \ -O ${sample}.vcf.gz done # Combine into PON gatk GenomicsDBImport \ -R reference.fa \ --genomicsdb-workspace-path pon_db \ -V normal1.vcf.gz \ -V normal2.vcf.gz \ -V normal3.vcf.gz \ -L intervals.bed gatk CreateSomaticPanelOfNormals \ -R reference.fa \ -V gendb://pon_db \ -O pon.vcf.gz
bashgatk Mutect2 \ -R reference.fa \ -I tumor.bam \ -I normal.bam \ -normal normal_sample_name \ --germline-resource af-only-gnomad.vcf.gz \ --panel-of-normals pon.vcf.gz \ --f1r2-tar-gz f1r2.tar.gz \ -O unfiltered.vcf.gz
bashgatk LearnReadOrientationModel \ -I f1r2.tar.gz \ -O read-orientation-model.tar.gz
bashgatk GetPileupSummaries \ -I tumor.bam \ -V small_exac_common.vcf.gz \ -L small_exac_common.vcf.gz \ -O tumor_pileups.table gatk GetPileupSummaries \ -I normal.bam \ -V small_exac_common.vcf.gz \ -L small_exac_common.vcf.gz \ -O normal_pileups.table gatk CalculateContamination \ -I tumor_pileups.table \ -matched normal_pileups.table \ -O contamination.table \ --tumor-segmentation segments.table
bashgatk FilterMutectCalls \ -R reference.fa \ -V unfiltered.vcf.gz \ --contamination-table contamination.table \ --tumor-segmentation segments.table \ --ob-priors read-orientation-model.tar.gz \ -O filtered.vcf.gz # Extract PASS variants bcftools view -f PASS filtered.vcf.gz -Oz -o somatic_final.vcf.gz
bash# Configure configureStrelkaSomaticWorkflow.py \ --normalBam normal.bam \ --tumorBam tumor.bam \ --referenceFasta reference.fa \ --runDir strelka_run # Execute strelka_run/runWorkflow.py -m local -j 16 # Output files # strelka_run/results/variants/somatic.snvs.vcf.gz # strelka_run/results/variants/somatic.indels.vcf.gz # Merge SNVs and indels bcftools concat \ strelka_run/results/variants/somatic.snvs.vcf.gz \ strelka_run/results/variants/somatic.indels.vcf.gz \ -a -Oz -o strelka_somatic.vcf.gz
bashgatk Funcotator \ -R reference.fa \ -V somatic_final.vcf.gz \ -O annotated.vcf.gz \ --output-file-format VCF \ --data-sources-path funcotator_dataSources.v1.7 \ --ref-version hg38
bashvep -i somatic_final.vcf.gz -o annotated.vcf \ --vcf --cache --offline \ --assembly GRCh38 \ --everything \ --plugin CADD,cadd_scores.tsv.gz \ --custom cosmic.vcf.gz,COSMIC,vcf,exact,0,CNT \ --fork 4
bash#!/bin/bash set -euo pipefail TUMOR_BAM=$1 NORMAL_BAM=$2 NORMAL_NAME=$3 REFERENCE=$4 OUTPUT_PREFIX=$5 GNOMAD=$6 PON=$7 THREADS=16 echo "=== Step 1: Mutect2 calling ===" gatk Mutect2 \ -R $REFERENCE \ -I $TUMOR_BAM \ -I $NORMAL_BAM \ -normal $NORMAL_NAME \ --germline-resource $GNOMAD \ --panel-of-normals $PON \ --f1r2-tar-gz ${OUTPUT_PREFIX}_f1r2.tar.gz \ --native-pair-hmm-threads $THREADS \ -O ${OUTPUT_PREFIX}_unfiltered.vcf.gz echo "=== Step 2: Learn orientation bias ===" gatk LearnReadOrientationModel \ -I ${OUTPUT_PREFIX}_f1r2.tar.gz \ -O ${OUTPUT_PREFIX}_orientation.tar.gz echo "=== Step 3: Pileup summaries ===" gatk GetPileupSummaries \ -I $TUMOR_BAM \ -V $GNOMAD \ -L $GNOMAD \ -O ${OUTPUT_PREFIX}_tumor_pileups.table gatk GetPileupSummaries \ -I $NORMAL_BAM \ -V $GNOMAD \ -L $GNOMAD \ -O ${OUTPUT_PREFIX}_normal_pileups.table echo "=== Step 4: Calculate contamination ===" gatk CalculateContamination \ -I ${OUTPUT_PREFIX}_tumor_pileups.table \ -matched ${OUTPUT_PREFIX}_normal_pileups.table \ -O ${OUTPUT_PREFIX}_contamination.table \ --tumor-segmentation ${OUTPUT_PREFIX}_segments.table echo "=== Step 5: Filter variants ===" gatk FilterMutectCalls \ -R $REFERENCE \ -V ${OUTPUT_PREFIX}_unfiltered.vcf.gz \ --contamination-table ${OUTPUT_PREFIX}_contamination.table \ --tumor-segmentation ${OUTPUT_PREFIX}_segments.table \ --ob-priors ${OUTPUT_PREFIX}_orientation.tar.gz \ -O ${OUTPUT_PREFIX}_filtered.vcf.gz echo "=== Step 6: Extract PASS variants ===" bcftools view -f PASS ${OUTPUT_PREFIX}_filtered.vcf.gz \ -Oz -o ${OUTPUT_PREFIX}_somatic.vcf.gz bcftools index -t ${OUTPUT_PREFIX}_somatic.vcf.gz echo "=== Step 7: Statistics ===" bcftools stats ${OUTPUT_PREFIX}_somatic.vcf.gz > ${OUTPUT_PREFIX}_stats.txt echo "=== Pipeline complete ===" echo "Somatic variants: ${OUTPUT_PREFIX}_somatic.vcf.gz" echo "Stats: ${OUTPUT_PREFIX}_stats.txt"
When matched normal is unavailable:
bashgatk Mutect2 \ -R reference.fa \ -I tumor.bam \ --germline-resource af-only-gnomad.vcf.gz \ --panel-of-normals pon.vcf.gz \ -O tumor_only.vcf.gz
Note: Higher false positive rate without matched normal.
| Resource | Purpose | |----------|---------| | gnomAD AF-only | Germline filtering | | Panel of Normals | Technical artifact removal | | COSMIC | Known cancer mutations | | Funcotator data sources | Functional annotation |
bash# Variant counts by filter status bcftools query -f '%FILTER\n' filtered.vcf.gz | sort | uniq -c # Ti/Tv ratio (expect ~2-3 for somatic) bcftools stats filtered.vcf.gz | grep TSTV # Variant allele frequency distribution bcftools query -f '%AF\n' somatic_final.vcf.gz | \ awk '{print int($1*100)/100}' | sort -n | uniq -c
<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→pass | 8,588 | 5,238 | -39% | 1 | 1 | 0% | 1,767 | 3,526 | +100% | 0 | 0 | — |
case-10 | pass→pass | 6,325 | 3,247 | -49% | 1 | 1 | 0% | 1,208 | 3,113 | +158% | 0 | 0 | — |
case-20 | pass→pass | 5,691 | 2,389 | -58% | 1 | 1 | 0% | 1,021 | 2,937 | +188% | 0 | 0 | — |
case-15 | pass→pass | 14,201 | 11,794 | -17% | 1 | 1 | 0% | 2,721 | 4,861 | +79% | 0 | 0 | — |
case-01 | fail→fail | 18,850 | 16,999 | -10% | 1 | 1 | 0% | 3,655 | 6,153 | +68% | 0 | 0 | — |
case-02 | pass→pass | 17,671 | 15,034 | -15% | 1 | 1 | 0% | 3,467 | 5,596 | +61% | 0 | 0 | — |
case-03 | fail→fail | 10,300 | 8,942 | -13% | 1 | 1 | 0% | 1,992 | 4,289 | +115% | 0 | 0 | — |
case-04 | pass→pass | 7,023 | 4,555 | -35% | 1 | 1 | 0% | 1,405 | 3,382 | +141% | 0 | 0 | — |
case-06 | pass→pass | 5,481 | 3,086 | -44% | 1 | 1 | 0% | 1,012 | 3,069 | +203% | 0 | 0 | — |
case-07 | fail→pass | 2,952 | 3,554 | +20% | 1 | 1 | 0% | 542 | 3,106 | +473% | 0 | 0 | — |
case-08 | pass→pass | 7,378 | 4,279 | -42% | 1 | 1 | 0% | 1,528 | 3,397 | +122% | 0 | 0 | — |
case-09 | pass→pass | 5,803 | 3,234 | -44% | 1 | 1 | 0% | 1,075 | 3,086 | +187% | 0 | 0 | — |
case-11 | pass→pass | 7,831 | 6,035 | -23% | 1 | 1 | 0% | 1,484 | 3,677 | +148% | 0 | 0 | — |
case-12 | pass→pass | 4,654 | 2,809 | -40% | 1 | 1 | 0% | 958 | 3,067 | +220% | 0 | 0 | — |
case-13 | pass→pass | 4,663 | 2,499 | -46% | 1 | 1 | 0% | 877 | 3,038 | +246% | 0 | 0 | — |
case-14 | pass→pass | 11,675 | 5,832 | -50% | 1 | 1 | 0% | 2,333 | 3,704 | +59% | 0 | 0 | — |
case-16 | fail→pass | 10,816 | 5,954 | -45% | 1 | 1 | 0% | 1,946 | 3,532 | +82% | 0 | 0 | — |
case-17 | pass→fail | 6,656 | 4,461 | -33% | 1 | 1 | 0% | 1,126 | 3,343 | +197% | 0 | 0 | — |
case-18 | pass→pass | 5,266 | 3,279 | -38% | 1 | 1 | 0% | 874 | 3,120 | +257% | 0 | 0 | — |
case-19 | pass→pass | 4,469 | 2,194 | -51% | 1 | 1 | 0% | 854 | 2,881 | +237% | 0 | 0 | — |
case-21 | pass→pass | 7,470 | 5,126 | -31% | 1 | 1 | 0% | 1,449 | 3,436 | +137% | 0 | 0 | — |
case-22 | pass→pass | 3,362 | 2,027 | -40% | 1 | 1 | 0% | 637 | 2,849 | +347% | 0 | 0 | — |
case-23 | pass→pass | 3,638 | 3,551 | -2% | 1 | 1 | 0% | 696 | 2,901 | +317% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +4 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/26/2026 | +5% |
Other measured skills in the registry, with their headline benchmark lift.