Skip to content
Back to skills

Tsa Pr Review

ASecurity

AST-grounded PR / diff review. One workflow → per-file risk ranking, blast radius per changed symbol, the exact pytest command to gate merge, any architecture-constraint violations, and a final BLOCK / REVIEW / APPROVE verdict — in ~1–2k tokens and 4–6 MCP calls. Goes beyond a generic LLM diff-read because only TSA's `edit action=impact` returns a deterministic `verification_command` + `queue_ledger`, and only the persisted call graph can enumerate true callers/callees of changed symbols. (P...

  • 50 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 4, 2026
ai-agentspythongobashrailsgit

Works with

  • cli
  • mcp

Security analysis

A100/100

Scanned September 4, 2026

npx -y skills add aimasteracc/tree-sitter-analyzer --skill tsa-pr-review --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tsa Pr Review?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tsa Pr Review
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aimasteracc-tsa-pr-review-tree-sitter-analyzer/badge)](https://www.skillsdirectory.com/skills/aimasteracc-tsa-pr-review-tree-sitter-analyzer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tsa-pr-review
version: 2.0.0
description: |
  AST-grounded PR / diff review. One workflow → per-file risk ranking, blast
  radius per changed symbol, the exact pytest command to gate merge, any
  architecture-constraint violations, and a final BLOCK / REVIEW / APPROVE
  verdict — in ~1–2k tokens and 4–6 MCP calls.

  Goes beyond a generic LLM diff-read because only TSA's `edit action=impact`
  returns a deterministic `verification_command` + `queue_ledger`, and only the
  persisted call graph can enumerate true callers/callees of changed symbols.
  (Per `docs/internal/COMPETITOR_HEAD_TO_HEAD_2026-05-23.md`: CodeGraphContext
  silently indexes 0 files, grep-ast crashes on Python 3.14, wrale miscounts
  imports. A pure-LLM diff reader has none of these guard-rails — it cannot
  reach the AST cache to enumerate callers, cannot consult
  `architectural-constraints.yml`, and invents a pytest command instead of
  reading the one TSA computed.)

  Use when:
  - Reviewing a local diff (`git diff`, staged, branch-vs-main)
  - Reviewing a GitHub PR URL
  - User asks "is this PR safe to merge?" / "what's the blast radius of this PR?"
  - Pre-merge gate in CI / agent loops
  - Right after `/ship` produced a diff and before `/land`

  Workflow: fan-out (edit action=pr + edit action=impact + edit action=constraints)
  → per-file fan-out (edit action=safe) → per-symbol fan-out (nav action=callers
  + nav action=callees) → fold into a verdict-ranked report.
allowed-tools:
  - mcp__tree-sitter-analyzer__edit
  - mcp__tree-sitter-analyzer__nav
  - Bash
  - Read
---

# tsa-pr-review — AST-grounded PR review with merge-gate verdict

> Replaces "LLM reads the diff and guesses" with a deterministic pipeline that
> grounds the review in (a) the AST diff, (b) the persisted call graph, and
> (c) architectural-constraints.yml. Output is a structured verdict an agent
> can act on — including the **exact** pytest command to run before merge.

## When to use

- Local diff review: unstaged, staged, or branch-vs-main
- GitHub PR review by URL (uses `gh` under the hood)
- CI / agent pre-merge gate
- Any time a generic "read the diff" review would be too shallow

**Don't use** when:
- The diff is a one-line typo in a comment or doc
- The change is in a file with no AST coverage (e.g. binary, image, .lock)
- You only need to *describe* the diff, not *judge* it → use plain `git diff`

## Why this beats a generic LLM diff-read

| Concern                          | LLM-only diff read   | tsa-pr-review                               |
|----------------------------------|----------------------|---------------------------------------------|
| Classify signature vs body change| Heuristic            | `edit action=pr` AST diff                   |
| Exact tests to run               | Invented / hallucinated | `edit action=impact verification_command` |
| Caller blast radius              | Grep guess           | `nav action=callers` from persisted graph   |
| Callee regression surface        | Read N files         | `nav action=callees` from persisted graph   |
| Architecture rule violations     | None                 | `edit action=constraints` vs YAML rules     |
| Per-file pre-merge verdict       | Subjective           | `edit action=safe` SAFE/REVIEW/CAUTION/UNSAFE|
| Behaviour on Python 3.14         | n/a                  | Stable (grep-ast crashes, cgc silent-0)     |

(Source: `docs/internal/COMPETITOR_HEAD_TO_HEAD_2026-05-23.md` — the 4
fault-tolerance advantages: oracle-consistent AST, indexed-not-silent,
Python 3.14-safe, no external DB dependency.)

## Procedure

### Step 1 — Diff-level fan-out (parallel, 3 calls)

In one message, call all three:

1. `edit action=pr mode="staged"` — AST diff classification, per-symbol
   change category (signature_change / body_change / new_symbol / deletion),
   and per-file changed-symbol list.
   - For PR URL: `edit action=pr mode="pr" pr_url="<url>"`
   - For unstaged: `mode="diff"`; for branch-vs-main: `mode="branch"`
2. `edit action=impact mode="staged"` — returns `verification_command`,
   `queue_ledger` (in-scope vs out-of-scope dirty files), `pytest_required`,
   and per-file impact rows.
3. `edit action=constraints` — full repo audit against `architectural-constraints.yml`,
   filtered to files the diff touches if you want noise-free output:
   `edit action=constraints path_filter="<glob covering changed files>"`.

### Step 2 — Per-file fan-out (parallel, N calls)

For each changed file from Step 1, call:

`edit action=safe file_path="<abs>" edit_type="<refactor|add_feature|fix_bug|rename>"`

Pick `edit_type` from the dominant change category in Step 1's
`edit action=pr` output. Returns per-file verdict + `risk_factors`.

### Step 3 — Per-symbol fan-out (parallel, ≤2N calls)

For each *changed signature* (not body-only) symbol from Step 1, fan out
two calls:

- `nav action=callers function_name="<sym>" language="<lang>" limit=20`
  — who depends on this; the blast radius if its signature moved.
- `nav action=callees function_name="<sym>" language="<lang>" limit=20`
  — what it calls; the regression surface if its body changed.

Filter callers/callees where `callee_resolution == "stdlib"` or `"unknown"` —
they're noise for blast-radius scoring (per tsa-graph guidance).

### Step 4 — Fold into verdict

```
verdict_precedence (worst wins):
  - BLOCK   ← any edit action=constraints violation with severity=error
              OR any edit action=safe verdict == UNSAFE
  - REVIEW  ← any edit action=safe verdict in {REVIEW, CAUTION}
              OR pytest_required=true with no nearby tests
              OR signature_change with ≥5 external callers
  - APPROVE ← all per-file verdicts == SAFE,
              all constraints pass,
              pytest_required=false OR verification_command is tightly scoped
```

Surface the precise `verification_command` verbatim — never paraphrase it.

## Worked example (this repo, 2-file diff)

Suppose `git diff HEAD~2 HEAD` shows changes in two files. To walk through
without committing anything new, simulate the workflow against a real diff:

```bash
git -C /Users/aisheng.yu/git-private/tree-sitter-analyzer diff HEAD~2 HEAD --name-only
# → tree_sitter_analyzer/mcp/tools/safe_to_edit_tool.py
# → tree_sitter_analyzer/mcp/tools/change_impact_tool.py
```

### Diff-level fan-out

```yaml
# Call 1
edit action=pr mode="branch" include_call_graph=true
# returns:
#   changed_files:
#     - path: ".../safe_to_edit_tool.py"
#       changed_symbols:
#         - name: "SafeToEditTool.execute"
#           category: "body_change"
#         - name: "SafeToEditTool._score_risk"
#           category: "signature_change"  # ← signature changed
#     - path: ".../change_impact_tool.py"
#       changed_symbols:
#         - name: "ChangeImpactTool.execute"
#           category: "body_change"

# Call 2
edit action=impact mode="branch" agent_summary_only=true
# returns:
#   verification_command: "uv run pytest tests/unit/test_safe_to_edit_tool.py
#                          tests/unit/test_change_impact_tool.py -q"
#   pytest_required: true
#   queue_ledger:
#     in_scope: [".../safe_to_edit_tool.py", ".../change_impact_tool.py"]
#     out_of_scope_dirty: []

# Call 3
edit action=constraints path_filter="tree_sitter_analyzer/mcp/**"
# returns:
#   verdict: SAFE
#   violations: []
```

### Per-file fan-out

```yaml
edit action=safe file_path=".../safe_to_edit_tool.py" edit_type="refactor"
# → verdict: REVIEW, risk_factors: [{factor: "high_caller_count", ...}]
edit action=safe file_path=".../change_impact_tool.py" edit_type="fix_bug"
# → verdict: SAFE
```

### Per-symbol fan-out (only the signature-change)

```yaml
nav action=callers function_name="_score_risk" language="python" limit=20
# → 7 callers; 6 in tests/, 1 in cli/commands/mcp_commands.py
nav action=callees function_name="_score_risk" language="python" limit=20
# → 4 callees; all local (good — no stdlib noise)
```

### Folded verdict

```yaml
verdict: REVIEW
files:
  - path: ".../safe_to_edit_tool.py"
    verdict: REVIEW
    changed_symbols: 2
    signature_changes: 1
    blast_radius: 7         # callers of _score_risk
    health_grade: B
  - path: ".../change_impact_tool.py"
    verdict: SAFE
    changed_symbols: 1
    signature_changes: 0
    blast_radius: 0
constraint_violations: []
verification_command: "uv run pytest tests/unit/test_safe_to_edit_tool.py
                       tests/unit/test_change_impact_tool.py -q"
stop_condition: "tests exit successfully AND no NEW failures vs main"
notes:
  - "Signature change on _score_risk affects 1 non-test caller — verify mcp_commands.py still passes."
```

## CLI equivalents (1-to-1 with MCP calls)

```bash
# Step 1 — Diff fan-out
uv run tree-sitter-analyzer --pr-review --change-impact-mode staged --output-format toon
uv run tree-sitter-analyzer --change-impact --change-impact-mode staged --agent-summary-only --output-format json
uv run tree-sitter-analyzer --check-constraints --constraint-path-filter "tree_sitter_analyzer/mcp/**" --output-format toon

# Step 2 — Per-file
uv run tree-sitter-analyzer <file> --safe-to-edit --edit-type refactor --output-format json

# Step 3 — Per-symbol
uv run tree-sitter-analyzer --callers <FUNC> --output-format toon
uv run tree-sitter-analyzer --callees <FUNC> --output-format toon
```

Exit codes mirror `--change-impact` / `--check-constraints`:
- 0 → APPROVE
- 2 → REVIEW (warn-only)
- 1 → BLOCK (error severity)

## Anti-patterns

- DO NOT ask the LLM to invent the pytest command — quote
  `verification_command` verbatim. r36 / past incidents confirm hallucinated
  pytest invocations skip the regressions TSA already pinpointed.
- DO NOT skip `edit action=constraints` because "the LLM can spot architecture
  drift". It can't — the rules live in YAML and are evaluated against the
  persisted edge graph.
- DO NOT run callers/callees on body-only changes. Bodies don't change
  signatures; callers don't care. Reserve graph queries for signature changes,
  deletions, and new public symbols.
- DO NOT mix `mode="staged"` for pr_review with `mode="branch"` for
  change_impact — keep modes consistent so both tools see the same diff.
- DO NOT swallow `queue_ledger.out_of_scope_dirty` — it tells you the working
  tree has files outside the diff that may still break the build.
- DO NOT flip MCP `output_format` from `toon` to `json` — locked by user
  (see `CLAUDE.md` "Deliberate design decisions"). Tokens matter.

## Decision surface (output schema)

```yaml
verdict: APPROVE | REVIEW | BLOCK
files:
  - path: <abs>
    verdict: SAFE | REVIEW | CAUTION | UNSAFE
    changed_symbols: <int>
    signature_changes: <int>
    body_changes: <int>
    new_symbols: <int>
    deletions: <int>
    blast_radius: <int>            # external callers of changed signatures
    regression_surface: <int>      # non-stdlib callees touched
    health_grade: A | B | C | D | F
    risk_factors: [{factor, detail, severity}]
constraint_violations: [
  {rule_id, caller_file, caller_line, callee_file, severity, reason}
]
verification_command: <copy-paste exact pytest line from edit action=impact>
pytest_required: bool
queue_ledger:
  in_scope: [<path>]
  out_of_scope_dirty: [<path>]     # non-empty → warn user
stop_condition: <when to declare done>
notes: [<short string>]
```

Verdict cascade (worst wins):
- `BLOCK` → constraint error OR per-file `UNSAFE`
- `REVIEW` → per-file `REVIEW`/`CAUTION` OR ≥5-caller signature change OR
  `pytest_required` and tests aren't co-located
- `APPROVE` → all per-file `SAFE`, no constraint violations, tight verify cmd

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.