sindre-ai

risk-classifier

Use to score a PR diff on the 0–100 risk band that gates the auto-merge pipeline. Triggers on every `pull_request` opened/synchronize/reopened/ready_for_review event against `main`, and on demand from the `orchestrate-pr-review` skill. The skill produces a deterministic score from a fixed signal table (path sensitivity, DDL detection, secrets-like patterns, SAST alerts, incident density, and friends), applies the floors declared in `.maskin/protected-paths.yml` and `.maskin/risk-floors.yml`, and emits a `## Risk Score` block plus a `maskin/risk-score` check run. Do NOT use to *decide* whether to merge — that's `orchestrate-pr-review`'s job. Do NOT use to author code or critique style — score, output, stop.

sindre-ai 7 1 Updated 3w ago

Resources

6
GitHub

Install

npx skillscat add sindre-ai/maskin/risk-classifier

Install via the SkillsCat registry.

SKILL.md

Risk Classifier

A deterministic 0–100 score over a PR diff. Same inputs in → same score out, every time, on every SHA. The classifier is the data layer of the auto-merge bet (bb682faa); without a stable score there is nothing for the approver to gate on.

When to invoke

  • A pull_request event arrives with action opened, synchronize, reopened, or ready_for_review against main.
  • The orchestrate-pr-review skill asks for the score for a specific commit SHA.
  • A human asks "what would the classifier score this?" on an open PR.

The skill re-runs on every synchronize (per-SHA). The previous verdict is invalidated as soon as the head SHA moves; never carry forward.

Do NOT invoke

  • After a merge has already happened — the score has no consumer.
  • For PRs targeting branches other than main — branch protection only enforces the gate on main.
  • As a substitute for review-checklist (correctness, tests, naming) or security-review (OWASP/STRIDE). The classifier is triage, not review.
  • To "improve" the diff. The score is read-only output; do not edit code, propose fixes, or comment lines.

Method

Walk these in order. Each step is non-negotiable; skipping any step makes the verdict non-deterministic.

1. Resolve the diff

Run the adapter binary at packages/risk-classifier/bin/risk-classifier.mjs. Pass:

  • --base = PR base SHA (the merge target's head at PR-open time)
  • --head = PR head SHA from the webhook payload
  • --repo = path to the checkout
  • --cve-dep <name@version> for each newly-introduced dependency that already has a CVE
  • --missing-tests if the diff includes logic-bearing files but no test changes
  • --ai-generated if the PR body or commit trailers carry an AI-authoring marker
  • --public-api-delta <n> for the count of public symbols added/removed
  • --incident-density <file.json> if Sentry/PagerDuty data is available

The adapter reads .maskin/protected-paths.yml, .maskin/risk-floors.yml, and .maskin/hot-tables.yml from the repo, runs git diff between the two SHAs, runs squawk on .sql files, and runs Semgrep diff-scan. It returns a ClassifierVerdict JSON object.

2. Apply the signal table

Sum the weights of every triggered signal. Cap the sum at 100.

Signal Weight
diff_loc bucketed: 6 / 12 / 20 / 30 at 50 / 200 / 500 / 1000 LOC
files_changed bucketed: 5 / 10 / 15 at 5 / 15 / 30 files
paths_auth_session +15
paths_payments_billing +20
paths_crypto_kms +20
paths_iam_policy +15
paths_migrations_ddl +15 (also runs squawk)
paths_iac +15
paths_gha_workflows +20
public_api_surface_delta +10
new_deps_with_cve +25
secrets_like_patterns +30
missing_tests_for_logic +10
top_decile_incident_file +10
file_unchanged_365d +5
ai_generated_marker +5
codeql_or_semgrep_alert severity-weighted: 30 / 20 / 8 / 2 (CRITICAL / ERROR / WARNING / INFO)
squawk_blocking_lock +15

3. Apply the floors

Floors override the additive sum. Apply in this order:

  • Protected path — any file matches a glob in .maskin/protected-paths.yml → score = 100, regardless of additive sum.
  • Regex floor — any line matches a pattern in .maskin/risk-floors.yml → score = max(additive_sum, 80).
  • Squawk hot-table hit — squawk flags a blocking lock against a table in .maskin/hot-tables.yml → score = max(additive_sum, 80).

The additive sum and the floors are independent computations; the final score is the maximum of all of them, capped at 100.

Verdict

Exactly one band. Not a range, not "around 30," not "depends." Pick.

  • AUTO-APPROVE ELIGIBLE — score < 25. The PR is eligible for orchestrate-pr-review to consume; it does not auto-approve here.
  • AGENT RECOMMENDS HUMAN — 25 ≤ score < 80. Orchestrator runs perspectives but never submits APPROVE.
  • HUMAN REVIEW REQUIRED — score ≥ 80. Orchestrator stops and routes to humans.

Output

Write the verdict on the task linked from the PR's github_link, under a ## Risk Score heading, with this structure:

## Risk Score

**Score:** <0-100>/100 — <BAND LABEL>
**Skill version:** 0.1.0
**Commit:** <head-sha>
**Deterministic seed:** <16-hex>

### Signals
- `paths_auth_session` +15 — packages/auth/src/session.ts
- `diff_loc` +12 — 240 lines changed across 3 files

### Floors applied
- `protected_path` — packages/auth/** matches packages/auth/src/session.ts

Also post the maskin/risk-score GitHub check run with conclusion:

  • success for AUTO-APPROVE ELIGIBLE
  • neutral for AGENT RECOMMENDS HUMAN
  • failure for HUMAN REVIEW REQUIRED

The check-run summary includes the score and band; the full block goes on the task, not on the PR (the PR is a projection of the task).

Attestation

Record the skill version (0.1.0) and the deterministic seed in the in-toto attestation built by orchestrate-pr-review. The orchestrator binds the version to the verdict; bumping version: in this skill's frontmatter must accompany any change to the signal table, weights, floors, or output format.

What NOT to do

  • Do not invent a signal that isn't in the table. The table is the contract; new signals require a version bump and a bet revision.
  • Do not produce a non-deterministic score. If the adapter cannot reach a sub-tool (squawk, semgrep, incident density), the affected signal is absent, not guessed. Absent signals are observable in the verdict — that is itself a feature.
  • Do not write a partial verdict. Either the full ## Risk Score block lands on the task and the check run lands on the PR, or neither does. Half-output is worse than no output.
  • Do not edit .maskin/protected-paths.yml, .maskin/risk-floors.yml, or .maskin/hot-tables.yml from this skill. Those are protected paths themselves; edits are always human-review PRs.
  • Do not approve, merge, or request changes. Score, output, stop. The downstream skills decide what happens next.