Skip to content
arjunrajlaboratoryPublic

About

Scientific code lint for R and Python analysis projects — agent-first design, structured ANALYSIS_OK waivers, 40 rules

Resources

Stars

13 stars

Watchers

0 watching

Forks

Repository files navigation

scilintr

Scientific lint for R and Python analysis code, plus LaTeX scientific reports.

Flags patterns that often correspond to hidden scientific commitments:

  • silent error swallowing (tryCatch(..., error = function(e) NA))
  • smuggled function-signature defaults (top_modules = 25L)
  • label leakage in selection-stage code
  • magic-eps floors in log/BIC formulas
  • shadow-overwrite of sourced helpers
  • "best of either side" reporting against a label
  • silent absence propagation (if (!file.exists(p)) return(NULL))
  • numeric drift between an analysis result and its written-up value in the report (a manifest-anchored check on the .tex source)
  • ...and ~50 other rules across the three packages.

Designed for agentic coding workflows: high recall over precision, structured ANALYSIS_OK[category]: waivers as the audit trail. Many findings will be legitimate; an LLM agent (or its reviewer) reads each finding and either fixes the pattern or adds a structured waiver.

What's in this monorepo

scilintr/
├── analysis_lint_strategy.md   <- the rule spec; language-agnostic
├── docs/
│   └── failure-modes.md        <- catalogued bugs each rule catches
├── r/scilintr/                 <- R package (v0.1.1, 44 rules, 161 tests)
│   └── ...
├── py/scilintr/                <- Python package (v0.1.2, 27 rules, 139 tests)
│   └── ...
└── tex/scitexlintr/            <- LaTeX + HTML report linter (v0.2.1, 13 rules, 214 tests)
    └── ...

The R and Python packages implement the same analysis-code rule spec from analysis_lint_strategy.md and share the structured-waiver mechanism, the Finding record schema, and the CLI surface. They are deliberately not 1:1: the R package has more rules because most of the leakage-driven failure modes (R029 read.csv mangling, R030 silent tryCatch, R-specific shadow-overwrite and scriptwise cross-file drift) come from R-style research codebases. The Python package covers the universal scientific-code failure modes plus a few Python-specific ones (runtime-assert, unconsumed-cli-flag, unvalidated-config, sentinel-mask-assignment). See the "Implementation status" section of analysis_lint_strategy.md for the full rule-code cross-reference table.

The tex/scitexlintr/ package is a sibling, not a port: it lints the .tex or .html source of scientific reports against a manifest of registered values, figures, and terms. Its rule catalog (snapshot drift, unfingerprinted figures, handwritten numeric claims, forbidden aliases, …) is disjoint from the analysis-code rules — they catch drift between the analysis and its writeup, not bugs inside the analysis. It uses the same structured waiver convention, as a TeX comment (% ANALYSIS_OK[rule]:) or an HTML comment (<!-- ANALYSIS_OK[rule]: … -->). See tex/scitexlintr/README.md for the full catalog and the wrapper-macro / manifest contract.

Install

R

install.packages("scilintr")   # from CRAN

# Or the development version:
# remotes::install_github("arjunrajlaboratory/scilintr", subdir = "r/scilintr")

# In a project root:
scilintr::lint_project(".")

CLI:

Rscript -e 'scilintr::main()' path/to/project

Python

pip install scilintr   # from PyPI

CLI:

scilintr path/to/analysis/                       # lint a file or directory
scilintr --rules broad-exception path/to/file.py # restrict to a rule
scilintr --summary path/to/dir/                  # per-rule counts only
scilintr --no-waivers path/to/file.py            # audit mode

Library:

from scilintr import lint_code, lint_paths, Finding
findings = lint_code(source_string, filename="foo.py")
findings = lint_paths(["path/to/dir/"])

LaTeX and HTML reports (scitexlintr)

pip install "scitexlintr>=0.2"   # from PyPI; the HTML frontend needs 0.2.0

scitexlintr is a separate package with its own console script. Installing scilintr (the code linter) does not install it, and vice versa. A project that lints both code and reports installs both: pip install scilintr scitexlintr.

CLI:

scitexlintr report.tex --manifest=.manifest.json   # lint a report
scitexlintr report.tex                              # manifest-free rules only
scitexlintr report.tex --manifest=... --write       # auto-fix snapshot drift
scitexlintr report.tex --no-waivers                 # audit mode
scitexlintr report.tex --rules=snapshot-mismatch    # restrict to specific rules
scitexlintr report.html --manifest=... --write      # HTML report: same rules and flags

Library:

from scitexlintr import lint_tex, lint_file, load_manifest, apply_fixes
manifest = load_manifest(".manifest.json")
findings = lint_tex(source_string, filename="report.tex", manifest=manifest)

Design point: agent-first

This linter is tuned for LLM-driven workflows where the cost of reading a finding and deciding "fix or waive" is near zero. That changes the cost-benefit math compared to a human-targeted linter:

  • High recall over precision. Flag anything that might be a scientifically meaningful choice.
  • Structured waivers as audit trail. Each accepted "false positive" becomes an ANALYSIS_OK[category]: <reason> comment — machine-grepable and human-readable.
  • Recall failures cost more than precision failures. A missed bug ships; a flagged-but-fine line is a 10-second triage.

Humans can use it too — the waiver mechanism helps either way.

How it works

For each .R (or .py) file in a project, scilintr walks the AST, matches each rule's pattern (via xml2/XPath for R, ast for Python), and emits a Finding record. The orchestrator then filters findings covered by a nearby ANALYSIS_OK[category]: comment.

Cross-file rules (shadow-overwrite, definition drift, dead code) walk every file once to build a project index, then run rules against the index.

For .tex reports, scitexlintr parses the source with a small TeX-aware scanner (comments stripped while preserving byte offsets, verbatim environments skipped, balanced-brace argument extraction), builds a "prose mask" that excludes structural-macro arguments (\label, \ref, \cite, \includegraphics, the wrapper macros themselves), and runs each rule over the prose. Snapshot drift, raw generated values, unfingerprinted figures, and unsourced numeric claims are then matched against the manifest's published contract. For .html reports, a stdlib html.parser frontend builds the same offset-preserving prose view (tags, comments, scripts, code, and value wrappers excluded; character references decoded in place), so every rule runs unchanged on both formats.

Status

  • R package (v0.1.1) — 44 rules implemented (R001–R044, including R020/R025/R026 cross-file). 161 testthat fixtures passing. R CMD check --as-cran clean. Tested against real analysis code with a ~62% noise reduction from naive matching (after v1.1 tightening).
  • Python package (v0.1.2) — 27 rules implemented (broad-exception, unchecked-merge, magic-threshold, label-in-blind-stage, synthetic-data-generation, …). 139 pytest tests passing. python -m build produces clean wheel + sdist; twine check PASSED.
  • scitexlintr (v0.2.1) — 13 rules, run on both .tex and .html report sources, covering manifest-anchored checks (snapshot-mismatch auto-fixable, raw-generated-value, bare-generated-macro, unwrapped-threshold, unfingerprinted-figure, unsourced-numeric-token, overloaded-term-no-warning, forbidden-alias) and manifest-free checks (handwritten-numeric-claim, magic-tex-threshold), plus three HTML-only checks (unknown-value-id, unfingerprinted-data, script-data-literal). 214 pytest tests passing, including an HTML port of the TeX corpus held to the same expectations. Hand-rolled TeX scanner and stdlib HTML frontend; no external runtime deps. Reviewed twice by automated review (codex) on PR #1; six findings addressed across two rounds.

All three are published: scilintr (Python) and scitexlintr are on PyPI, and scilintr (R) is on CRAN (published 2026-06-12). Install with the commands in the Install section above.

See also

License

MIT.

About

Scientific code lint for R and Python analysis projects — agent-first design, structured ANALYSIS_OK waivers, 40 rules

Resources

Stars

13 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages