coderot

AI-writtenhuman-writtenrotting

Is AI-written code rotting your codebase?

coderot measures complexity, duplication and hotspots on every push, splits them into AI-written and human-written code, and shows you which one ages worse, per folder and per month.

GitHub · C# · TypeScript/JavaScript · Privacy

// 00what you get

See if AI-written code is aging worse than yours.

For every folder, coderot charts how complexity changes month by month, split by who wrote the code. Here's one folder from an example report:

complexity per 1,000 lines · higher means harder to maintain

now AI-written +61% human-written +4%

12 months agonow

Illustrative data. Your first report covers the last 12 months of your git history.

// 01the problem

Your codebase is changing faster than anyone can review it.

LGTM

Reviewers go easy on AI code.

Your team ships more code than ever with Claude Code, Cursor and Copilot. A 2026 study of agent pull requests found they skip code reuse and add redundancy, yet reviewers react to them more neutrally or positively than to human PRs.

4×

Copy-paste is winning.

Across 211M changed lines, GitClear found 4× more code cloning, and copy/pasted code now outpaces refactored code for the first time.

ai == human

Your tools can't tell the difference.

Quality tools treat AI and human code the same. They show that the codebase got worse, not whether AI is the reason.

// 02how it works

One workflow file. A year of history on day one.

  1. Add one workflow file.

    No secrets, no repo access for us. Setup takes 5 minutes.

  2. coderot runs in your CI.

    On the first run it walks 12 months of git history, so you start with a trend, not a single number.

  3. See what got worse, where, and why.

    Which folders are rotting, which PR did it, and whether AI wrote it.

# .github/workflows/coderot.yml
on: push
permissions:
  contents: read   # check out the repo
  id-token: write  # upload via GitHub OIDC, no secrets
jobs:
  coderot:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: coderot-ai/action@v1

// 03privacy

Your code never leaves your CI.

your repo→your CInumbers + names →coderot

The analysis runs on your own runners. We receive numbers, file paths and function names, never source code, and coderot gets no read access to your repository. Run coderot analyze --out report.json to read exactly what would be sent:

// report.json, excerpt
{
  "commit": "4f2a9c1",
  "ai": { "agent": "claude-code", "lines": 38 },
  "path": "src/Orders/InvoiceCalculator.cs",
  "function": "CalculateTotal",
  "complexity": 14,
  "duplicates": ["src/Billing/Totals.cs:40-51"]
}

// 04attribution

We don't guess who wrote it.

Quick test, 3 rounds. Every method is real, from dotnet/runtime and VS Code. In each round, GitHub Copilot's coding agent wrote one of the two. Which one?

Round 1 of 3

A · GroupJoinIterator()

{
    using (IEnumerator<TOuter> e = outer.GetEnumerator())
    {
        if (e.MoveNext())
        {
            Lookup<TKey, TInner> lookup =
                Lookup<TKey, TInner>.CreateForJoin(
                    inner, innerKeySelector, comparer);
            do
            {
                TOuter item = e.Current;
                yield return resultSelector(
                    item, lookup[outerKeySelector(item)]);
            }
            while (e.MoveNext());
        }
    }
}
Human. dotnet/runtime as of 2020, before Copilot existed. Source

B · GroupJoinIterator()

{
    using IEnumerator<TOuter> e = outer.GetEnumerator();
    if (e.MoveNext())
    {
        Lookup<TKey, TInner> lookup =
            Lookup<TKey, TInner>.CreateForJoin(
                inner, innerKeySelector, comparer);
        do
        {
            TOuter item = e.Current;
            yield return new GroupJoinGrouping<TOuter, TInner>(
                item, lookup[outerKeySelector(item)]);
        }
        while (e.MoveNext());
    }
}
AI. GitHub Copilot coding agent, April 2026, recorded as Co-authored-by: copilot-swe-agent[bot]. Commit

Spotted the newer using syntax? That dates the code, but it doesn't name the author: the agent mirrored the existing method next to it, line for line.

Round 2 of 3

A · IsWhiteSpaceByte()

{
    // Check for ASCII whitespace characters: space (0x20),
    // tab (0x09), newline (0x0A), carriage return (0x0D),
    // form feed (0x0C), and vertical tab (0x0B)
    // For non-ASCII bytes, convert to char and check with
    // char.IsWhiteSpace
    return b <= 127 ? char.IsWhiteSpace((char)b) : false;
}
AI. GitHub Copilot coding agent, March 2026, recorded as Co-authored-by: copilot-swe-agent[bot]. Commit

B · SkipWhiteSpace()

{
    // Create local copy to avoid bounds checks.
    ReadOnlySpan<byte> localBuffer = _buffer;
    for (; _consumed < localBuffer.Length; _consumed++)
    {
        byte val = localBuffer[_consumed];

        // JSON RFC 8259 section 2 says only these 4
        // characters count, not all of the Unicode
        // defintions of whitespace.
        if (val != JsonConstants.Space &&
            val != JsonConstants.CarriageReturn &&
            val != JsonConstants.LineFeed &&
            val != JsonConstants.Tab)
        {
            break;
        }

        if (val == JsonConstants.LineFeed)
        {
            _lineNumber++;
            _bytePositionInLine = 0;
        }
        else
        {
            _bytePositionInLine++;
        }
    }
}
Human. System.Text.Json in dotnet/runtime as of 2020, before Copilot existed. Source

A's comment says non-ASCII bytes get checked with char.IsWhiteSpace, but the code returns false for them. A tell? People leave wrong comments too, and this one passed review.

Round 3 of 3

A · normalizeGitRepoPath()

{
    const trimmed =
        path.replace(/\/+/g, '/').replace(/\/+$/g, '');
    if (!trimmed.toLowerCase().endsWith('.git')) {
        return undefined;
    }

    const withLeadingSlash =
        trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
    const pathWithoutGit = withLeadingSlash.slice(1, -4);
    if (!pathWithoutGit || !pathWithoutGit.includes('/')) {
        return undefined;
    }

    return withLeadingSlash;
}
Unknown. Committed by a VS Code maintainer, February 2026, with no AI trailer. Commit

B · normalizeGitRepoPath()

{
    const gitSuffix = '.git';
    const trimmed =
        path.replace(/\/+/g, '/').replace(/\/+$/g, '');

    const withLeadingSlash =
        trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
    // Strip .git suffix (if present) only for the purposes
    // of validating path depth.
    const pathWithoutGit =
        withLeadingSlash.toLowerCase().endsWith(gitSuffix)
            ? withLeadingSlash.slice(1,
                withLeadingSlash.length - gitSuffix.length)
            : withLeadingSlash.slice(1);
    if (!pathWithoutGit || !pathWithoutGit.includes('/')) {
        return undefined;
    }

    return withLeadingSlash;
}
AI. GitHub Copilot coding agent, March 2026, recorded as Co-authored-by: copilot-swe-agent[bot]. Commit

The named constant and the explaining comment felt like AI? Fair guess. But A isn't proven human either: its commit message says "(Commit message generated by Copilot)", and nothing records who wrote the code. coderot would count A as unknown.

Method bodies as committed, with long lines wrapped.

So coderot doesn't guess. AI attribution comes only from what your tools record. Everything else is marked "unknown", and every AI number shows how much of the code we could attribute.

Co-authored-by: ClaudeAIgit notes: Git AI / Agent TraceAIauthor: copilot-swe-agent[bot]AIno signalunknown

// 05live demo

Which open-source repos are rotting fastest?soon

We're running coderot on well-known C# and TypeScript repos, with the same CLI you'll run. The waitlist gets the first report.

$ npx coderot analyze .
✓ 12 months, 1,284 commits
✓ attribution coverage 88%

complexity change      AI    human
  src/Orders         +61%      +4%
  src/Billing        +23%      -2%
  web/ui              +3%      +5%

$ 

Example output.

// 06waitlist

Be the first to see your own numbers.

Get the first open-source report, then an invite when the private beta opens.

Double opt-in. Unsubscribe anytime. How we handle your email.