patina-action

May 20, 2026 · View on GitHub

patina — Strip the AI packaging. Keep the meaning.

Score AI-sounding prose in your pull requests, before it ships.

This Action scores the Markdown a PR changes, posts the result as one sticky comment, and can fail the check when a file crosses your threshold.

The number is Patina's deterministic prose hotspot score — the share of paragraphs flagged as editing hotspots. It runs offline: no model call, no API key. Korean, English, Chinese, and Japanese.

Quick start

name: Patina prose score

on:
  pull_request:
    paths:
      - '**/*.md'
      - '**/*.mdx'

permissions:
  contents: read
  pull-requests: read
  issues: write

jobs:
  patina:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: devswha/patina-action@v1
        with:
          score-threshold: 30
          comment: true
          # Optional: publish patina-badge.json for a Shields.io README badge.
          # Requires permissions.contents: write.
          # badge-branch: patina-badge

What you get

A sticky PR comment with a per-file table — paragraphs, hot paragraphs, and score — plus a job summary. When score-threshold is set, the check fails if any file goes over it. Leave it unset to keep the report advisory.

Inputs

InputDefaultMeaning
github-token${{ github.token }}Token for change detection and comments.
fileschanged PR MarkdownOptional newline/comma/JSON file list. Overrides paths-filter.
langautoauto, ko, en, zh, or ja.
score-thresholdunsetIf set, fail when any file score is above this percentage.
report-threshold30Advisory report gate when score-threshold is unset.
max-files50Maximum Markdown files to score.
commenttrueCreate or update a sticky PR comment.
badge-branchunsetOptional branch where patina-badge.json is published for Shields.io endpoint badges.
patina-packagepatina-cli@latestnpm package spec used by npx.
patina-binunsetLocal patina-score executable for tests / self-hosted runners.

Outputs

OutputMeaning
file-countNumber of Markdown files scored.
failed-countNumber of files above the active threshold.
max-scoreHighest file score percentage.
badge-jsonOne-line Shields.io endpoint JSON derived from max-score.
threshold-failedtrue when score-threshold was set and exceeded.
comment-body-pathPath to the generated Markdown comment body.

README badge

Set badge-branch to publish patina-badge.json, then point Shields.io at the raw JSON file:

[![patina](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/<owner>/<repo>/patina-badge/patina-badge.json)](https://github.com/devswha/patina)

Publishing requires contents: write permission in the calling workflow. The badge JSON is generated from the same deterministic max score used by the PR comment and contains no per-visitor tracking.

How it works

It finds changed Markdown with dorny/paths-filter, scores each file by running patina-score from npx -p patina-cli@latest, and upserts the sticky comment with peter-evans/create-or-update-comment. The score itself is deterministic and local — no model, no key.

Forks

On public repos, GitHub limits GITHUB_TOKEN permissions for pull_request runs triggered from forks. If comments are blocked, the score still shows in the job summary; for inline comments on fork PRs, use a locked-down pull_request_target workflow.

License

MIT. Part of patina.