Skip to main content

check-source-overlap.yml

Flag paragraphs of Markdown and Quarto prose that copy a reference text, as a guard against unintentional plagiarism by authors and AI agents alike.

Why this check

A language model that has read a textbook can reproduce its sentences without anyone noticing, and a bulk-generated chapter can carry passages no author reviewed for provenance. Crediting the source does not cure a copied passage, so this check looks for the copying itself.

What it does

It reads the PDFs (or .txt files) named by corpus-files from a private corpus repository, extracts their text with pdftotext into the runner’s temporary directory, and compares each paragraph the pull request adds with that text by word 6-grams. A paragraph is flagged when at least min-run consecutive words occur verbatim in a source, or when a quarter or more of its 6-grams do. The extracted text is never uploaded or committed.

Not compared, by design: fenced code, math, table rows, citations, URLs, Quarto attributes, blockquotes, and text in quotation marks. A paragraph containing <!-- overlap-ok: reason --> is skipped, for a passage checked by hand.

It finds copying that the corpus holds. A source nobody put in the corpus is invisible, and a paraphrase that keeps no run of six consecutive words is missed. It is a net, not a proof of originality.

A run that could not check anything exits 2, not 0. An empty or missing corpus, or no input files, is an error. A change that adds no prose passes, and the summary line reports examined 0 paragraphs.

Inputs

Input Type Default Description
corpus-repo string 'Morrison-Lab/mlr' Repository holding the reference PDFs, as owner/name.
corpus-files string required Comma- or newline-separated git pathspecs of the PDFs or .txt files in corpus-repo.
base-ref string '' Git base ref; only lines added since it are checked. Use HEAD^1 on a pull request. Empty checks every Markdown and Quarto file in paths.
paths string '' Space-separated files to check when base-ref is empty.
paths-ignore string ':(exclude)_extensions :(exclude)lectures' Space-separated git pathspecs to exclude, in both modes.
min-run string '12' Consecutive verbatim words that flag a paragraph.
fail boolean false Fail the job when a paragraph is flagged (else warn only).

Secrets

Secret Required Description
corpus-token yes A token that can read corpus-repo; the workflow token reaches only the caller’s repository.

Permissions

Only contents: read.

Example

name: Check source overlap
on:
  pull_request:
    paths: ['**.qmd', '**.md']
jobs:
  check:
    uses: Morrison-Lab/gha/.github/workflows/check-source-overlap.yml@v3
    with:
      corpus-files: 'books/Hastie-*.pdf'
      base-ref: HEAD^1
    #   fail: true
    secrets:
      corpus-token: ${{ secrets.CORPUS_TOKEN }}

See check-source-overlap.yml in examples/ for the full caller stub. An agent can run the same script before pushing: python3 check-source-overlap/check_source_overlap.py --corpus <dir of .txt> --git-diff origin/main.