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
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]