agent-roast 0.1.0 โ†’ 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,79 +1,68 @@
1
- # agent-roast ๐Ÿค–๐Ÿ”ฅ
1
+ <div align="center">
2
2
 
3
- > **Zero-telemetry CLI scorecard auditing your git history for AI coding agent infractions, shortcuts, and panic loops.**
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/logo-dark.svg">
5
+ <img src="docs/assets/logo.svg" alt="agent-roast" width="340">
6
+ </picture>
4
7
 
5
- [![npm version](https://img.shields.io/npm/v/agent-roast?color=cb3837&label=npm)](https://www.npmjs.com/package/agent-roast)
6
- [![telemetry: zero](https://img.shields.io/badge/telemetry-zero%20(offline)-1f9d55)](#privacy--security)
8
+ ### Audit your git history for AI coding agent infractions and panic loops
9
+
10
+ [![npm version](https://img.shields.io/npm/v/agent-roast?logo=npm&logoColor=white&color=cb3837)](https://www.npmjs.com/package/agent-roast)
11
+ [![telemetry: zero (offline)](https://img.shields.io/badge/telemetry-zero%20(offline)-1f9d55)](#privacy)
7
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
13
 
9
- Did your AI coding agent (Cursor, Claude Code, Aider) write clean software, or did it silently delete tests and slap `as any` everywhere to pretend everything compiles?
14
+ </div>
10
15
 
11
- Run in any git repository (100% local, zero tokens, zero telemetry):
16
+ `agent-roast` scans recorded git diffs for shortcuts taken by AI coding assistants (Claude Code, Cursor, Aider). It checks for skipped tests, compiler type escapes (`as any`), swallowed errors, and panic revert loops, then outputs a discipline score and line proofs. 100% offline, zero API keys, zero telemetry.
12
17
 
13
18
  ```bash
14
- npx agent-roast
19
+ npx agent-roast # scan AI-attributed commits (last 90 days)
20
+ npx agent-roast --all # scan entire repository history
21
+ npx agent-roast --verbose # show commit SHAs and line proofs
15
22
  ```
16
23
 
17
- ---
18
-
19
- ### What it looks like:
24
+ What a report looks like (a real repository audit, trimmed):
20
25
 
21
26
  ```text
22
- โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
23
- โ•‘ ๐Ÿค– AGENT ROAST DOSSIER โ•‘
24
- โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
25
-
26
- Repository: my-app
27
- Commits: 18 AI-attributed / 42 total
28
- Code Added: 4,210 lines (AI) / 12,480 lines (total)
29
-
30
- AGENT ARCHETYPE: The Any Architect
31
- Leans on compiler bypasses (`as any`, `@ts-ignore`) rather than strict type modeling.
32
-
33
- DISCIPLINE SCORE: 68 / 100
34
- (100 = spotless discipline, 0 = complete shortcut addiction)
35
-
36
- ๐Ÿ“Š INFRACTIONS BREAKDOWN:
37
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
38
- โœ– Skipped tests: 4 (it.skip, describe.skip, xit)
39
- โ–ฒ Type escapes: 16 (as any, @ts-ignore, eslint-disable)
40
- โœ– Swallowed errors: 3 (empty catch {}, except: pass)
41
- โ†ป Panic fix loops: 2 (quick fixes < 15m apart)
42
-
43
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
44
- ๐Ÿ“ข Share your score on X: https://twitter.com/intent/tweet?text=...
45
- Tip: Run with --verbose to see exact commit SHAs and lines
46
- ```
27
+ agent-roast โ€” git discipline audit (304 AI commits in Soup)
47
28
 
48
- ---
29
+ SCORE: 55 / 100 ยท The Panic Looper
30
+ Prone to rapid-fire fix and revert thrashing cycles when wrestling stubborn bugs.
49
31
 
50
- ### What it detects (only added lines, zero false alarms from file renames):
32
+ INFRACTIONS DETECTED (42)
51
33
 
52
- 1. **Skipped Tests:** `it.skip`, `describe.skip`, `xit`, `test.todo` added when tests fail.
53
- 2. **Type Escapes:** `as any`, `// @ts-ignore`, `// @ts-expect-error`, `# type: ignore`, `eslint-disable`.
54
- 3. **Swallowed Errors:** Empty `catch {}` or `except: pass` blocks hiding production bugs.
55
- 4. **Panic Loops:** Consecutive quickfix/revert commits within 15 minutes by the agent.
34
+ โ— panic-loop (15) โ€” consecutive quick fixes < 15m apart
35
+ latest: 3df29a1 -> a910f21 (3m apart)
56
36
 
57
- ---
37
+ โ— type-escape (19) โ€” compiler type bypasses in added lines
38
+ e.g. src/client.ts:42 โ€” response.body as any
58
39
 
59
- ### Options
40
+ โ— test-skip (6) โ€” disabled or skipped test suites
41
+ e.g. tests/test_outbound.py:18 โ€” pytest.skip(...)
60
42
 
61
- ```bash
62
- npx agent-roast # Audit AI-attributed commits (Co-Authored-By: Claude, Cursor, Aider, etc.)
63
- npx agent-roast --all # Audit ALL commits across the repository
64
- npx agent-roast --verbose # Print exact commit SHAs and code lines as proof
65
- npx agent-roast --since 30d # Audit last 30 days of git history
66
- npx agent-roast --json # Output raw JSON metrics for CI pipelines
43
+ โ— swallowed-error (2) โ€” empty catch blocks or except: pass
67
44
  ```
68
45
 
46
+ <img src="docs/assets/case-study.gif" alt="agent-roast git audit session" width="100%">
47
+
48
+ ---
49
+
50
+ ### What it checks
51
+
52
+ Only **added lines** (`+`) in source files are scanned โ€” file moves and refactoring never trigger false alarms.
53
+
54
+ - **Skipped tests:** `it.skip`, `describe.skip`, `xit`, `pytest.skip`, `test.todo` added when tests fail.
55
+ - **Type escapes:** `as any`, `// @ts-ignore`, `// @ts-expect-error`, `# type: ignore`, `eslint-disable`.
56
+ - **Swallowed errors:** Empty `catch {}` or `except: pass` blocks hiding production errors.
57
+ - **Panic fix loops:** Rapid-fire fix and revert commits by the same author within 15 minutes.
58
+
69
59
  ---
70
60
 
71
- ### Privacy & Security
61
+ ### Privacy
72
62
 
73
- `agent-roast` is strictly **local and deterministic**:
74
- - Runs streaming `git log` on your machine in milliseconds.
75
- - **Zero network calls**, zero LLM APIs, zero tracking or analytics.
76
- - Your source code never leaves your terminal.
63
+ - **100% Local:** Runs streaming `git log` on your machine in <200ms.
64
+ - **Zero Telemetry:** No cloud dependencies, no LLM API calls, no network traffic.
65
+ - **Hermetic:** Source code never leaves your computer.
77
66
 
78
67
  ---
79
68
 
@@ -425,71 +425,81 @@ var MetricsCollector = class {
425
425
  // src/renderer.ts
426
426
  import pc from "picocolors";
427
427
  function generateShareUrl(summary) {
428
- const text = `My AI coding agent scored ${summary.score}/100 on @agent_roast! \u{1F916}\u{1F525}
429
-
430
- Archetype: "${summary.archetype}"
428
+ const text = `My AI coding agent scored ${summary.score}/100 on agent-roast.
431
429
 
430
+ Archetype: ${summary.archetype}
432
431
  Infractions:
433
432
  \u2022 ${summary.testSkips.length} skipped tests
434
433
  \u2022 ${summary.typeEscapes.length} type escapes
435
434
  \u2022 ${summary.panicLoops.length} panic revert loops
436
435
 
437
- Audit your repo with: npx agent-roast`;
436
+ Audit your repo: npx agent-roast`;
438
437
  return `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}`;
439
438
  }
440
439
  function formatTerminalCard(summary, options) {
441
440
  const lines = [];
442
- const boxW = 60;
443
- const hLine = "\u2550".repeat(boxW - 2);
444
- lines.push(pc.cyan(`\u2554${hLine}\u2557`));
445
- lines.push(
446
- pc.cyan("\u2551") + pc.bold(pc.white(" \u{1F916} AGENT ROAST DOSSIER ")) + pc.cyan("\u2551")
447
- );
448
- lines.push(pc.cyan(`\u255A${hLine}\u255D`));
449
441
  lines.push("");
450
- lines.push(` ${pc.dim("Repository:")} ${pc.bold(pc.white(options.repoName))}`);
451
442
  lines.push(
452
- ` ${pc.dim("Commits:")} ${pc.yellow(summary.aiCommits)} AI-attributed / ${summary.totalCommits} total`
443
+ `${pc.bold(pc.cyan("agent-roast"))} ${pc.dim("\u2014 git discipline audit")} ${pc.white(options.repoName)}`
453
444
  );
454
445
  lines.push(
455
- ` ${pc.dim("Code Added:")} ${pc.green(summary.aiLinesAdded)} lines (AI) / ${summary.totalLinesAdded} lines (total)`
446
+ ` ${pc.dim("commits:")} ${pc.yellow(summary.aiCommits)} AI / ${summary.totalCommits} total ${pc.dim("lines added:")} ${pc.green(summary.aiLinesAdded)} AI / ${summary.totalLinesAdded} total`
456
447
  );
457
448
  lines.push("");
458
449
  if (!summary.isSufficientData) {
459
- lines.push(pc.yellow(` \u26A0 INSUFFICIENT DATA TO ROAST (< 200 lines added)`));
460
- lines.push(pc.dim(" Commit more AI code and run `npx agent-roast` again."));
450
+ lines.push(pc.yellow(` insufficient data (< 200 lines added by AI)`));
451
+ lines.push(pc.dim(" commit more code and run `npx agent-roast` again."));
461
452
  lines.push("");
462
453
  return lines.join("\n");
463
454
  }
464
455
  let scoreColor = pc.green;
465
456
  if (summary.score < 60) scoreColor = pc.red;
466
457
  else if (summary.score < 80) scoreColor = pc.yellow;
467
- lines.push(` ${pc.bold("AGENT ARCHETYPE:")} ${pc.magenta(pc.bold(summary.archetype))}`);
468
- lines.push(` ${pc.italic(summary.archetypeDescription)}`);
469
- lines.push("");
470
- lines.push(` ${pc.bold("DISCIPLINE SCORE:")} ${scoreColor(pc.bold(`${summary.score} / 100`))}`);
471
458
  lines.push(
472
- pc.dim(" (100 = spotless discipline, 0 = complete shortcut addiction)")
459
+ ` ${pc.bold("SCORE:")} ${scoreColor(pc.bold(`${summary.score} / 100`))} ${pc.dim("\xB7")} ${pc.magenta(pc.bold(summary.archetype))}`
473
460
  );
461
+ lines.push(` ${pc.dim(summary.archetypeDescription)}`);
474
462
  lines.push("");
475
- lines.push(pc.bold(" \u{1F4CA} INFRACTIONS BREAKDOWN:"));
476
- lines.push(pc.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
477
- const skipIcon = summary.testSkips.length > 0 ? pc.red("\u2716") : pc.green("\u2714");
478
- lines.push(
479
- ` ${skipIcon} ${pc.bold("Skipped tests:")} ${summary.testSkips.length} ${pc.dim("(it.skip, describe.skip, xit)")}`
480
- );
481
- const escapeIcon = summary.typeEscapes.length > 0 ? pc.yellow("\u25B2") : pc.green("\u2714");
482
- lines.push(
483
- ` ${escapeIcon} ${pc.bold("Type escapes:")} ${summary.typeEscapes.length} ${pc.dim("(as any, @ts-ignore, eslint-disable)")}`
484
- );
485
- const errorIcon = summary.swallowedErrors.length > 0 ? pc.red("\u2716") : pc.green("\u2714");
486
- lines.push(
487
- ` ${errorIcon} ${pc.bold("Swallowed errors:")} ${summary.swallowedErrors.length} ${pc.dim("(empty catch {}, except: pass)")}`
488
- );
489
- const panicIcon = summary.panicLoops.length > 0 ? pc.magenta("\u21BB") : pc.green("\u2714");
490
- lines.push(
491
- ` ${panicIcon} ${pc.bold("Panic fix loops:")} ${summary.panicLoops.length} ${pc.dim("(quick fixes < 15m apart)")}`
492
- );
463
+ const totalInfractions = summary.testSkips.length + summary.typeEscapes.length + summary.swallowedErrors.length + summary.panicLoops.length;
464
+ lines.push(pc.bold(` INFRACTIONS (${totalInfractions})`));
465
+ lines.push(pc.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
466
+ if (summary.panicLoops.length > 0) {
467
+ lines.push(
468
+ ` ${pc.magenta("\u25CF")} ${pc.bold("panic-loop")} (${summary.panicLoops.length}) ${pc.dim("consecutive quick fixes < 15m apart")}`
469
+ );
470
+ const sample = summary.panicLoops[0];
471
+ lines.push(` ${pc.dim(sample.reason)}`);
472
+ }
473
+ if (summary.typeEscapes.length > 0) {
474
+ lines.push(
475
+ ` ${pc.yellow("\u25CF")} ${pc.bold("type-escape")} (${summary.typeEscapes.length}) ${pc.dim("compiler bypasses (as any, @ts-ignore)")}`
476
+ );
477
+ const sample = summary.typeEscapes[0];
478
+ if (sample.file) {
479
+ lines.push(` ${pc.dim(`${sample.file}: ${sample.lineSnippet || ""}`)}`);
480
+ }
481
+ }
482
+ if (summary.testSkips.length > 0) {
483
+ lines.push(
484
+ ` ${pc.red("\u25CF")} ${pc.bold("test-skip")} (${summary.testSkips.length}) ${pc.dim("skipped or disabled tests")}`
485
+ );
486
+ const sample = summary.testSkips[0];
487
+ if (sample.file) {
488
+ lines.push(` ${pc.dim(`${sample.file}: ${sample.lineSnippet || ""}`)}`);
489
+ }
490
+ }
491
+ if (summary.swallowedErrors.length > 0) {
492
+ lines.push(
493
+ ` ${pc.red("\u25CF")} ${pc.bold("swallowed-error")} (${summary.swallowedErrors.length}) ${pc.dim("empty catch blocks or except: pass")}`
494
+ );
495
+ const sample = summary.swallowedErrors[0];
496
+ if (sample.file) {
497
+ lines.push(` ${pc.dim(`${sample.file}: ${sample.lineSnippet || ""}`)}`);
498
+ }
499
+ }
500
+ if (totalInfractions === 0) {
501
+ lines.push(` ${pc.green("\u25CF")} ${pc.dim("zero infractions detected across all inspected lines.")}`);
502
+ }
493
503
  lines.push("");
494
504
  if (options.verbose) {
495
505
  const allInfractions = [
@@ -499,27 +509,22 @@ function formatTerminalCard(summary, options) {
499
509
  ...summary.panicLoops
500
510
  ];
501
511
  if (allInfractions.length > 0) {
502
- lines.push(pc.bold(" \u{1F50D} VERBOSE PROOFS (First 10 items):"));
503
- lines.push(pc.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
512
+ lines.push(pc.bold(" EVIDENCE (First 10 occurrences):"));
513
+ lines.push(pc.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
504
514
  for (const inf of allInfractions.slice(0, 10)) {
505
- const fileLoc = inf.file ? `${inf.file}` : "git commit";
515
+ const fileLoc = inf.file ? `${inf.file}` : "commit history";
506
516
  lines.push(
507
- ` \u2022 ${pc.cyan(inf.commitHash.slice(0, 7))} [${inf.type}] ${pc.white(fileLoc)}`
517
+ ` \u25CF ${pc.cyan(inf.commitHash.slice(0, 7))} [${inf.type}] ${pc.white(fileLoc)}`
508
518
  );
509
519
  if (inf.lineSnippet) {
510
- lines.push(` ${pc.dim(inf.lineSnippet)}`);
520
+ lines.push(` ${pc.dim(`\u2514\u2500 ${inf.lineSnippet}`)}`);
511
521
  }
512
522
  }
513
523
  lines.push("");
514
524
  }
515
525
  }
516
- const shareUrl = generateShareUrl(summary);
517
- lines.push(pc.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
518
- lines.push(
519
- ` ${pc.cyan("\u{1F4E2} Share your score on X:")} ${pc.underline(pc.cyan(shareUrl))}`
520
- );
521
526
  lines.push(
522
- ` ${pc.dim("Tip: Run with --verbose to see exact commit SHAs and lines")}`
527
+ ` ${pc.dim("Share score:")} ${pc.cyan(generateShareUrl(summary))}`
523
528
  );
524
529
  lines.push("");
525
530
  return lines.join("\n");
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  roastRepository
4
- } from "./chunk-RFCXDN22.js";
4
+ } from "./chunk-EPPMO4JE.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  roastRepository,
9
9
  streamGitLog,
10
10
  stripStringAndRegexLiterals
11
- } from "./chunk-RFCXDN22.js";
11
+ } from "./chunk-EPPMO4JE.js";
12
12
  export {
13
13
  MetricsCollector,
14
14
  detectAiAttribution,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-roast",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Zero-telemetry CLI scorecard analyzing git history for AI coding agent infractions and panic loops",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,7 +44,9 @@
44
44
  "picocolors": "^1.1.0"
45
45
  },
46
46
  "devDependencies": {
47
+ "@napi-rs/canvas": "^1.0.10",
47
48
  "@types/node": "^20.0.0",
49
+ "gifenc": "^1.0.3",
48
50
  "tsup": "^8.0.0",
49
51
  "typescript": "^5.0.0",
50
52
  "vitest": "^3.0.0"