coderot
AI-writtenhuman-writtenrotting
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
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:
now AI-written +61% human-written +4%
Illustrative data. Your first report covers the last 12 months of your git history.
// 01the problem
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.
Across 211M changed lines, GitClear found 4× more code cloning, and copy/pasted code now outpaces refactored code for the first time.
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
No secrets, no repo access for us. Setup takes 5 minutes.
On the first run it walks 12 months of git history, so you start with a trend, not a single number.
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
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
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());
}
}
}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());
}
}Co-authored-by: copilot-swe-agent[bot]. CommitSpotted 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;
}Co-authored-by: copilot-swe-agent[bot]. CommitB · 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++;
}
}
}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;
}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;
}Co-authored-by: copilot-swe-agent[bot]. CommitThe 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
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
Get the first open-source report, then an invite when the private beta opens.
Double opt-in. Unsubscribe anytime. How we handle your email.