analyze-stats — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited analyze-stats (Agent Skill) and scored it 91/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} is the classic direct prompt-injection phrasing. Placed in a skill body that the agent reads as trusted instructions, it tries to make the agent abandon its prior rules and follow whatever comes next — a full system-prompt override.
ignore/disregard/forget … previous instructions sentence.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Generate reproducible code (Python preferred, R when necessary) for medical research analyses, run it, and produce publication-ready tables and figures from its output.
Before reading any data file, check whether it might contain Protected Health Information (PHI):
*_deidentified.* files exist in the working directory, use those.(names, national ID / RRN, contact details); if so, have them de-identify it first with /deidentify.
output is copied into transcripts and drafts. If you encounter them, warn the user and suggest /deidentify.
${CLAUDE_SKILL_DIR}/references/templates/ — reusable analysis scripts; read the matching onebefore generating code.
${CLAUDE_SKILL_DIR}/references/analysis_guides/ — methodology guides; the Phase 2 table sayswhich to read.
${CLAUDE_SKILL_DIR}/references/table-standards/ — table-standards.md (universal and AMArules, footnote order, gtsummary pipeline), journal-profiles/ (YAML per journal: radiology, jama, nejm, lancet, european_radiology, ajr), table-types/ (one template per table type), tool-comparison.md (R/Python table tools).
${CLAUDE_SKILL_DIR}/references/style/figure_style.mplstyle — figure style.categorical, ordinal, binary, datetime); missing values per column (count and percentage); first 5 rows; unique value counts for categorical columns.
[VERIFY: variable_name] and ask the user toconfirm it against the data dictionary; never guess a column name or coding.
Observational designs (cohort, case-control, cross-sectional, registry, survey): before planning, look for a variable_operationalization.md from /define-variables (or an equivalent codebook-backed definition table). If none exists, warn the user and recommend running /define-variables first, because exposure/outcome/covariate definitions and cutoffs invented ad hoc from the data dictionary are a common reason reviewers reject observational work. This is a warning, not a block: proceed on explicit user confirmation and record that the artifact was not available.
Propose an analysis plan; the user decides the research question and endpoints. Mark any clinical definition, cutoff, diagnostic criterion or guideline claim you could not confirm [VERIFY].
user's specification.
parameter itself (a likelihood-ratio test of the interaction term, or the interaction OR/HR on one stated scale) — not a main-effect OR whose CI is then read as "no synergy". A public-health or biological synergy claim is additive-scale: report RERI, AP or S**, each with a CI (R interactionR / epiR; Knol & VanderWeele reporting), not only a multiplicative product term, because a non-significant multiplicative interaction is compatible with a large additive one (and vice versa). Joint categories (high/high vs low/low) show joint association, not interaction; "stronger in A than in B" from separate stratum estimates is the difference-in-significance fallacy — report the formal interaction term.
compared against a pre-stated MCID); a non-significant difference is not equivalence without a margin.
predictor that perfectly predicts the outcome has no finite MLE, and the failure is silent: glm returns an odds ratio near 0 (or enormous), p ≈ 0.99, and an AUC that ends up in a table. Pathognomonic imaging signs (T2-FLAIR mismatch, the string sign, a halo sign) cause it routinely: 100% specificity means an empty cell by construction.
python3 "${CLAUDE_SKILL_DIR}/scripts/check_separation.py" \
--data cohort.csv --outcome idh_mutant --auto --strictCOMPLETE_SEPARATION (an empty cell) and QUASI_SEPARATION (a cell below the sparsity floor) both halt the plan. With two or more predictors the script also tests them jointly (a linear combination can separate the outcome when no single predictor does); that test needs scipy, and without it the report says the joint check was not run. The remedy is a design decision, made in the plan: Firth's penalised likelihood keeps one model, while a two-stage rule — classify the sign-positive cases directly, model only the sign-negative remainder — is usually the clinically meaningful choice for a pathognomonic sign, because a sign-positive patient is already diagnosed.
Present the plan and wait for user approval before executing.
Generate and run a Python (preferred) or R script. Every number you report MUST come from its executed output, never estimated or hand-typed, because a number no script reproduces cannot be verified.
#### Script Structure
Start every script with a reproducibility header:
"""
Analysis: {description}
Date: {YYYY-MM-DD}
Random seed: 42
Python: {version}
Key packages: {package==version, ...}
"""
import numpy as np
import pandas as pd
np.random.seed(42)#### Execution Rules
np.random.seed(42) or set.seed(42). import matplotlib.pyplot as plt
style_path = os.path.join(os.environ.get('CLAUDE_SKILL_DIR', '.'), 'references/style/figure_style.mplstyle')
if os.path.exists(style_path):
plt.style.use(style_path)PDF (vector) and PNG (300 DPI).
direct copy-paste into the Results section.
#### Assumption Checking
Choose the summary and test from the design and the distribution, not a preliminary test:
|skewness| > 1 → median (IQR) with arank test, else mean (SD) (table-types/table1_demographics.md). Never gate on a Shapiro-Wilk or KS P value: it flags trivial departures at large n, misses real ones at small n, and test-then-test inflates the type I error (Rochon et al. 2012).
tests a different hypothesis and does not fix them. State the choice and reason in Methods.
#### Stratified & Ordinal-Trend Reporting
test (or any analysis that treats tiers as an ordered partition), assert that the strata are mutually exclusive and exhaustive: sum(n per stratum) == unique N and sum(events per stratum) == total events. A trend test on overlapping or non-exhaustive strata is invalid. Print the per-stratum N/event table and the reconciliation (the analysis-side mirror of /self-review check_cohort_arithmetic.py PARTITION_OVERLAP).
category), (b) the event count in each stratum, and (c) a sparse-stratum caveat when any stratum has few events (< 10 is unstable). "HR 1.55 in lean participants" without the referent and the events is uninterpretable.
max(0, lower). A zero-eventWilson/score interval can print a negative or tiny-exponent bound (e.g. 3.47e-16) that is a display artifact; report 0 (or 0.0%), and prefer an exact (Clopper-Pearson) interval for zero/near-zero cells.
#### Output Manifest
After all analyses complete, save _analysis_outputs.md in the output directory, in the format given in references/analysis_run_workflow.md, so /make-figures and /write-paper can find the outputs without asking.
For prespecified binary predictions on independent units, use the bundled scripts/run_analysis.py run workflow in that file: it embeds data/configuration/code/output hashes, exact counts, metric-specific denominators and the reproduction command in the same manifest (audit checks recorded versions without rewriting them; compare separates declared context and numeric equality from byte drift). It does not select thresholds or establish study validity, privacy clearance or reuse rights. Synthetic example: python3 ${CLAUDE_SKILL_DIR}/scripts/demo_analysis_run.py --out demo-project.
Before reporting any script as final, lint every emitted .py/.R file:
python3 ${CLAUDE_SKILL_DIR}/scripts/check_generated_code.py {script.py} --strict
# or scan a whole output directory:
python3 ${CLAUDE_SKILL_DIR}/scripts/check_generated_code.py --code-dir {analysis_dir} --strictFix every Major before reporting the script: MISSING_SEED (randomness with no seed), HARDCODED_DATA_LITERAL (hand-typed, table-shaped data instead of read_csv()/read.csv() + subset), HARDCODED_ABS_PATH (non-portable and a PII risk), and INPLACE_SOURCE_OVERWRITE (writing to the path read as input — never modify raw data; write derived outputs to a new path). Fix the flags DEBUG_LEFTOVER and UNUSED_IMPORT when tidying. Minor, Python only: API_DEFAULT_STUDENT_T (scipy's ttest_ind without equal_var is Student's t; state Welch or the reason for Student) and API_DEFAULT_PENALIZED_OR (LogisticRegression with none of penalty, C, l1_ratio, in a file that exponentiates a coef_). R's t.test() is Welch by default.
After execution, generate manuscript-ready text from the script's output:
section. Cite only references whose DOI/PMID /search-lit confirmed; mark any other [UNVERIFIED - NEEDS MANUAL CHECK].
Report statistical results; leave judgments of clinical significance to the user. For designs this skill does not cover (adaptive or Bayesian trials, complex multilevel or causal-mediation models), say so and recommend biostatistician review before the results are reported.
report p < 0.001.
risk ratio, etc., as appropriate).
Benjamini-Hochberg correction, state the method, and report both uncorrected and corrected p-values.
precision of the measurement.
effect (MDE), a-priori power, or required sample size that will appear in the manuscript MUST be printed by the committed script with its method and inputs (n per arm, alpha, power, allocation ratio, one/two-sided), not computed in a side tool (GPower, an online calculator) and pasted in, because a manuscript value no script reproduces cannot be checked. Use one method family throughout (e.g. the exact noncentral-t via `statsmodels` `TTestIndPower` or `scipy`'s `nct`); do not mix a normal approximation for some values with exact-t for others. Do not report post-hoc (observed) power*: it is a function of the P value and adds nothing (Hoenig & Heisey 2001); report the CI of the effect instead.
estimands (T25, median time-to-event), pooled proportions and subdistribution HRs, not just ORs/HRs/AUCs — MUST be emitted with its 95% CI, because /self-review treats a primary metric without one as a defect. In the output CSV carry the interval as explicit columns (estimate, ci_lower, ci_upper) or as one text column in est (lo–hi) form; never a point estimate with no interval beside it. Round ORs/HRs/sHRs to 2 decimals and AUC/C-statistic to 3.
Whenever a primary result is a correlation, a standardized coefficient, a regression slope, an OR/HR/RR or a Cohen's d, also report it as a plain-language unit shift a non-statistician can act on — in addition to the effect size (rule 3), not instead of it. This applies to continuous exposure–outcome associations (Spearman's rho, Pearson's r, standardized slopes), to relative measures where the audience needs an absolute-risk feel, and to reader or expert-elicitation studies, clinical-utility framing, abstracts and figure captions.
percentile (IQR), both endpoints in native units.
delta_outcome = b * (x_p75 - x_p25). Keep the sign:a negative b is a decrease.
delta_outcome = beta * (x_p75 - x_p25) / SD_x; afully standardized beta is also multiplied by SD_outcome.
data (b = r * SD_outcome / SD_x), which is the mean change per unit only if the relation is linear; Spearman's rho converts to no change in outcome units. Fit the outcome on the exposure and translate that slope (Theil-Sen if outliers are why Spearman was used); for a monotonic but curved association, report the fitted outcome at x_p25 and at x_p75 instead.
{outcome units} on average", and state the linearity assumption behind it.
imply causation from a crude or unadjusted estimate.
Output contract (clinical utility is a default, not an optional add-on):
baseline, the absolute risk difference, and NNT = 1/ARR (or NNH = 1/ARI), with the baseline risk explicit. A relative-only headline is incomplete.
the effect size.
calibration plot, or calibration slope/intercept) alongside discrimination, because AUC alone is insufficient; a decision-curve / net-benefit pass at the relevant threshold is standard output. An incremental claim reports the added-value test on the new term (likelihood ratio / Wald in the nested model), ΔC-statistic with its CI and Δnet benefit over the established clinical model, not the new model's AUC alone; NRI only as the categorical, event/non-event-split version, and IDI with caution. See references/table-standards/table-types/incremental_value.md and the make-figures decision_curve exemplar (and render_core_figures.py for the rendered curve).
column name) and present a fix. Do not rerun the same script more than once without modifying it or asking the user.
install.packages() and wait for user confirmation.Code, tables, figures and console output are in English.
Before generating any publication table, load:
${CLAUDE_SKILL_DIR}/references/table-standards/journal-profiles/{journal}.yaml if a targetjournal is known (it sets footnote markers, P and CI format, title and abbreviation order)
${CLAUDE_SKILL_DIR}/references/table-standards/table-types/{type}.md for the table typeOutput formats (always generate all three): a CSV file (downstream use and archival), a console markdown rendering (user review), and R gtsummary code (Word/LaTeX export). Read ${CLAUDE_SKILL_DIR}/references/table-standards/table-standards.md for the gtsummary pipeline and the footnote placement order (general note, abbreviations, specific notes, probability notes).
Validation checklist (run before finalizing any table):
PDF (vector) + PNG (300 DPI), styled with figure_style.mplstyle (Arial, colorblind-safe palette); width 3.5 inches (single column) or 7.0 inches (double column); axis labels with units.
Before generating code for any row, read the files listed for it (paths relative to ${CLAUDE_SKILL_DIR}); they carry the method rules this file does not repeat.
| Analysis type | Read before generating code | Template |
|---|---|---|
| Table 1 (demographics) | references/table-standards/table-types/table1_demographics.md | references/templates/table1_demographics.py |
| Diagnostic accuracy (Se, Sp, PPV, NPV, AUC) | references/analysis_guides/diagnostic_accuracy.md | references/templates/diagnostic_accuracy.py |
| Added value beyond a baseline model (ΔAUC, NRI/IDI, net benefit) | references/table-standards/table-types/incremental_value.md | — |
| Reader study (MRMC) | references/table-standards/table-types/reader_study.md | — |
| Prediction model (outputs a risk used for a decision) | references/analysis_guides/calibration.md | — |
| Inter-rater agreement (kappa, ICC, Bland–Altman) | references/analysis_guides/agreement_reliability.md, references/table-standards/table-types/agreement.md | references/templates/agreement_analysis.py |
| Meta-analysis (pairwise, single-arm proportion, DTA) | references/analysis_guides/meta_analysis.md | references/templates/dta_meta_analysis.R (DTA) |
| Network meta-analysis (≥3 interventions) | references/analysis_guides/network_meta_analysis.md | — |
| Health economic evaluation (CEA, CUA, budget impact) | references/analysis_guides/health_economic_evaluation.md | — |
| Survey/Likert (ordinal rating scales) | the Survey/Likert section below | references/templates/likert_summary.py |
| Survival, competing risks, interval-censored events | references/analysis_guides/survival.md, references/table-standards/table-types/survival_results.md | references/templates/survival_analysis.py (--cluster <id> for nested units) |
| Group comparison, correlation | references/analysis_guides/test_selection.md | — |
| Logistic or linear regression | references/analysis_guides/regression.md | references/templates/regression.py (regression_type = "logistic" or "linear") |
| Propensity score (PSM, IPTW, SIPTW, overlap weighting) | references/analysis_guides/propensity_score.md | references/templates/propensity_score.py |
| Survey-weighted (KNHANES, NHANES, KCHS) | references/analysis_guides/survey_weighted.md | references/templates/survey_weighted_analysis.py |
| NHIS claims-based studies (ICD-10 definitions) | references/analysis_guides/nhis_icd10_mapping.md | — |
| Repeated measures (LMM, GEE, RM ANOVA) | references/analysis_guides/repeated_measures.md; for missing data see references/analysis_guides/missing_data.md (an LMM already uses every observed outcome under MAR, so missing outcomes alone do not call for MICE) | references/templates/repeated_measures.py |
| Mediation | references/analysis_guides/mediation.md | — |
| Multiple testing, many-exposure scans (ExWAS/EWAS) | references/analysis_guides/multiplicity.md | — |
| Mendelian randomization | references/analysis_guides/mendelian_randomization.md | — |
| Polygenic risk score | references/analysis_guides/polygenic_risk_score.md | — |
| Burden of disease, decomposition, forecasting | references/analysis_guides/burden_decomposition_forecasting.md | — |
Comparing models: use a paired DeLong test for two separate scores on the same patients. For a nested "baseline + new term" model fitted and evaluated on the same data, DeLong is not the added-value test — test the term (likelihood ratio) and report ΔAUC with its CI.
The rules below have no separate guide.
or Kruskal-Wallis (ordinal data). Visualization: diverging stacked bar chart.
(min+max) - x before computing the scale total or Cronbach's alpha. An un-recoded reverse item produces a negative item-rest correlation and can make alpha negative — a coding bug, not evidence of a multidimensional construct; do not defend it as one. likert_summary.py prints per-item item-rest correlations, flags negative ones as reverse-code suspects, warns on a negative alpha, and applies the recode with --reverse-items E3 .... To screen at cleaning time, run python3 "${CLAUDE_SKILL_DIR}/../clean-data/scripts/check_reverse_coding.py".
Applies to any multivariable adjustment (logistic / linear / Cox / propensity score / survey- weighted) with a dose/duration variable anchored to a categorical exposure (pack-years under smoking status, grams/week under alcohol use, cessation duration under former smoker):
pack_years is a *structuralzero* — known to be 0 by definition of the category, not missing. Imputing it (MICE/MNAR) fabricates a dose for unexposed subjects and corrupts the exposure contrast. Before imputing a dose/duration column, set the implied zero explicitly (IF status == 'never' THEN dose = 0) and impute only the genuinely missing residual among the exposed. /clean-data flags never rows with a NULL dose (scripts/check_structural_zero.py).
model drops the unexposed stratum wholesale when its zeros are stored as NULL, collapsing n (commonly 40–60%) and distorting subgroup estimates. Adjust for the categorical status (never/former/current); reserve the continuous dose for an exposed-only secondary analysis. Report n before and after model fitting and confirm the denominator did not silently collapse.
Applies to any cross-sectional / single-visit outcome regression (temporal order is not observed). Select covariates causally, not statistically:
collider bias and removes part of the effect under study. Signature case: with eGFR as the outcome, serum uric acid is renally excreted (a lower eGFR raises urate), so it is an outcome-consequence, not a confounder; blood pressure and HbA1c are often similarly downstream. Classify each candidate covariate against a DAG as confounder / mediator / outcome-consequence / collider, and keep only confounders in the primary model.
considering a variable; a mediator or outcome-consequence stays out however imbalanced it is.
model primary; report the fuller model as a sensitivity analysis that drops (or adds) the suspect covariate and show whether the headline estimate moves. Print VIF and the n actually fitted. If dropping the covariate materially changes the estimate, carry that to the abstract and conclusion.
with missingness, the analytic n shrinks (e.g. 84 → 49 events), and comparing that estimate with the full-frame unadjusted one confounds adjustment with who was dropped. Refit the unadjusted model on the reduced complete-case frame and report unadjusted and adjusted on that frame beside the full-frame estimate; never describe "adjustment changed the estimate" from a comparison across different frames (or use multiple imputation so all models share one frame).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.