Code Review — act101 Agent Skill
Use when reviewing a PR, checking code quality, finding dead code, measuring function complexity, auditing a module, or checking for type errors. AST-aware analysis plus the deterministic merge gate.
Code Review with act
Tools
Parser-only, always available:
skeleton(file): structure, signatures, nesting depth.symbols(file): every symbol with kind and location.unsafe_surface(file): unsafe blocks,eval, raw SQL, FFI, reflective invocation, unsafe deserialization. Run it on any file handling input, queries, or native interop.modeled_kindssays which categories the grammar models; an empty surface with an empty mask is unmodeled, not clean.definition(symbol, file, line, column): where a symbol is defined, AST-based.
Language-server backed (the server starts on first call; status reports LSP
readiness; references and callers name the answering server in resolved_by):
diagnostics(files=[…]): compiler and linter errors, warnings, hints. Call it once over the change with every edited path infiles, not once per file.references(symbol, file): every binding of a symbol; zero external references on an export means possibly dead.callers(symbol, file): call sites, for coupling and blast radius.get_type(file, line, column): inferred type at a position.
Explore broad to narrow: directory skeletons, then file symbols, then reference chains.
Workflow
Single file: diagnostics, then skeleton for function sizes and nesting, then
symbols for density and naming.
Directory or module: skeleton per file for the structural overview; one
diagnostics call over the files; references on exports to find unused public
API; callers on key functions for coupling.
PR (changed files):
- One
diagnosticscall over the changed files. skeletonon changed files for the new structure.referenceson renamed or moved symbols to confirm every binding moved.callerson modified functions for direct impact, andanalyze_impact(target=<file>, symbol=<name>)(Architecture) for transitive callers without LSP. Confirm the symbol name withskeletonfirst and readconfidence,modeled_kinds, andunresolved.analyze_api_diff(base_ref=<base>)(Architecture; CLIact analyze api-diff --base-ref <base>) when the PR touches public API. It diffs the merge-base of the base branch against the working tree and returns per-symbolrowsandsummarycounts. Cite Breaking (a parameter added, an export removed), PossiblyBreaking (other signature drift), and Additive rows, and surface every Unjudgeable row: it names a grammar whose visibility act does not model, so that change is unjudged, not safe.scan(root=<repo>, base_ref=<base>)for the AI-code security pass over the change: hardcoded credentials,.cursorrulesbackdoors, MCP-config RCE, typosquat or hallucinated dependencies, Actions expression injection, LLM-output-to-exec flows, prompt-injection surfaces, and the AI-Code Health Score. Withbase_refthe scan computes the changed set itself (merge-base, rename-aware) and discloses it in the report'sscopesection. Without a git base, passfiles=[<changed files>]instead (scope.mode: "paths"). Either way the score covers only the selected files and is never comparable to a full-repo score. With a committed.act/baseline.json, addbaseline=".act/baseline.json":baseline.new_finding_idsis then exactly the set of findings this change introduces.gate(base_ref=<base>)(CLIact gate --base-ref <base>) for the merge-safety verdict over the whole changed set. Cite its output directly; the gate owns the rules, andverify_diff_semantics,verify_test_impact, andverify_side_effectsare its inputs. Verdict semantics, exit codes, and degradation rules are in the safe-to-merge skill. UNKNOWN means not verified, never a pass.
gate --receipts (MCP gate(receipts=true)) writes per-function receipts to
.act/receipts/. On a later run, functions matching a valid receipt report
pre-verified by receipt <id>, which reviewers may accept without re-running the
trio. Receipt mechanics are in the refactor-receipt skill. A behavior change with
no test reaching it blocks:
$ act gate
act gate — working tree vs HEAD (1 changed function(s))
BLOCK auth.ts::validateAge — behavior, NO test reaches it (behavior change with no test reaching it)
verdict: BLOCK
$ echo $?
1
- Structural health of the change —
analyze_patterns,analyze_clones,analyze_cycles(Architecture tier; CLIact analyze patterns|clones|cycles). Compute the changed lines first:git diff -U0 --diff-filter=AMR <base>; each hunk header@@ -a,b +c,d @@marks the new-side linesctoc + d - 1as changed, and a deletion-only hunk (d= 0) marks linecat the deletion point. Scope every call withinclude= the changed files' top-level directories (includematches directories, not files; for a handful of changed files, callanalyze_patternsonce per file with itsfileparam instead), and passtier: "fast": the defaultallalso runs the deep pass, which builds the workspace graph on every call regardless offileandinclude. analyze_patterns: keep each finding whosefileis a changed file and whoselineis a changed line, or whoselinestarts a function that a changed line falls inside (function spans come fromskeleton). Reportpattern,severity,symbol,file:lineandthresholds_exceeded. Findings on unchanged lines of changed files are pre-existing context — report them as such or drop them.analyze_clones: keep a clone class with a member whosefileis a changed file and whosestart_line..end_lineoverlaps a changed range. Reportclone_type,token_countand the member ranges.analyze_cycles: keep a cycle with a changed file in itsfiles. The kept set holds cycles the change takes part in, not only cycles it creates — proving a cycle new needs the base analyzed in a second checkout; do not claim newness.
What to look for
| Signal | Tool | Severity |
|---|---|---|
| Compiler errors | diagnostics | Error |
| Type mismatches | diagnostics, get_type | Error |
| Unused imports or variables | diagnostics | Warning |
| Function over 50 lines | skeleton | Warning |
| Function with over 5 parameters | skeleton | Warning |
| Nesting depth over 4 | skeleton | Warning |
| Symbol with 0 references | references | Info |
| High caller count (over 10) | callers | Info |
| eval, raw SQL, FFI, unsafe deserialize | unsafe_surface | Error/Warning |
Hardcoded credential, .cursorrules backdoor, MCP-config RCE |
scan | Error |
References
- review-patterns.md: review checklists and multi-file strategies.
- error-recovery.md: LSP failures, file not found, ambiguous symbols.
Output format
## Review: src/auth/login.ts
### Errors (2)
- Line 45: Type 'string' is not assignable to type 'number' [diagnostics]
- Line 72: Cannot find name 'authConfig' [diagnostics]
### Warnings (3)
- Line 15-80: Function `handleLogin` is 65 lines (>50 threshold) [skeleton]
- Line 23: Unused import 'lodash' [diagnostics]
- Line 90: `validateToken` has 0 external references — possibly dead code [references]
### Info (1)
- `processAuth` called from 12 locations — high coupling [callers]
### Structural health (2)
- src/auth/login.ts:45 `handleLogin` god_function (statements: 65 >= 50) [analyze_patterns]
- type-2 clone, 120 tokens: src/auth/login.ts:12-58 ~ src/auth/session.ts:8-54 [analyze_clones]
### Limits
Clones are subtree-shaped duplicates: type-1 (exact) and type-2 (renamed
identifiers/literals); type-3 (gapped) clones are not detected, and a shared
shape is not shared behavior. Pattern findings are threshold measurements
(`thresholds_exceeded`), not proofs of a defect. Test files are excluded by
convention — `summary.test_files_excluded` from `analyze_clones` says how
many. State the `include` directories the calls were scoped to; a finding
outside them was not looked for.