agentleFS
ShehabovShehabov/ai-dev-crew/.claude/skills/team-code-analysis/SKILL.md

team-code-analysis

Skill4 starsChanged 45 days agoMITView on GitHub

The line-by-line defect rubric code-analyst applies at the review-defects gate. Covers correctness, line-level security, the data layer, row-level security and database authorisation, concurrency and async, error handling, structural rot measured against fixed thresholds, and the project-specific defects drawn from PROJECT.md, the brand spec and the API contract, with the S1 to S3 finding format, the method and the gate's pass conditions. Use when scanning a diff for bugs, checking complexity and duplication, hunting structural rot, or proving a suspected defect before the regression guard and the engineering gate.

The file

Show its rules and text

We split this skill into the rules it gives an agent

13 rules. About 0 are common, 8 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈8

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Read the diff line by line for facts.

    line 8
  2. Distinctive:

    Every finding carries a file, a line, a category, what is wrong, why it is wrong, the concrete fix and a severity.

    line 23
  3. Distinctive:

    Every hit here is at least S2, and most are S1.

    line 87
  4. Distinctive:

    Run the complexity pass with a tool where the repository has one, and count branches by hand where it does not.

    line 137
  5. Very specific:

    Add one probe per invariant.

    line 182
  6. Very specific:

    Read the task brief and the ADR, so you know what the code was supposed to do.

    line 230
  7. Very specific:

    Run git diff --stat against the base ref in run.json, then git diff in full.

    line 236
  8. Very specific:

    Run the tooling and read its output rather than trusting the exit code: the typecheck and lint commands in PROJECT.md § Commands, the …

    line 246
  9. Distinctive:

    No S1 finding is open.

    line 261
  10. Distinctive:

    Every planned probe ran, with its output in evidence.

    line 263
  11. Very specific:

    Every structural threshold breach is fixed, or carried with a written, dated reason in the author's files.md and recorded as S3.

    line 264
  12. Distinctive:

    Every detection the regression brief names ran exactly as published, with its log in evidence.

    line 267
  13. Distinctive:

    No migration in the diff is non-reversible or locks a live table, unless tech-architect signed the lock window in the ADR.

    line 269

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Code analysis

Read the diff line by line for facts. A null path either exists or it does not. A query either runs inside the loop or it does not. These findings are true or false regardless of taste, and every one is proved before it is filed.

Four reviews read the same diff, each independently: peer-reviewer for judgement (review-judgement), code-analyst for defects (review-defects, this skill), code-steward for readability (review-readability) and security-analyst for security (security). All four must pass. They are separate because a well-designed change can carry a real bug, and a correct line can implement the wrong thing. Another reviewer's pass is never evidence about anything this review checks.

Outside this review: whether this is the right solution, whether the abstraction is sound, whether the rollout plan is safe. Those are peer-reviewer's. Also outside it: anything a formatter or a linter already enforces.

Every finding carries a file, a line, a category, what is wrong, why it is wrong, the concrete fix and a severity. A finding without a concrete fix is not finished.

The examples use the generic product from team-architecture: accounts, projects and invoices, with its example invariants I1 to I6. Your invariants are in PROJECT.md § Product invariants.


1. Correctness

Hunt forSignature
Off by one<= where < was meant, a cursor boundary that repeats or skips a row, a limit compared with > where >= was meant
Null and undefined pathsA value that can be null or undefined reaching a property access or a method call unchecked. An optional chain that stops early, then a bare access two lines down.
Unhandled promise rejectionAn async call with no await and no .catch, a floating promise in an effect, a .then chain whose error path returns undefined
Swallowed exceptionAn empty catch, an exception when others then null block, a try that logs and continues into an invalid state
Wrong boolean logicDe Morgan errors, && and `` precedence, a negated condition that reads correctly and is not
Wrong comparison== on floats, identity where equality was meant, string comparison of numbers, = null in SQL where is null was meant
Time zone and DSTtimestamp where timestamptz was meant, date arithmetic across a DST boundary done in days, a due date computed in the server's zone instead of the account's
Money as a floatCurrency held as float, real or a fractional JavaScript number. Use numeric or integer minor units as bigint, and round once, at the end.
Division by zeroA rate computed before its denominator is checked
Race conditionRead then write without a lock or a transaction, check-then-act, two requests both passing a uniqueness check
Mutation of shared stateA module-level mutable in a server function reused across invocations, a React state object mutated in place
Unawaited asyncA promise created and dropped in a serverless function, so the runtime freezes before it settles
Incorrect early returnA guard that returns before a required side effect, such as the audit write

2. Security

security-analyst runs the full sweep: history, dependencies, configuration and the platform's advisors. These are the line-level defects a reader of the diff must not walk past. When you find one, it is your finding too.

Hunt forSignature
InjectionSQL built by string concatenation, a dynamic format() without %I or %L, eval, an unsanitised value reaching a shell
Missing authorisationA table reachable with no policy covering the command, or a handler trusting a client-supplied identity instead of the verified session. Every table, every command.
Mass assignmentAn update policy with no with check, or a server function taking a whole row as JSON and writing it unfiltered
Secrets in code or logsA key, token or password literal. A log line carrying an email address, a name, a payment detail or free text a customer wrote.
Personal data in a URLAn email address or a personal identifier in a query string. It lands in access logs and in referrers.
Unsafe deserialisationUntrusted JSON written straight into a typed column, or a webhook body parsed without schema validation
Server-side request forgeryA user-supplied URL fetched on the server
Open redirectA next or return_to parameter not validated against an allowlist
Timing leakAn equality check on a secret or a signature that is not constant time
Missing rate limitAn endpoint that sends a message, charges a card, or can be used to enumerate accounts

3. Data layer

Hunt forSignature
N+1A query per row in a loop, where a join, an embedded select or one set-returning function would do it once
Missing indexA filter, sort, join or policy predicate on an unindexed column, especially the hottest list, such as invoices on (account_id, status, due_at)
Unbounded readA query with no limit and no cursor range
Offset paginationoffset on a list that changes while a reader pages, so they see duplicates and gaps. Use a cursor on (due_at, id).
Missing transactionTwo writes that must both happen, not wrapped in one transaction or one server function
Aggregate in a loopA count(*) per row, where one aggregate would do
Non-reversible migrationNo written reverse in the run's rollback notes, and no stated reason at the head of the migration file
Locking migrationAn alter table that rewrites a large live table, an index built without concurrently, a column type changed in place, a backfill in the same migration as the schema change
Migration outside the source of recordThe stack pack names the source of record for the schema. With stack-nextjs-supabase it is hand-authored migration files, forward-only, one concern per file. The finding is SQL applied to the target with no file behind it, a file edited after it was applied, a file carrying more than one concern, or a change made only in a file the pack says is not a source of record.

3a. Row-level security and database authorisation

This applies where PROJECT.md § Stack names a database that enforces row-level security, such as Postgres and the platforms built on it. It is the class a general scan never finds. Every hit here is at least S2, and most are S1. Traps specific to one platform are in the stack pack.

Hunt forWhy it hurtsFix
A per-row function call in a policy predicate, such as the function that returns the current userIt is evaluated for every row, so a list scan becomes thousands of callsWrap it in a scalar subquery, (select app.current_member_id()), which Postgres evaluates once per statement
security definer without a pinned search_pathA caller can shadow an object and run their own code with the definer's rightsset search_path = '', and schema-qualify every reference in the body
A view over protected data without security_invoker = onIt runs as its creator, silently bypassing the caller's policiesSet it on every view over a protected table
A new table without row-level security enabledOpen the moment anything is grantedEnable it in the same file that creates the table
Row-level security enabled with no policyDenies everything, looks like a bug, and gets "fixed" by disabling row-level securityWrite the policy in the same migration
Policies for select onlyinsert, update and delete fall to a broad grant added later to unblock someoneAll four written explicitly, even where one is false
using without with check on an update policyA row can be updated into a state the caller could not have selected, such as moved into another accountAlways both
A grant on a base table the architecture says only a server function may serveEvery function and policy above it becomes decorationRevoke, and expose only the server function
A credential that bypasses authorisation, such as a service or admin key, outside server-side secretsIt skips every policy. In a client bundle it is a full breach.S1, always
A policy predicate calling a volatile functionRe-evaluated per row, and can leak timingMark the function stable and index what it reads

4. Concurrency and async

  • A background job that is not idempotent, where the queue delivers at least once.
  • A retry with no backoff cap, or no dead-letter path.
  • Shared mutable state across requests.
  • A React effect with a missing or over-broad dependency array.
  • A React effect deriving state that could be computed during render.
  • A missing cleanup on a subscription or a timer.

5. Error handling

  • An error message that leaks internals to the client.
  • An error body that carries prose instead of a code from the contract. Copy belongs to ux-writer.
  • A caught error that returns a success shape.
  • A failure path with no observability: nothing logged, no metric, no way to know at two in the morning.
  • A user-facing failure that blames the reader. "Couldn't save the invoice" is correct. "You entered an invalid amount" is not.

6. Structural rot

Measure each one, and report the number rather than an impression.

MetricThresholdFinding
Function lengthover 50 linesIt does more than one thing. Name the things and the seams to split on.
Cyclomatic complexityover 10The measured value and the branch count. Extract the branches, or invert the guards.
Nesting depthover 3The depth and the innermost line. Guard clauses and early returns.
Parameter countover 4The parameters, and the object that should carry them
Duplicated blockover 6 lines, twice or moreBoth locations, and the extraction, or why the duplication is honest
File lengthover 400 linesThe responsibilities to split
Class methodsover 15A god object forming

Run the complexity pass with a tool where the repository has one, and count branches by hand where it does not. Report the number either way.

Named smells, and the refactor that resolves each:

SmellLooks likeResolve with
God objectOne class that knows every otherSplit by responsibility, and push behaviour to the data it uses
Flag argumentarchiveProject(project, force = false) where the body forks entirelyTwo functions with honest names
Shotgun surgeryOne change touching seven filesThe concept is smeared. Give it a home.
Feature envyA method using another object's data more than its ownMove the method
Primitive obsessionA status, a role or a currency passed as a bare string or numberAn enum or a value object
Circular importa imports b imports aThe shared thing belongs in a third module
Dead codeUnreachable, unreferenced, or behind a flag removed months agoDelete it. Git remembers.
Commented-out codeA block in commentsDelete it. Git remembers.
Unnamed literalif (days > 30) with no namePAYMENT_TERMS_DAYS, defined once
Layering violationA rule in a handler that a direct call to the data API bypasses, or a grant on a table only a function should serveSee the architecture of record and the stack pack
Boolean parameter listrender(true, false, true)An options object, or separate functions

7. Project-specific defects

A general scan never finds these. They come from PROJECT.md, from the brand spec at the path in PROJECT.md § Brand, and from the contracts in docs/architecture/contracts/. Check every one on any diff that touches the interface, an API or a product invariant. Read the brand spec and cite its section in the finding, never a value from memory.

DefectWhy it mattersSeverity
A path that can break an invariant in PROJECT.md § Product invariants: its enforcement point removed, bypassed, or held only in the clientIt is the product's claimS1, always
A direct client read of a table the architecture says only a server function may serveThe grant was revoked for a reason, and this route goes around itS1
An error that names the protected input behind an access or privacy refusalIt leaks the thing the rule protectsS1
An outbound message or a charge sent with no idempotency keyRetries are routine, and each one costs the customerS1
A design value written as a literal: a colour, spacing value, radius, duration or type sizeThe brand spec holds every value as a token, and a literal is driftS2
A spacing or size value that is not on the brand spec's scaleThe scale is closedS2
A number rendered without the brand spec's numeric treatmentFigures that do not align in a column misread as different magnitudesS2
A rate rendered without its base or sampleA percentage with no base is a claim the reader cannot checkS2
A status carried by colour alone, with no written labelFails readers who cannot tell the colours apartS2
A text and background pair below the contrast threshold in PROJECT.md § Quality bar, computed from the brand spec's hex valuesFails the accessibility standard the project committed toS2
A date or deadline rendered in the reader's time zone where the contract carries the account'sThe reader acts on the wrong dayS2
A string concatenated with a countBreaks the plural rules of many locales in PROJECT.md § LocalesS2
A physical CSS property where a logical one belongsBreaks right to left, where PROJECT.md § Locales has a right-to-left localeS2
A font or script loaded at runtime from a third-party origin the brand spec and the stack do not declareA blocked request leaves the page unreadable, and every page view is disclosed to that originS2

Add one probe per invariant. For each of I1 to In in PROJECT.md § Product invariants, the plan names the path in this diff that could break it, and the scan records whether its enforcement point still stands and whether its test still fails when that point is removed.


Finding format

One block per finding in .devteam/runs/<run-id>/code-analyst/findings.md, S1 first. The label is the severity and a sequence number.

### S1-03  Credit note written without its audit entry
file:     src/billing/credit-notes.ts:61
category: product-invariant
what:     createCreditNote() inserts the credit note, then inserts the audit entry in a
          second call, with no transaction around the two.
why:      If the second insert fails, a credit note exists with no audit entry. I6 says
          that state cannot exist, and nothing reports it: the invoice page shows a
          credit nobody can trace.
fix:      Move both inserts into the create_credit_note server function, which runs in
          one transaction, and call it once. Add a test that forces the audit insert to
          fail and asserts that no credit note remains.
evidence: evidence/code-analyst/credit-note-trace.txt

Categories: correctness, security, data, authorisation, concurrency, error-handling, structure, product-invariant, brand, locale.

LabelSeverityMeans
S1BlockerData loss, a security hole, a broken invariant, or a defect that will fire in normal use
S2MajorA real defect on a path that is reachable but not routine, or a structural threshold breach the author neither fixed nor carried
S3MinorA latent problem, a smell below threshold, or a threshold breach the author carried with a written, dated reason in files.md. Worth fixing, does not hold the gate.

A carried breach is recorded as S3 with the author's reason quoted, so it does not hold the review-defects gate. engineering-lead accepts or refuses each carry at the engineering gate. code-analyst records the carry and never grants it.

Rank by severity. Never pad the list with style opinions a formatter owns: each one makes the S1 at the top less likely to be read. Merge a finding that appears under two categories and keep the higher severity. A defect you could not prove is written as suspected, with the trace so far, as a question for the author. It is never filed as fact.


Method

  1. Read the task brief and the ADR, so you know what the code was supposed to do. Read the regression brief at .devteam/runs/<run-id>/bug-historian/brief.md, and add every detection command it names to your probe list, copied exactly as the brief publishes it: the same flags, the same pattern, the same pathspecs. A shortened command is a different check with an unknown result. Save each detection's output with a first line holding $ and the command as run, so bug-historian can compare it with the brief.
  2. Run git diff --stat against the base ref in run.json, then git diff in full. Read every changed line and the whole function around it, because context lines hide the bug more often than changed lines do. Where a changed function calls something outside the diff, read the callee too.
  3. Choose the probes from the change surface, and write them in plan.md. A diff with a migration needs every probe in sections 3 and 3a. A diff that touches a view or a server function touches authorisation, whether or not a policy changed.
  4. Work sections 1 to 7 in order. Sections 1 to 3a catch the defects that ship. Sections 6 and 7 catch the ones that accumulate.
  5. Prove each finding: trace the path, or write the failing case. Quote the lines.
  6. Run the tooling and read its output rather than trusting the exit code: the typecheck and lint commands in PROJECT.md § Commands, the complexity report, the data-layer checks the stack pack names, and a query plan for any query a policy filters. Read that plan as the role the policy applies to, inside a transaction that rolls back, because a plan read as the owner skips the policy. Nothing beyond PROJECT.md § Toolchain may be assumed, and a tool that is missing is reported as blocked, never faked.
  7. Save command output, grep results, complexity numbers and query plans under .devteam/runs/<run-id>/evidence/code-analyst/. Write the findings, ordered by severity, and record in review.md every probe that ran and returned clean. A clean probe is evidence, and a report of zero findings with no probe list reads as a shrug.

The gate

review-defects passes when every one of these is true:

  1. No S1 finding is open.
  2. No S2 finding is open.
  3. Every planned probe ran, with its output in evidence.
  4. Every structural threshold breach is fixed, or carried with a written, dated reason in the author's files.md and recorded as S3. engineering-lead accepts or refuses the carry at the engineering gate. code-analyst records the carry and never grants it.
  5. Every detection the regression brief names ran exactly as published, with its log in evidence.
  6. No migration in the diff is non-reversible or locks a live table, unless tech-architect signed the lock window in the ADR.

A fail is binding: engineering-lead does not open the engineering gate without this pass.

Handoff

On a pass: status: passed, gate review-defects result pass with findings.md as evidence, and next is bug-historian, whose regression guard runs once all four reviews are in and before engineering-lead.

On a fail: status: rejected, gate review-defects result fail, each open S1 and S2 in blockers[] with what, why and needs set to the author, the round number carried, and next is orchestrator, which routes the fix. On the third round with the same finding unfixed, escalate to the Product Lead through decisions_for_product_lead instead.

code-analyst writes findings and never edits the code under review. The author makes the fix.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.