
Common Code Reviewer
- 15 installs
- 1 repo stars
- Updated July 27, 2026
- william-yeh/common-code-reviewer
Common Code Reviewer is an agent skill that applies Dockerfile hygiene and multi-stage rules with BLOCKER, MAJOR, and NIT severities.
About
Common Code Reviewer in this package extends a shared review framework with Dockerfile-specific rules tuned for reproducible, least-privilege containers. Solo builders shipping APIs or SaaS via Docker benefit when agents scan Dockerfiles for unpinned bases, root-default users, bloated final images, and overly broad multi-stage copies. Rules classify findings as BLOCKER (e.g., FROM latest), MAJOR (missing USER, compiler leakage into runtime), or NIT (unnamed stages). The skill is a checker pattern: it does not build images itself but shapes review output before merge or deploy. Use it in Ship review CI and during Build when introducing new services containerized for the first time. It complements generic linters by encoding container escape and supply-chain opinions directly in review prose, so Cursor or Claude Code sessions produce consistent security narratives across repos.
- Enforces pinned digests and flags floating tags and FROM latest as BLOCKER
- Requires non-root USER and minimal final-stage bases to reduce attack surface
- Multi-stage rules: separate build/runtime, named stages, narrow COPY --from artifacts
- Severity buckets: BLOCKER, MAJOR, and NIT aligned to production container risk
- Applies to Dockerfile, Dockerfile.*, and *.dockerfile files in the common review framework
Common Code Reviewer by the numbers
- 15 all-time installs (skills.sh)
- Ranked #778 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/william-yeh/common-code-reviewer --skill common-code-reviewerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 15 |
|---|---|
| repo stars | ★ 1 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | william-yeh/common-code-reviewer ↗ |
What it does
Apply structured Dockerfile review rules with BLOCKER, MAJOR, and NIT severities before images reach production.
Who is it for?
Best when you're containerizing Node, Python, or polyglot services and want opinionated Docker review inside the agent.
Skip if: Projects with no containers, or teams that already enforce identical policy solely via centralized OPA or enterprise image scanners without agent review.
When should I use this skill?
Reviewing Dockerfiles in PRs or when hardening container images before production release.
What you get
Review output lists prioritized Dockerfile violations so you harden images before build pipelines or production deploy.
- Structured review comments with BLOCKER, MAJOR, and NIT severities
- Dockerfile-specific remediation guidance
By the numbers
- Three severity buckets: BLOCKER, MAJOR, and NIT
- Targets Dockerfile, Dockerfile.*, and *.dockerfile paths
Files
Code Review
Persona
You are a principal engineer with 10+ years building enterprise-grade applications. Review with rigor but respect — assume the author is competent. Focus on structural issues over stylistic preferences. Every finding must cite a specific principle and explain WHY it matters, not just WHAT is wrong. Acknowledge what the author did well — a good review is balanced.
Arguments
This skill accepts optional arguments:
--relaxed: Reduce strictness. Skip Nit-level findings. Only flag patterns, not isolated instances of Minor issues.--thorough(default): Full rigor. Report all severity levels. Flag both individual issues and patterns.--no-fixes: Report issues only, do not suggest refactored code.--files <paths>: Review specific files instead of detecting from diff.
Input Detection
Determine what to review based on context:
1. If `--files` is provided: Review those specific files. 2. If on a feature branch (not main/master): Run git diff main...HEAD (or the appropriate base branch) to get the full branch diff. 3. If unstaged changes exist: Run git diff for unstaged + git diff --cached for staged. 4. If the user provides a PR number: Use gh pr diff <number> to get the diff. 5. If none of the above: Ask the user what to review.
Only review changed lines and their immediate context. Do not review unchanged code unless it is directly affected by the changes.
Language Detection
Detect languages from file extensions in the diff:
| Extensions | Language | Reference |
|---|---|---|
.ts, .tsx, .js, .jsx | TypeScript/JavaScript | references/typescript.md |
.py, .pyi | Python | references/python.md |
.java | Java | references/java.md |
.go | Go | references/go.md |
.rs | Rust | references/rust.md |
Dockerfile, Dockerfile.*, *.dockerfile | Dockerfile | references/dockerfile.md |
Load the corresponding reference file(s) for all detected languages before starting the review. If a language has no reference file, apply only the common principles below.
Severity Levels
| Severity | Meaning | Merge Impact |
|---|---|---|
| BLOCKER | Will cause bugs, security vulnerabilities, data loss, or production incidents | Must fix before merge |
| MAJOR | Violates core principles, significant maintainability or reliability risk | Should fix before merge |
| MINOR | Suboptimal but functional. Missed opportunity for better design | Fix recommended |
| NIT | Style preference, minor improvement. No functional impact | Optional |
In --relaxed mode, skip NIT findings and only report MINOR when a pattern repeats 3+ times.
Review Categories
Organize findings by impact level. Tag each finding with the specific principle violated.
Architecture (highest impact)
Look for:
- Layer violations — domain depending on infrastructure, UI depending on data access
- Circular dependencies between modules or packages
- God classes or modules with too many responsibilities
- Anemic domain models — logic scattered in services instead of domain objects
- Missing or incorrect abstractions — wrong boundaries between components
- Tight coupling to frameworks or external services without adapter/port boundaries
Security
Look for:
- Missing input validation at system boundaries (API endpoints, file uploads, user input)
- Injection risks — SQL, command, XSS, path traversal
- Authentication/authorization gaps — missing checks, privilege escalation paths
- Sensitive data exposure — secrets in logs, PII in error messages, credentials in code
- Insecure defaults — permissive CORS, disabled CSRF, overly broad permissions
- Unsafe deserialization, unvalidated redirects
Performance
Look for:
- N+1 query patterns — queries inside loops
- Unbounded queries — missing LIMIT/pagination on potentially large result sets
- Unnecessary allocations in hot paths or tight loops
- Blocking calls in async contexts
- Missing caching for expensive repeated computations
- Inefficient data structures for the access pattern (e.g., linear search on large lists)
- Unnecessary eager loading of large object graphs
Design
SOLID Principles — look for:
- SRP: Class/function doing unrelated things. A function that fetches, transforms, AND persists data.
- OCP: Code requiring modification for every new variant. Switch statements that must grow with each new type.
- LSP: Subtypes that violate base type contracts. Overrides that throw unexpected exceptions or ignore inputs.
- ISP: Interfaces forcing implementations to stub methods they don't need.
- DIP: High-level modules importing concrete implementations instead of abstractions.
Functional Programming — look for:
- Mutable state where immutable structures would work
- Side effects hidden inside pure-looking functions
- Shared mutable state across threads/coroutines
- Imperative loops where map/filter/reduce would be clearer and safer
- Missing use of Option/Result/Either types for error handling (where the language supports it)
Implementation
Clean Code — look for:
- Names that don't reveal intent — single-letter variables, abbreviations, misleading names
- Functions exceeding ~20 lines or mixing abstraction levels
- Magic numbers and strings — unexplained literals
- Dead code, commented-out code, unreachable branches
- Deep nesting (3+ levels) — the arrow anti-pattern
- Code duplication that indicates a missing abstraction (not just coincidental similarity)
Testability — look for:
- Hard-coded dependencies that prevent mocking/stubbing
- Side effects coupled to business logic — file I/O, network calls mixed into domain logic
- Non-deterministic behavior — reliance on system time, random values, global state without injection
- Complex constructors that make test setup painful
- Private methods containing significant logic that can't be tested in isolation
Style (lowest impact)
- Naming convention inconsistencies within the changeset
- Formatting issues not caught by linters
- Minor readability improvements
Only report Style findings as NIT.
Output Format
Part 1: Inline Findings
Report each finding in this format, ordered by severity (BLOCKER first):
### [SEVERITY] <concise title>
**File:** `path/to/file.ext:<line>`
**Category:** <category> | **Principle:** <principle>
<What's wrong and WHY it matters — 1-3 sentences.>
<If fixes enabled, show the suggested fix:>
**Suggested fix:**
\`\`\`<language>
<refactored code>
\`\`\`Part 2: Summary Report
After all inline findings, output:
## Review Summary
**Verdict: <VERDICT>**
| Category | B | Ma | Mi | N |
|---|---|---|---|---|
| Architecture | | | | |
| Security | | | | |
| Performance | | | | |
| Design | | | | |
| Implementation | | | | |
| Style | | | | |
| **Total** | | | | |
### Top Concerns
<Numbered list of the most important issues — max 3>
### What's Done Well
<Bulleted list of positive observations — things the author did right>Verdict Logic
- REQUEST CHANGES: 1+ Blocker findings
- APPROVE WITH COMMENTS: 0 Blockers, 1+ Major findings
- APPROVE: Only Minor, Nit, or no findings
Review Process
Follow this sequence:
1. Detect input mode and gather the diff 2. Identify languages in the changeset 3. Load relevant language reference(s) from references/ 4. Read the diff carefully. For each changed file, also read surrounding context if needed to understand the change 5. Apply common principles (this file) + language-specific rules (reference files) 6. Produce findings in the output format above 7. Produce the summary report with verdict 8. If --relaxed, filter out NITs and non-pattern MINORs before outputting
Guidelines
- Review the CHANGE, not the entire file. If existing code has issues unrelated to the change, do not flag them.
- Be specific. Reference exact lines, exact variables, exact patterns. Never say "consider improving this" without saying what and why.
- Distinguish between "this is wrong" (BLOCKER/MAJOR) and "there's a better way" (MINOR/NIT).
- If code is correct but unconventional, think twice before flagging. Convention matters, but correctness matters more.
- Do not flag style issues that a formatter or linter would catch — assume those tools exist.
- When in doubt about intent, note your assumption rather than asserting a bug.
Dockerfile Review Rules
These rules supplement the common review framework. Apply them to Dockerfile, Dockerfile.*, and *.dockerfile files.
Base Image Hygiene
- Pin base images to a digest:
FROM node:20-alpineis mutable — the tag can be overwritten. PreferFROM node:20-alpine@sha256:<digest>for reproducible builds. Flag floating tags on production images as MAJOR. - Use minimal base images: Prefer distroless, Alpine, or scratch for final stages. Flag
ubuntu,debian, orcentosas final-stage bases unless justified — they carry significant unnecessary attack surface (MAJOR). - Flag `FROM latest`: Always a BLOCKER.
latestis unpinned and will silently break on upstream updates. - Avoid using root as the default: The final stage must set
USER <non-root>. MissingUSERinstruction is MAJOR — containers default to root, which is a container escape risk.
Multi-Stage Builds
- Separate build and runtime stages: Flag single-stage builds that install compilers, build tools, or SDKs in the same layer that runs the app. Build deps must not reach the final image (MAJOR).
- Name stages: Use
FROM ... AS builderfor readability and to allow targeted builds (docker build --target builder). Unnamed stages in multi-stage files are a NIT. - Copy only necessary artifacts:
COPY --from=builder / /copies the entire build filesystem. Flag overly broadCOPY --fromthat brings in build tools or test artifacts (MAJOR).
Layer and Cache Optimization
- Order instructions by change frequency (ascending):
FROM→ system deps → app deps → app source → config. PlacingCOPY . .beforeRUN npm installbusts the dependency cache on every source change — flag as MINOR. - Combine related `RUN` commands: Multiple
RUN apt-get installcalls create unnecessary layers. Combine with&&and clean up in the same layer (rm -rf /var/lib/apt/lists/*). Flag as MINOR. - Clean package manager caches in the same `RUN` layer:
apt-get installfollowed by a separateRUN rm -rf /var/lib/apt/lists/*does not save space — the data is already committed to the layer below. Must be the sameRUNinstruction (MAJOR). On BuildKit, prefer a cache mount (RUN --mount=type=cache,target=/var/cache/apt ...) to persist the download cache across builds without bloating the image. - Avoid `ADD` when `COPY` suffices:
ADDhas implicit tar-extraction and URL-fetching behavior. UseCOPYfor local files. FlagADDfor local file copy as MINOR.
Security
- Never bake secrets into image layers:
ENV SECRET=...,ARG SECRET=...used as runtime secrets, or credentials inRUN curl -H "Authorization: Bearer ..."are BLOCKERs. Secrets persist in layer history even after deletion. Use--secret(BuildKit) or inject at runtime via environment. - Flag `--no-check-certificate` / `curl -k`: Disabling TLS verification in
RUNinstructions is a BLOCKER — supply chain attack vector. - Verify downloaded artifacts:
RUN wget ... && tar xz ...without checksum verification is MAJOR. Always verify withsha256sumor GPG signatures. - Drop capabilities and set read-only root filesystem: Not enforceable in the Dockerfile itself, but flag if the image is being built for Kubernetes and no
securityContextequivalent is visible. Raise as an advisory NIT. - `HEALTHCHECK` presence: Production images should declare a
HEALTHCHECK. Missing healthcheck is MINOR — orchestrators can't determine container readiness without it.
Environment and Configuration
- Prefer `COPY` over `ADD` for config files: Explicit is better than implicit.
- Use `ENV` for runtime configuration, `ARG` for build-time configuration: Swapping these means secrets or build metadata leak into the runtime environment (or vice versa). Flag
ARGused for values that need to persist at runtime as MINOR. - Set `WORKDIR` explicitly: Relying on implicit
/as working directory makes paths fragile. Flag missingWORKDIRin any non-trivial Dockerfile as MINOR. - Expose only necessary ports:
EXPOSEis documentation, not enforcement, but flagEXPOSE 0-65535or overly broad port ranges as MAJOR.
Common Anti-Patterns
| Anti-Pattern | Severity | Reason |
|---|---|---|
| `FROM ... AS ... \ | FROM latest` | BLOCKER |
Secrets in ENV or ARG | BLOCKER | Persisted in image history |
curl -k / --no-check-certificate | BLOCKER | Disables TLS, supply chain risk |
No USER in final stage | MAJOR | Container runs as root |
| Single-stage with build tools | MAJOR | Inflates attack surface and image size |
RUN apt-get install without cleanup in same layer | MAJOR | Layer bloat |
COPY . . before dependency install | MINOR | Busts cache on every code change |
Multiple RUN for related commands | MINOR | Unnecessary layers |
Missing WORKDIR | MINOR | Implicit / is fragile |
Missing HEALTHCHECK | MINOR | Orchestrators can't probe readiness |
ADD for local files | NIT | Use COPY; ADD semantics are implicit |
| Unnamed multi-stage | NIT | Reduces readability and targeted build capability |
Go Review Rules
These rules supplement the common review framework. Apply them to .go files.
Style Standard
Follow Effective Go and Go Code Review Comments (the official standards). Additionally:
- Run
gofmt/goimports— formatting is non-negotiable in Go. Do not flag any formatting issue that these tools handle. - Naming: short, concise names.
inotindexfor loop vars.ctxnotcontext.errnoterror. Exported names are the API — make them clear. - Package names: lowercase, single word, no underscores. The package name is part of the call site (
http.Get, nothttpPackage.Get). - No stutter:
http.HTTPServer→http.Server. Package-qualified names should read naturally. - Acronyms:
ID,URL,HTTPin all caps when exported.id,url,httpin lower when unexported. - Getters: no
Getprefix.user.Name()notuser.GetName(). Setters useSetprefix:user.SetName(). - Interface names: single-method interfaces use
-ersuffix:Reader,Writer,Closer,Stringer. - Comment every exported name. Comments start with the name:
// Server represents an HTTP server. - No
init()unless absolutely necessary — it hides side effects and makes testing harder.
Prefer Modern Features
| Legacy Pattern | Prefer | Since |
|---|---|---|
| Manual error type switching | errors.Is(), errors.As() | 1.13 |
interface{} | any (type alias) | 1.18 |
Manual sort with sort.Slice | slices.Sort, slices.SortFunc | 1.21 |
| Manual min/max | min(), max() builtins | 1.21 |
| Manual contains check | slices.Contains | 1.21 |
| Type-specific containers | Generics with type parameters | 1.18 |
sync.Mutex for simple atomics | atomic.Int64, atomic.Bool, etc. | 1.19 |
ioutil package | io and os equivalents | 1.16 |
golang.org/x/exp/maps, slices | maps, slices stdlib | 1.21 |
Goroutine leak with bare go func() | errgroup.Group, or sync.WaitGroup.Go for fire-and-wait without error collection | 1.25 |
| Context-less function signatures | Accept context.Context as first parameter | 1.7 |
log.Println / log.Fatalf | slog (structured logging) | 1.21 |
| Global logger | Inject *slog.Logger via dependency | 1.21 |
Type System
- Prefer small interfaces: Interfaces with 1-2 methods are idiomatic Go. Flag interfaces with 5+ methods — likely too broad.
- Define interfaces at the consumer, not the provider: The package that uses the interface should define it. Flag interfaces defined next to their only implementation.
- Accept interfaces, return structs: Functions should accept interfaces for flexibility but return concrete types for clarity.
- Struct embedding: Use for composition, not inheritance. Flag embedded types that expose methods the outer type shouldn't have.
- Generics: Use for containers, algorithms, and utility functions. Flag generic code where a concrete type or interface would be simpler — don't over-generalize.
- Type aliases vs definitions:
type UserID string(new type, prevents mixing) vstype UserID = string(alias, interchangeable). Flag aliases where a distinct type would provide safety.
Functional Patterns
Go is not a functional language, but these patterns apply:
- Prefer value semantics over pointer semantics when structs are small — reduces aliasing bugs
- Flag mutation of slice/map parameters without documentation — callers may not expect it
- Prefer returning new slices/maps over mutating inputs
- Use functional options pattern (
func WithTimeout(d time.Duration) Option) for configurable constructors - First-class functions: use function types and closures for strategy patterns, middleware, decorators
- Flag global mutable state (
varat package level) — inject dependencies instead
Error Handling
Go's explicit error handling is a feature, not a problem. Review it carefully:
- Never
_ = someFunc()that returns an error — BLOCKER unless explicitly justified - Never bare
if err != nil { return err }without wrapping context — usefmt.Errorf("doing X: %w", err)for wrapped errors - Flag error messages starting with uppercase or ending with punctuation — Go convention is lowercase, no period
- Flag
panicin library code — BLOCKER. Panics are for truly unrecoverable situations inmainorinit. - Flag
log.Fatal/os.Exitin library code — it kills the process. Only allowed inmain. - Encourage sentinel errors (
var ErrNotFound = errors.New(...)) for expected failure modes - Encourage custom error types implementing
errorfor errors carrying structured data - Flag
errors.Newin hot paths — pre-allocate as package-level vars - Use
errors.Is()anderrors.As()for checking — not string comparison or type assertions
Standard Library HTTP (net/http)
- Use
http.NewServeMux(1.22+) with method-based routing:mux.HandleFunc("GET /users/{id}", handler) - Flag
http.DefaultServeMuxin production — it's a global, shared across packages - Set timeouts on
http.Server:ReadTimeout,WriteTimeout,IdleTimeout. Flag zero-value servers — MAJOR (slowloris risk). - Flag handlers that don't check
r.Context().Done()for long-running operations - Use
http.MaxBytesReaderon request bodies — flag unboundedio.ReadAll(r.Body)(DoS risk) - Middleware: use
func(http.Handler) http.Handlerpattern. Flag middleware that doesn't callnext.ServeHTTP.
Gin
- Context abuse:
gin.Contextis both request context and response writer. Flag storing*gin.Contextbeyond the handler scope — it's not safe after the handler returns. - Binding and validation: Use
ShouldBindJSON(returns error) notBindJSON(writes 400 automatically). Let the handler control the error response. - Middleware: Flag
c.Next()misuse.c.Abort()should be followed by a return. - Route grouping: Group routes by resource/domain. Flag flat route registration with 20+ routes.
- Error handling: Use
c.Error()to collect errors and handle them in middleware, notc.JSON(500, ...)scattered in handlers. - Avoid global Gin engine: Flag
gin.Default()at package level. Create the engine inmainor a constructor.
gRPC
- Proto design: Flag overly large messages (50+ fields). Use composition with nested messages.
- Error codes: Use proper gRPC status codes (
codes.NotFound,codes.InvalidArgument). Flagcodes.Internalfor all errors — be specific. - Interceptors: Use interceptors for cross-cutting concerns (auth, logging, tracing). Flag auth checks in individual RPC methods.
- Streaming: Flag server-side streams that don't check
stream.Context().Err()— clients may disconnect. - Deadlines: Flag RPC calls without deadline/timeout set on the context —
context.WithTimeout. Unbounded RPCs can hang forever. - Proto backwards compatibility: Flag removal or renumbering of fields in
.protofiles — BLOCKER. Usereservedfor removed fields.
Concurrency
Go concurrency requires careful review:
- Goroutine lifecycle: Every
go func()must have a clear termination path. Flag goroutines without cancellation (context) or done channels — goroutine leak risk (BLOCKER). - Prefer `errgroup.Group` over bare goroutine spawning — manages lifecycle, collects errors, propagates cancellation.
- Channel direction: Function parameters should specify direction (
chan<- Tor<-chan T). Flag bidirectional channels in function signatures. - Mutex scope: Keep critical sections small. Flag mutexes protecting entire function bodies — rethink the design.
- sync.Once for initialization: Flag double-checked locking patterns — use
sync.Once. - Race conditions: Flag shared state accessed from goroutines without synchronization. Suggest
-raceflag in tests. - Context propagation: Pass
context.Contextthrough the call chain. Flag functions that create their owncontext.Background()when a caller could provide one. - Select with default: Flag
selectwithdefaultin loops without a sleep/backoff — busy loop (CPU burn).
Testing
- Table-driven tests: use
[]struct{ name string; ... }witht.Run(tc.name, ...). Flag repetitive test functions that could be parameterized. t.Helper(): call in test helper functions for correct error line reporting.t.Parallel(): encourage for independent tests. Flag tests that share mutable state.- Prefer stdlib
testingover testify when possible. If using testify, useassert(continues) vsrequire(stops) deliberately. t.Cleanup()for teardown instead ofdefer— survives subtests.- Flag
time.Sleepin tests — use channels, tickers, ortesting.Tdeadlines for synchronization. For testing concurrent code with virtual time, prefertesting/synctest(GA in 1.25) over real sleeps. - For HTTP handlers: use
httptest.NewRecorder()andhttptest.NewRequest(). - Flag tests that depend on network, filesystem, or environment without build tags or skip conditions.
Common Enterprise Anti-Patterns
- Interface pollution: Defining interfaces before there are multiple implementations. Define interfaces at the consumer when you actually need the abstraction.
- Package `util` / `common` / `helpers`: Dumping ground for unrelated functions. Name packages by what they provide, not by how vague they are.
- Premature channels: Using channels for simple mutex-protected state. Channels are for communication between goroutines, not as a generic synchronization primitive.
- Ignoring context: Functions that accept
context.Contextbut don't pass it to downstream calls. Every I/O call should respect context. - Over-packaging: 50 packages for a simple service. Go favors fewer, larger packages over Java-style one-class-per-package.
- Error string matching:
if err.Error() == "not found"— fragile. Use sentinel errors orerrors.Is. - Pointer overuse: Using
*Fooeverywhere "for performance." Value semantics are often faster (less GC pressure) and safer for small structs. - Missing graceful shutdown:
http.ListenAndServewithout signal handling. Usesignal.NotifyContext+server.Shutdown(ctx).
Java Review Rules
These rules supplement the common review framework. Apply them to .java files.
Target the current LTS — Java 25 (GA September 2025) — when recommending modern features. Java 21 remains a widely-deployed LTS; gate suggestions on the project's actual toolchain version rather than assuming the latest.
Style Standard
Follow the Google Java Style Guide as the baseline:
- 2-space or 4-space indentation (respect project config). Google uses 2, many enterprises use 4.
- Braces on same line (K&R style). No single-statement blocks without braces.
- Column limit: 100 (Google) or 120. Respect project config.
- Static imports grouped separately, after all non-static imports. Wildcard imports (
*) discouraged. - Javadoc on all public API members.
@param,@return,@throwsfor non-trivial methods. @Overrideon every overriding method — flag missing annotation.- Constants:
UPPER_SNAKE_CASE. Everything else:camelCase/PascalCaseper Java convention. - Annotations on their own line before the annotated element, not inline.
Do not flag formatting issues that Checkstyle / Spotless / google-java-format would auto-fix.
Prefer Modern Features
| Legacy Pattern | Prefer | Since |
|---|---|---|
| Verbose data classes (getters, setters, equals, hashCode, toString) | record | 17 |
instanceof + manual cast | Pattern matching instanceof | 16 |
Long if/else if chains on type | switch with pattern matching | 21 |
instanceof / switch boxing primitives to match | Primitive patterns in switch and instanceof | 25 |
ThreadLocal for request-scoped context | Scoped Values (ScopedValue) | 25 |
Boilerplate validation/this(...) before constructor body | Flexible constructor bodies (statements before super()/this()) | 25 |
| Extensive class hierarchies for variants | sealed classes/interfaces | 17 |
Optional.get() without check | orElse, orElseThrow, map, ifPresent | 8 |
Collections.unmodifiableList(new ArrayList<>(...)) | List.of(), Map.of(), Set.of() | 9 |
| Anonymous inner classes (single method) | Lambda expressions | 8 |
External iteration (for loop) | Stream API or enhanced for-each | 8 |
Thread / ExecutorService for concurrent tasks | Virtual threads (Thread.ofVirtual()) | 21 |
| String concatenation in loops | StringBuilder or String.join() or """ text blocks | 15 |
SimpleDateFormat | java.time API (LocalDate, Instant, DateTimeFormatter) | 8 |
| Checked exceptions for domain errors | Custom unchecked exceptions or Result types | — |
null as return value for "not found" | Optional<T> | 8 |
Raw types (List without generics) | Parameterized types (List<String>) | 5 |
Type System
- Flag raw types:
List,Mapwithout type parameters — MAJOR. - Flag unchecked casts:
(List<String>) objwithout prior type check. Use pattern matching instanceof. - Encourage sealed interfaces: For domain types with known subtypes — enables exhaustive switch.
- Records for DTOs: If a class is just data (getters, equals, hashCode), it should be a
record. Flag manual implementations of what a record gives free. - Var for local variables: Encourage
varwhen the right-hand side makes the type obvious. Flagvarwhen it obscures the type. - Generics: Flag method signatures with more than 2 wildcards (
? extends,? super) — likely too complex. Consider a named type parameter.
Functional Patterns
- Prefer Stream API for collection transformations —
filter,map,flatMap,collect - Flag streams that mutate external state — streams should be pure pipelines
- Prefer method references (
String::toLowerCase) over trivial lambdas (s -> s.toLowerCase()) - Flag
Optionalused as a field type or method parameter —Optionalis for return values only - Flag
Optional.get()withoutisPresent()check — useorElseThrow()ormap/flatMapchains - Encourage
Collectors.toUnmodifiableList()orStream.toList()(16+) overCollectors.toList() - Flag nested streams (stream inside a stream's
map) — usually indicates a need forflatMapor extracting a method
Error Handling
- Never empty catch block — BLOCKER. At minimum, log and rethrow or explain why ignored.
- Flag
catch (Exception e)/catch (Throwable t)at fine-grained level — catch specific types. - Flag
throws Exceptionon method signatures — be specific about what can fail. - Encourage domain-specific exception hierarchy:
DomainException→OrderNotFoundException, etc. - Flag checked exceptions used for business logic flow — prefer unchecked exceptions or result types.
- Use try-with-resources for all
AutoCloseable— flag manualfinallyblocks for resource cleanup. - Flag
e.printStackTrace()— use a logging framework (SLF4J + Logback/Log4j2).
Spring Boot
- Constructor injection only: Flag
@Autowiredon fields — use constructor injection (preferably with Lombok@RequiredArgsConstructoror manual constructor). Field injection hides dependencies and breaks testability. - Layer discipline: Controller → Service → Repository. Flag controllers calling repositories directly. Flag services importing Spring Web types (
HttpServletRequest,ResponseEntity). - DTO ↔ Entity separation: Flag JPA entities exposed in API responses/requests. Use DTOs (records) at the API boundary.
- Validation: Use
@Valid/@Validatedon request DTOs with Bean Validation annotations. Flag manual validation in controllers for common rules. - Exception handling: Use
@ControllerAdvice/@RestControllerAdvicewith@ExceptionHandler. Flag try/catch in individual controllers for error-to-response mapping. - Profiles and configuration: Flag hardcoded URLs, credentials, feature flags. Use
@Value/@ConfigurationPropertieswith profiles. - Transaction management:
@Transactionalon service methods, not repositories or controllers. Flag@Transactionalon read-only queries withoutreadOnly = true. - Avoid `@Component` scanning abuse: Flag
@Service/@Componenton classes that should be explicitly configured as@Bean(e.g., third-party wrappers, conditional beans). - Security: Flag endpoints missing
@PreAuthorizeor Spring Security config. Flag disabled CSRF without justification.
Quarkus
- CDI over Spring DI: Use
@Inject,@ApplicationScoped,@RequestScoped. Flag Spring-specific annotations in Quarkus code. - Native-image awareness: Flag reflection-heavy patterns that break GraalVM native compilation. Use
@RegisterForReflectionwhen necessary. - RESTEasy Reactive: Prefer reactive endpoints (
@GETreturningUni<T>/Multi<T>) for non-blocking I/O. Flag blocking calls without@Blockingannotation. - Panache: Prefer Active Record or Repository pattern via Panache over raw JPA
EntityManagerfor standard CRUD. - Configuration: Use
@ConfigPropertyor MicroProfile Config. Flag hardcoded values. - Health and metrics: Flag missing health checks (
@Liveness,@Readiness) in production services. - Dev Services: Leverage Quarkus Dev Services for tests. Flag manual container setup in tests when Dev Services would work.
Testing
- Use JUnit 5 (
@Testfromorg.junit.jupiter). Flag JUnit 4 (org.junit.Test) in new code. - Prefer AssertJ (
assertThat) over JUnit assertions — more readable, better error messages. @Nestedclasses for grouping related tests (replaces descriptive naming conventions).- Flag
@SpringBootTestwhen a@WebMvcTestor@DataJpaTestslice would suffice — startup cost. - Use
@MockBeanor Mockito@Mock+@InjectMocks. Flag mock setup that reaches 3+ levels deep — indicates the code under test has too many dependencies. - Flag tests without assertions — MAJOR.
- Parameterized tests (
@ParameterizedTest+@CsvSource/@MethodSource) for data-driven tests. - For Quarkus: use
@QuarkusTestfor integration,@QuarkusTestResourcefor external dependencies.
Common Enterprise Anti-Patterns
- God service:
XxxServicewith 20+ methods. Split by use case or aggregate. - Anemic domain model: Entities are pure data bags, all logic in services. Move behavior to domain objects where it belongs.
- Overuse of `@Transactional`: Every method annotated — transactions should be at the use-case level, not per-method.
- Stringly-typed code: Using
Stringfor IDs, statuses, currency codes. Userecord-wrapped primitives or enums. - `Util` / `Helper` classes: Static method dumping grounds. Refactor into domain-specific methods or extension services.
- Lombok abuse:
@Dataon JPA entities (breaks equals/hashCode with lazy-loaded fields). Use@Getter+@Setter+ explicit@EqualsAndHashCodeexcluding lazy fields, or use records for DTOs. - Over-abstraction:
AbstractBaseService<T>with a single implementation — YAGNI. Create abstractions when the second use case arrives. - Ignoring `java.time`: Using
Date,Calendar,Timestampin new code. Always usejava.timetypes. - Mutable `ThreadLocal` for context: On Java 25+, prefer immutable
ScopedValuefor request/task-scoped context — it has clearer lifetime semantics and works cleanly with virtual threads and structured concurrency. FlagThreadLocalset-and-forget that risks leaking across pooled threads.
Python Review Rules
These rules supplement the common review framework. Apply them to .py and .pyi files.
Style Standard
Follow PEP 8 as the baseline, enforced by Ruff (or Black + isort). Additionally:
- PEP 484 / PEP 526: Type hints on all public function signatures. Internal functions should have hints when non-obvious.
- PEP 585: Use built-in generics (
list[str],dict[str, int]) nottyping.List,typing.Dict(deprecated since 3.9). - PEP 604: Use
X | Yunion syntax, notUnion[X, Y](3.10+). - PEP 695: Use the
type X = ...statement for type aliases and inline type parameters (class Box[T],def first[T](xs: list[T]) -> T) instead ofTypeAlias/ explicitTypeVardeclarations (3.12+). - PEP 673: Use
Selffor methods returning the same class type. - Docstrings: Google style or NumPy style — pick one and be consistent within the project. Flag mixed styles.
- Line length: 88 (Black default) or 120 — respect project config. Do not flag line length if a formatter is configured.
Do not flag formatting issues that Ruff/Black would auto-fix. Focus on semantic and structural issues.
Prefer Modern Features
| Legacy Pattern | Prefer | Since |
|---|---|---|
typing.Optional[X] | `X \ | None` |
typing.List, typing.Dict, etc. | list, dict, tuple, set | 3.9 |
typing.Union[X, Y] | `X \ | Y` |
NamedTuple class syntax | @dataclass(frozen=True) or NamedTuple functional form | 3.7+ |
| Plain dicts for structured data | dataclass, TypedDict, or Pydantic BaseModel | 3.7+ |
if/elif/elif chains on a value | match/case (structural pattern matching) | 3.10 |
TypeAlias / explicit TypeVar declarations | type X = ... statement and inline generics (class Box[T], def f[T]()) | 3.12 |
.format() templating with manual escaping | t-strings (template string literals) for safe custom string processing | 3.14 |
@abstractmethod + ABC for protocols | Protocol (structural subtyping) | 3.8+ |
try/except for flow control | LBYL with guards, or match/case | — |
Manual __enter__/__exit__ | contextlib.contextmanager or contextlib.asynccontextmanager | — |
os.path | pathlib.Path | 3.4+ |
% formatting or .format() | f-strings | 3.6+ |
dict.keys() iteration | Iterate dict directly | — |
| Mutable default arguments | field(default_factory=...) or None sentinel | — |
Type System
- Flag untyped public APIs: All public functions, methods, and class attributes should have type annotations.
- Flag `Any`: Same rule as
anyin TypeScript — MAJOR unless justified. - Encourage `Protocol` over
ABCwhen you only need structural compatibility, not inheritance. - Encourage `TypeGuard` / `TypeIs` for custom type narrowing functions.
- Flag `cast()` the same way as TypeScript's
as— it bypasses checking. - Generics: Prefer PEP 695 inline type parameters (
class Box[T],def f[T]()) over module-levelTypeVardeclarations (3.12+). Use constraints/bounds ([T: numbers.Real]) where applicable. Flag unbounded type parameters on public APIs — it's the Python equivalent ofany. - `TypedDict` for dictionaries with known keys. Flag raw
dict[str, Any]for structured data. - `Literal` types for string enums and fixed values. Flag
strwhere only specific values are valid.
Functional Patterns
- Prefer list/dict/set comprehensions over
map/filterwith lambdas — more Pythonic and readable - Prefer generator expressions for large sequences — lazy evaluation, lower memory
- Flag mutable default arguments (
def foo(items=[])) — BLOCKER, shared state bug - Encourage
functools.reduce,itertoolsfor complex transformations - Flag mutation of function parameters — create new objects instead
- Encourage
@dataclass(frozen=True)for immutable value objects - Use
tuplefor fixed-size immutable sequences,frozensetfor immutable sets
Error Handling
- Never bare
except:orexcept Exception:without re-raising — BLOCKER - Prefer specific exception types. Flag
except Exception as e: pass. - Use custom exception hierarchies for domain errors — not
ValueErrorfor everything. - Flag
exceptblocks that silently swallow and return a default — MAJOR if the default hides data integrity issues. - Encourage
ExceptionGroupandexcept*for concurrent error handling (3.11+). - Logging: use
logger.exception()in catch blocks (includes traceback), notlogger.error(str(e)).
FastAPI
- Pydantic models for all I/O: Flag raw dicts or untyped parameters in route handlers. All request bodies, query params, and responses should use Pydantic
BaseModel. - Dependency injection: Use
Depends()for shared logic (auth, DB sessions, config). Flag manual instantiation in route handlers. - Response models: All routes should specify
response_model— this enforces output validation and drives OpenAPI docs. - Status codes: Use
status.HTTP_xxxconstants, not magic integers. Flagreturn {"error": "..."}— useHTTPExceptionor custom exception handlers. - Async consistency: If the route is
async def, all I/O inside must be awaited. Flag sync I/O (e.g.,open(),requests.get()) in async routes — useaiofiles,httpx, or run in executor. - Path operations: Flag business logic in route functions. Route handlers should validate input, call a service, and return output.
- Security: Flag routes missing dependency-injected auth. Flag
Depends()chains that don't propagate auth context. - Background tasks: Use
BackgroundTasksfor fire-and-forget work, not bareasyncio.create_task()— FastAPI manages lifecycle.
SQLAlchemy
- Session management: Flag sessions created without a context manager or
try/finally. Usewith Session() as session:or FastAPI'sDepends(get_db). - N+1 queries: Flag relationship access in loops without
joinedload(),selectinload(), orsubqueryload(). This is a BLOCKER in production. - Raw SQL: Flag
text()queries that interpolate user input — SQL injection risk (BLOCKER). Use bound parameters. - Model design: Flag models mixing domain logic with ORM mapping. Keep models thin — business logic belongs in services.
- Migrations (Alembic): Flag manual schema changes without a migration. Flag
op.execute()with raw DDL that could be expressed as Alembic operations. - Eager loading strategy: Flag
lazy="select"(the default) on relationships accessed in list views — uselazy="selectin"or explicit loading. - Prefer 2.0 style: Flag legacy
QueryAPI (session.query(...)) — useselect()statements withsession.execute(). - Transaction boundaries: Flag commits inside loops. Prefer a single commit per unit of work.
Testing
- Use
pytestidioms: plain functions overunittest.TestCaseclasses - Use
pytest.fixturefor setup, notsetUp/tearDown - Flag
mock.patchon internal implementation details — mock at boundaries (HTTP, DB, file system) - Use
pytest.raiseswithmatchparameter for exception messages - Prefer
factory_boyor fixture factories over complex manual test data setup - Flag tests without assertions (MAJOR — false green)
- Parameterize related test cases with
@pytest.mark.parametrizeinstead of copy-paste
Common Enterprise Anti-Patterns
- Circular imports: Usually indicates wrong module boundaries. Suggest restructuring or using
TYPE_CHECKINGimports. - God modules: A
utils.pyorhelpers.pywith 500+ lines — split by domain. - Stringly-typed code: Using
strfor statuses, types, modes — useEnum,Literal, orStrEnum. - Global mutable state: Module-level mutable variables shared across requests — use dependency injection or request-scoped state.
- Ignoring async: Using synchronous libraries (e.g.,
requests) in an async application — causes thread starvation. - Over-inheriting: Deep class hierarchies where composition would be simpler and more flexible.
- Missing `__all__`: Public modules should define
__all__to make the public API explicit.
Rust Review Rules
These rules supplement the common review framework. Apply them to .rs files.
Style Standard
Follow the Rust API Guidelines and Rust Style Guide (the official standards). Additionally:
- Run
rustfmtandclippy— formatting and most lints are non-negotiable. Do not flag any formatting issue or basic lint that these tools handle (e.g.cargo clippy -- -D warnings). - Naming:
snake_casefor functions, variables, modules, and crates;CamelCasefor types, traits, and enum variants;SCREAMING_SNAKE_CASEfor constants and statics. Flag deviations. - Acronyms are treated as one word:
Uuid,HttpClient,parse_id— notUUID,HTTPClient,parseID. - Conversions:
as_*(borrow → borrow, cheap),to_*(borrow → owned, expensive),into_*(owned → owned, consuming). Flag ato_*method that is actually cheap, orinto_*that doesn't consumeself. - Getters: no
get_prefix for the common case —user.name()notuser.get_name().get_is reserved for theIndex-like fallible accessor convention. - Document every public item with
///doc comments; document the crate/module with//!. Flag missing docs onpubitems in a library crate. - Prefer
#[non_exhaustive]on public enums/structs that may grow — flag public enums likely to gain variants without it (forces downstream_ =>arms, easing SemVer evolution).
Prefer Modern / Idiomatic Features
| Legacy / Non-idiomatic | Prefer | Notes |
|---|---|---|
match opt { Some(x) => x, None => return ... } | let Some(x) = opt else { return ... }; | let-else (1.65+) |
if let Some(x) = opt { ... } else { ... } for binding-or-default | let x = opt.unwrap_or(default); / unwrap_or_else | clearer intent |
Manual impl of From/Display for errors | thiserror derive (libraries) | less boilerplate |
Stringly-typed errors / Box<dyn Error> everywhere | anyhow::Result (apps) / typed enums (libs) | see Error Handling |
vec.iter().map(...).collect::<Vec<_>>() then loop | chain iterator adaptors, collect once | avoid intermediate allocations |
.clone() to satisfy the borrow checker | borrow, restructure, or Rc/Arc deliberately | see Ownership |
lazy_static! | std::sync::LazyLock / OnceLock (1.80+) | stdlib, no macro crate |
mem::replace(&mut x, Default::default()) | mem::take(&mut x) | clearer |
Manual Future polling | async/.await | unless writing a runtime primitive |
extern crate declarations | edition 2018+ implicit imports | remove |
Type System & Ownership
Rust's type system encodes invariants — review whether the change uses it or fights it:
- Make illegal states unrepresentable: prefer enums over a struct with several
Optionfields and "only one is set" invariants. Flagboolflags or sentinel values where a 2–3 variant enum would be self-documenting and exhaustively checked. - Newtypes for domain values: flag raw
String/u64passed where mixing units/IDs is possible (UserId,Cents). Astruct UserId(u64)prevents accidental argument swaps the compiler can't otherwise catch. - Borrow, don't own, in function signatures: accept
&strnotString,&[T]notVec<T>,&TnotTwhen you only read. Flag owned parameters that force callers into needless clones/allocations. - `impl Trait` vs generics vs `dyn`:
impl Traitin argument position for simple cases; named generic<T: Trait>when the type is referenced more than once;Box<dyn Trait>only when you genuinely need heterogeneous types or dynamic dispatch. Flagdynused purely to avoid writing a generic — it costs a vtable indirection. - `.clone()` as a borrow-checker escape hatch: flag clones that exist only to sidestep a borrow error, especially in hot paths or on large owned types. Ask whether a borrow, a restructure, or
Rc/Arcis the right tool. A.clone()of aStringin a loop is a MINOR smell; cloning a largeVec/struct per iteration is MAJOR. - `Rc`/`Arc` + `RefCell`/`Mutex`: this combination re-introduces shared mutability that the borrow checker normally prevents. Flag it when a simpler ownership model (single owner + borrows, or passing
&mut) would work — it moves aliasing bugs and borrow violations from compile time to runtime (RefCellpanics). - Lifetimes in public APIs: flag gratuitous explicit lifetimes that elision would cover, and flag returning references tied to local data (won't compile, but the design of returning a borrow vs an owned value is worth a comment).
Error Handling
Rust models errors as values — review them as carefully as Go's:
- `.unwrap()` / `.expect()` in library code: BLOCKER on any input-derived or fallible value — it panics the caller's process. Acceptable only for invariants that are provably infallible (and then
.expect("reason")documenting why). Tests andmain/prototypes are exempt. - `panic!` / `unreachable!` / `todo!` / `unimplemented!` in shipped library paths: BLOCKER. Return a
Resultinstead.unreachable!is acceptable only when the compiler can't see exhaustiveness but you can prove it. - `?` over manual matching: prefer
?for propagation. Flag verbosematch/if letthat only forwards the error — but ensure the error type implementsFromfor the conversion, or use.map_err(...). - Library vs application error strategy: libraries should expose typed errors (an enum, ideally via
thiserror) so callers can match on variants — flag a public API returninganyhow::ErrororBox<dyn Error>, which erases the variant. Applications/binaries may useanyhow/eyrefor convenience. - Don't swallow errors: flag
let _ = fallible();andif let Ok(x) = ... {}with noelsethat silently drops the error — equivalent to an empty catch block. Also flag.ok()used purely to discard an error without intent. - Error context: flag bare propagation that loses context. Encourage
.with_context(|| ...)(anyhow) or a wrapping variant so logs show what operation failed, not just the leaf cause. - `Result` must be used:
Resultis#[must_use], but flag any deliberate ignoring. Neverunwrap()aResultwhoseErrcarries information you discard.
Unsafe
unsafe opts out of the compiler's guarantees — review it like security-critical code:
- Every `unsafe` block needs a `// SAFETY:` comment stating the invariants the caller/author guarantees. Flag missing SAFETY comments — MAJOR.
- Justify the `unsafe`: flag
unsafeused for performance without a benchmark, or where a safe abstraction (split_at_mut,slice::windows,Vec::with_capacity) exists. Most application code should have zerounsafe. - `unsafe fn` must document preconditions in a
# Safetydoc section. Flag publicunsafe fnwithout it. - Raw pointers, `transmute`, `mem::uninitialized`/`MaybeUninit`, FFI: scrutinize for UB — aliasing
&mut, dangling pointers, invalid bit patterns, alignment, and uninitialized reads. Any potential UB is a BLOCKER. - `unwrap` inside `unsafe` that panics across an FFI boundary is undefined behavior — BLOCKER.
Functional & Iterator Patterns
- Prefer iterator chains (
.iter().filter().map().collect()) over manual index loops — clearer and bounds-check-friendlier. But flag chains so long they obscure intent; extract named steps. - Flag
.collect::<Vec<_>>()immediately followed by another iteration — fuse the chain andcollectonce, or don't collect at all. - Prefer
.unwrap_or,.unwrap_or_else,.map,.and_then,.ok_oronOption/Resultover manualmatchwhen it reads clearer. - Flag
forloops that merely accumulate —sum(),product(),fold(),collect()express intent and avoid mutable accumulators. - Prefer immutability: a binding without
mutis the default. Flaglet mutthat is never mutated (clippy catches this) and, more importantly, mutation-heavy code where a transformation pipeline would be clearer and alias-free. - Closures capturing by
moveunnecessarily — flagmovethat forces clones when a borrow would do.
Concurrency & Async
- `Send`/`Sync` are the compiler's race guards — but review what crosses threads. Flag
Arc<Mutex<T>>protecting a large critical section, or aMutexheld across an.awaitpoint (deadlock / blocks the executor) — MAJOR/BLOCKER. - Blocking in async: flag synchronous blocking calls (
std::fs,std::thread::sleep, blocking DB drivers, heavy CPU work) inside anasync fn— it stalls the runtime's worker thread. Use the async equivalent ortokio::task::spawn_blocking. BLOCKER in a server hot path. - `.await` holding a lock or `RefCell` borrow: flag — the guard lives across the suspension point.
- Spawned task lifecycle: flag
tokio::spawnwhoseJoinHandleis dropped with no supervision or cancellation path — detached tasks leak and swallow panics. Mirror Go's goroutine-leak rule. - `std::sync::Mutex` vs `tokio::sync::Mutex`: in async code, use the async mutex only when the guard must cross
.await; otherwise the std mutex is cheaper. Flag the wrong choice. - Channels: prefer the right primitive (
mpsc,oneshot,broadcast). Flag a busy-loop polling atry_recv()without backoff. - `unwrap()` in spawned threads/tasks: panics there are isolated and silently lost — flag as a swallowed failure.
Testing
- Unit tests live in a
#[cfg(test)] mod testsblock in the same file; integration tests intests/. Flag tests that exercise private internals fromtests/(they can't see them) or heavy logic with no#[cfg(test)]coverage. - Prefer
assert_eq!/assert!with informative messages. Flagassert!(a == b)whereassert_eq!gives a better diff. - Use
#[should_panic(expected = "...")]with theexpectedsubstring — flag bare#[should_panic]that could mask the wrong panic. - Flag
.unwrap()chains in tests that obscure which step failed — acceptable but.expect("...")aids debugging. - Encourage property-based tests (
proptest/quickcheck) for pure functions with broad input domains; flag exhaustive hand-written cases where a property would be stronger. - Flag tests depending on wall-clock time, filesystem, or network without isolation. Inject a clock or use a tempdir.
- For async tests use
#[tokio::test](or the runtime's macro) — flagblock_onscattered in test bodies.
Common Anti-Patterns
- `.unwrap()` culture: pervasive
.unwrap()outside tests/prototypes — each is a latent panic. The most common Rust review finding. - Clone-to-compile: sprinkling
.clone()until the borrow checker is satisfied, instead of fixing ownership. Hides design problems and allocates. - `Arc<Mutex<_>>` as a default: reaching for shared mutability before considering message passing or single-ownership designs.
- Stringly-typed APIs:
fn handle(action: &str)with a match on string literals — use an enum; the compiler then enforces exhaustiveness (mirrors the OCP/switch anti-pattern). - `Box<dyn Error>` in library public APIs: erases error types so callers can't match — return a typed enum.
- `unsafe` for micro-optimization without a benchmark proving the safe version is the bottleneck.
- Over-use of generics: deeply generic signatures (
fn f<T, U, V, W>(...)with many bounds) where two concrete types or adyntrait object would read far better and compile faster. - `pub` everything: leaking internals as
pubinstead ofpub(crate)/private — everypubitem is part of your SemVer contract. - Ignoring `#[must_use]`: discarding a
Resultor a builder's returned value. - Index-based loops over `.iter()`:
for i in 0..v.len() { v[i] }reintroduces bounds checks and panics the borrow checker would prevent.
TypeScript / JavaScript Review Rules
These rules supplement the common review framework. Apply them to .ts, .tsx, .js, and .jsx files.
Prefer Modern Features
Flag legacy patterns when modern alternatives exist:
| Legacy Pattern | Prefer | Why |
|---|---|---|
enum (numeric/string) | Union types or as const objects | Enums leak runtime code, have quirky behavior with reverse mapping |
any | unknown + type narrowing | any disables the type system entirely |
Type assertion as Foo | Type guards / satisfies | Assertions bypass checking; satisfies validates while inferring |
interface for utility shapes | type alias | type supports unions, intersections, mapped types; use interface for contracts that may be extended |
Promise chains with .then | async/await | Readability; easier error handling |
var | const / let | Block scoping, no hoisting surprises |
arguments object | Rest parameters ...args | Type-safe, actual array |
require() | import / import() | Tree-shakeable, statically analyzable |
Index signatures [key: string] | Record<K, V> or Map | More expressive, better tooling support |
| Class with only static methods | Module-level functions | No reason for a class wrapper |
Type System
Flags
- `any` leakage: Any
anyin new code is MAJOR unless explicitly justified with a comment. Check for implicitanyfrom untyped dependencies. - Missing return types on public APIs: Exported functions should have explicit return types. Internal functions can rely on inference.
- Type assertions in chains:
foo as Bar as Bazis almost always hiding a type error — BLOCKER. - Non-null assertions (`!`): Flag unless the author explains why null is impossible. Prefer optional chaining or early returns.
- Overly broad types:
stringwhere a union of literals would enforce correctness.objectwhere a specific shape exists.
Patterns to Encourage
- Discriminated unions for state machines and variant types
satisfiesfor validating object shapes while preserving literal typesconstassertions (as const) for readonly tuples and literal types- Template literal types for string patterns
- Branded types for domain primitives (e.g.,
UserId,Email) readonlyon array/object parameters that shouldn't be mutatedusing/await usingfor resource management (Explicit Resource Management)- For published libraries: explicit return/export types to enable
isolatedDeclarations(5.5+) — faster, parallelizable.d.tsemit. Flag exported APIs relying on inferred types in packages that ship declarations.
Functional Patterns
- Prefer
map/filter/reduce/flatMapoverforloops when the intent is transformation - Flag
forEachwith side effects — if you're not returning a value, usefor...offor clarity - Encourage pure functions: same input, same output, no mutations
- Flag mutation of function parameters — use spread or
structuredClone - Prefer
Object.freeze/as const/readonlyfor data that shouldn't change - Encourage pipe/compose patterns when chaining 3+ transformations
Error Handling
- Never catch and ignore:
catch (e) {}is BLOCKER - Prefer typed error results over thrown exceptions for expected failures. Use discriminated unions:
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };catch (e: unknown)— never assume error type. Useinstanceofor type guard.- Avoid
catchat every level — let errors propagate to a boundary handler - Flag
console.log/console.errorin production code — use a structured logger
Style Standard
Follow ESLint recommended + Airbnb style guide conventions (the de-facto community standard):
- Trailing commas in multiline structures (less noisy diffs)
- Semicolons required
- Single quotes for strings (double quotes in JSX)
- Explicit function return types on exports
- No default exports (named exports for refactoring safety)
- Imports ordered: external → internal → relative, each group alphabetized
- Prefer
typeimports (import type { Foo }) to avoid runtime import of types
Do not flag style issues that ESLint/Prettier would catch. Only flag style when it affects semantics or readability beyond formatter scope.
React (.tsx / .jsx)
- Flag
useEffectwith missing or incorrect dependency arrays - Flag
useEffectused for derived state — preferuseMemoor compute inline - Flag
useStatefor values derivable from props or other state - Flag inline object/array/function literals in JSX props (causes unnecessary re-renders)
- Flag components over ~100 lines — likely needs decomposition
- Encourage custom hooks to extract reusable stateful logic
keyprop: flag array index as key when list items can reorder- Prefer Server Components by default in Next.js App Router — only add
"use client"when necessary - Flag prop drilling through 3+ levels — use context or composition
NestJS
- Module boundaries: Each module should encapsulate a bounded context. Flag cross-module direct imports that bypass the module system.
- Dependency injection: Flag
new Service()inside controllers/services. Use constructor injection. - DTOs and validation: All API inputs must have DTO classes with
class-validatordecorators. Flag raw@Body()without a DTO type. - Guards over middleware: Prefer guards (
@UseGuards) for auth/authorization over Express middleware. - Exception filters: Use domain-specific exception classes, not raw
HttpExceptionwith hardcoded status codes. - Circular dependencies: Flag
forwardRef()— usually indicates a design problem. Suggest extracting a shared module. - Repository pattern: Data access logic belongs in repositories/services, not controllers.
Next.js (App Router)
- Server vs Client: Flag
"use client"on components that don't use hooks, event handlers, or browser APIs — they should be Server Components. - Data fetching: Prefer
fetchin Server Components over client-sideuseEffect+useState. Use React Server Components for data loading. - Route handlers: Flag business logic in
route.ts— it belongs in a service layer. - Server Actions: Validate all inputs in server actions — they're publicly accessible endpoints. Use Zod or similar.
- Metadata: Flag pages missing
metadataorgenerateMetadataexports. - Loading/Error states: Flag route segments missing
loading.tsxanderror.tsxboundaries. - Caching: Be explicit about caching — flag
fetchcalls withoutcacheorrevalidateoptions in production code.
Testing
- Prefer
describe/itstructure with intention-revealing test names - Flag tests that test implementation details (e.g., asserting internal state, method call counts) over behavior
- Flag missing edge case tests: null, empty array, boundary values
- Mock at module boundaries, not deep internals
- Flag
anyin test code — tests should be type-safe too - Prefer
toEqualovertoBefor objects; prefertoStrictEqualwhen undefined properties matter
Common Enterprise Anti-Patterns
- Barrel files that re-export everything: Kills tree-shaking, creates circular dependency risks
- God services: A
UserServicewith 20+ methods — split by use case - Shared mutable singletons: Module-level
letstate accessed by multiple consumers - String-typed APIs: Using
stringfor IDs, statuses, types — use branded types or unions - Callback hell in legacy code being modified: If touching it, refactor to async/await
- Default exports: Prefer named exports for refactoring safety and IDE support
Related skills
How it compares
Checker-style review rules for Dockerfiles—not a Dockerfile generator or a CI build skill.
FAQ
Who is common-code-reviewer for?
Developers and small teams using AI agents for PR review who need Dockerfile-specific BLOCKER and MAJOR guidance.
When should I use common-code-reviewer?
In Ship review before merging container changes, in Build integrations when adding a new Dockerfile, or during Operate infra refreshes that retag base images.
Is common-code-reviewer safe to install?
Treat it like any review skill: consult Prism Security Audits on this page; it should not execute containers but may read repo Dockerfiles during review.