agent-roast 0.1.4 → 0.2.0

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/dist/cli.js CHANGED
@@ -1,31 +1,87 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ GitError,
4
+ GitResolutionError,
5
+ InvalidDetectorError,
6
+ auditPullRequest,
7
+ formatGithubStepSummary,
3
8
  roastRepository
4
- } from "./chunk-EPPMO4JE.js";
9
+ } from "./chunk-FHRPVXCI.js";
5
10
 
6
11
  // src/cli.ts
7
12
  import { Command } from "commander";
8
13
  import { createRequire } from "module";
14
+ import * as fs from "fs";
9
15
  var require2 = createRequire(import.meta.url);
10
16
  var pkg = require2("../package.json");
11
17
  var program = new Command();
12
- program.name("agent-roast").description("Audit your git history for AI coding agent infractions and panic loops").version(pkg.version || "0.1.2").argument("[path]", "Path to git repository (default: current directory)", ".").option("-a, --all", "Audit ALL commits regardless of AI attribution tags", false).option("-s, --since <time>", "Time window for git analysis", "90 days ago").option("-v, --verbose", "Show commit SHAs and line snippets for all infractions", false).option("--json", "Output results as raw JSON", false).action(async (repoPath, options) => {
18
+ program.name("agent-roast").description("Audit your git history or PR diffs for AI coding shortcuts and panic loops").version(pkg.version || "0.2.0").argument("[path]", "Path to git repository (default: current directory)", ".").option("-a, --all", "Audit ALL commits regardless of AI attribution tags", false).option("-s, --since <time>", "Time window for git analysis", "90 days ago").option("-v, --verbose", "Show commit SHAs and line snippets for all infractions", false).option("--json", "Output results as raw JSON", false).option("--base <ref>", "Base git ref/branch for PR mode audit (e.g. origin/main)").option("--head <ref>", "Head git ref/SHA for PR mode audit (default: HEAD)", "HEAD").option("--fail-on <rules>", "Comma-separated detectors that trigger CI failure", "test-skip").option("--format <format>", "Output format: terminal, json, github", "terminal").option("--authors", "Display author names and emails in report", false).action(async (repoPath, options) => {
13
19
  try {
14
- const result = await roastRepository({
15
- cwd: repoPath,
16
- since: options.since,
17
- all: options.all,
18
- verbose: options.verbose,
19
- json: options.json
20
- });
21
- if (options.json) {
22
- console.log(JSON.stringify(result.summary, null, 2));
20
+ if (process.env.GITHUB_EVENT_NAME === "pull_request_target") {
21
+ console.warn(
22
+ "Warning: pull_request_target runs with elevated repository privileges. agent-roast recommends running on 'pull_request'."
23
+ );
24
+ }
25
+ if (options.base) {
26
+ const failOnRules = options.failOn ? options.failOn.split(",").map((s) => s.trim()).filter(Boolean) : ["test-skip"];
27
+ const chosenFormat = options.json ? "json" : options.format || "terminal";
28
+ const prResult = await auditPullRequest({
29
+ cwd: repoPath,
30
+ base: options.base,
31
+ head: options.head || "HEAD",
32
+ failOn: failOnRules,
33
+ format: chosenFormat,
34
+ verbose: options.verbose,
35
+ authors: options.authors
36
+ });
37
+ if (process.env.GITHUB_STEP_SUMMARY) {
38
+ try {
39
+ fs.appendFileSync(
40
+ process.env.GITHUB_STEP_SUMMARY,
41
+ formatGithubStepSummary(prResult) + "\n"
42
+ );
43
+ } catch (err) {
44
+ console.error(`Warning: Failed to write to GITHUB_STEP_SUMMARY: ${err.message}`);
45
+ }
46
+ }
47
+ if (process.env.GITHUB_OUTPUT) {
48
+ try {
49
+ fs.appendFileSync(
50
+ process.env.GITHUB_OUTPUT,
51
+ `passed=${prResult.passed}
52
+ infractions-count=${prResult.infractions.length}
53
+ has-supported-changes=${prResult.hasSupportedChanges}
54
+ `
55
+ );
56
+ } catch (err) {
57
+ console.error(`Warning: Failed to write to GITHUB_OUTPUT: ${err.message}`);
58
+ }
59
+ }
60
+ console.log(prResult.output);
61
+ process.exitCode = prResult.exitCode;
23
62
  } else {
24
- console.log(result.output);
63
+ const result = await roastRepository({
64
+ cwd: repoPath,
65
+ since: options.since,
66
+ all: options.all,
67
+ verbose: options.verbose,
68
+ json: options.json
69
+ });
70
+ if (options.json) {
71
+ console.log(JSON.stringify(result.summary, null, 2));
72
+ } else {
73
+ console.log(result.output);
74
+ }
75
+ process.exitCode = 0;
25
76
  }
26
77
  } catch (err) {
27
- console.error(`Error auditing repository: ${err.message}`);
28
- process.exit(1);
78
+ if (err instanceof GitResolutionError || err instanceof InvalidDetectorError || err instanceof GitError || err.name === "GitError") {
79
+ console.error(`agent-roast error: ${err.message}`);
80
+ process.exitCode = 2;
81
+ } else {
82
+ console.error(`Error auditing repository: ${err.message}`);
83
+ process.exitCode = 1;
84
+ }
29
85
  }
30
86
  });
31
87
  program.parse(process.argv);
package/dist/index.d.ts CHANGED
@@ -9,15 +9,19 @@ interface CommitInfo {
9
9
  isAiAttributed: boolean;
10
10
  aiSignatures: string[];
11
11
  }
12
+ type DetectorType = "test-skip" | "type-escape" | "swallowed-error" | "panic-loop";
13
+ declare const VALID_DETECTORS: readonly DetectorType[];
12
14
  interface Infraction {
13
- type: "test-skip" | "type-escape" | "swallowed-error" | "panic-loop";
15
+ type: DetectorType;
14
16
  file?: string;
17
+ lineNumber?: number;
15
18
  lineSnippet?: string;
16
19
  commitHash: string;
17
20
  commitDate: string;
18
21
  reason: string;
19
22
  }
20
23
  interface MetricSummary {
24
+ schemaVersion: string;
21
25
  totalLinesAdded: number;
22
26
  aiLinesAdded: number;
23
27
  totalCommits: number;
@@ -27,10 +31,12 @@ interface MetricSummary {
27
31
  swallowedErrors: Infraction[];
28
32
  panicLoops: Infraction[];
29
33
  rawScore: number;
30
- score: number;
34
+ score: number | null;
31
35
  isSufficientData: boolean;
32
36
  archetype: string;
33
37
  archetypeDescription: string;
38
+ skippedLongLines?: number;
39
+ skippedUnknownFiles?: number;
34
40
  }
35
41
  interface RoastOptions {
36
42
  cwd?: string;
@@ -38,6 +44,33 @@ interface RoastOptions {
38
44
  all?: boolean;
39
45
  verbose?: boolean;
40
46
  json?: boolean;
47
+ authors?: boolean;
48
+ }
49
+ interface PrAuditOptions {
50
+ cwd?: string;
51
+ base: string;
52
+ head: string;
53
+ failOn?: string[];
54
+ format?: "terminal" | "json" | "github";
55
+ verbose?: boolean;
56
+ authors?: boolean;
57
+ }
58
+ interface PrAuditResult {
59
+ schemaVersion: string;
60
+ baseSha: string;
61
+ headSha: string;
62
+ mergeBaseSha: string;
63
+ hasSupportedChanges: boolean;
64
+ filesInspected: number;
65
+ linesAdded: number;
66
+ defectDensityPer100Lines: number;
67
+ infractions: Infraction[];
68
+ failedRules: string[];
69
+ passed: boolean;
70
+ skippedLongLines: number;
71
+ skippedUnknownFiles: number;
72
+ output: string;
73
+ exitCode: number;
41
74
  }
42
75
 
43
76
  interface AiAttributionResult {
@@ -53,14 +86,69 @@ interface ParsedDiffLine {
53
86
  interface ParsedFileDiff {
54
87
  path: string;
55
88
  addedLines: ParsedDiffLine[];
89
+ deleted?: boolean;
56
90
  }
57
91
  interface ParsedCommit extends CommitInfo {
58
92
  files: ParsedFileDiff[];
59
93
  totalAddedCount: number;
60
94
  }
61
- declare function parseGitLogStream(stream: Readable, onCommit: (commit: ParsedCommit) => void): Promise<void>;
95
+ interface ParseDiffResult {
96
+ files: ParsedFileDiff[];
97
+ totalAdded: number;
98
+ skippedLongLines: number;
99
+ skippedUnknownFiles: number;
100
+ }
101
+ declare const RECORD_SEPARATOR = "\u001E__ROAST_REC__\u001E";
102
+ declare const FIELD_SEPARATOR = "\u001F__ROAST_FIELD__\u001F";
103
+ declare class GitError extends Error {
104
+ stderr: string;
105
+ code: number | null;
106
+ constructor(stderr: string, code: number | null);
107
+ }
108
+ declare const HERMETIC_GIT_FLAGS: readonly ["-c", "core.quotepath=false", "-c", "diff.noprefix=false", "-c", "diff.mnemonicPrefix=false", "--no-pager"];
109
+ declare function getHermeticGitEnv(extraEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
110
+ declare function parseGitLogStream(stream: Readable, onCommit: (commit: ParsedCommit, stats: {
111
+ skippedLongLines: number;
112
+ skippedUnknownFiles: number;
113
+ }) => void): Promise<{
114
+ totalSkippedLongLines: number;
115
+ totalSkippedUnknownFiles: number;
116
+ }>;
117
+ /**
118
+ * Unquotes git C-style escaped path (e.g. "b/foo\"bar.ts" or "b/\321\202\320\265\321\201\321\202.ts")
119
+ * decoding octal escape sequences and full Unicode code points safely without surrogate splitting.
120
+ */
121
+ declare function unquoteGitPath(pathStr: string): string;
122
+ /**
123
+ * Parses raw git diff text into structured files and added lines.
124
+ * Deterministic, robust against desynchronization and minified files.
125
+ */
126
+ declare function parseDiff(rawDiff: string): ParseDiffResult;
62
127
  declare function streamGitLog(cwd?: string, since?: string): Readable;
128
+ declare function streamGitDiff(cwd: string, baseSha: string, headSha: string): Readable;
129
+ /**
130
+ * Validates and resolves a git ref to a full commit SHA safely using --end-of-options.
131
+ * Exits with code 2 on failure.
132
+ */
133
+ declare function resolveCommitSha(cwd: string, ref: string, roleName?: string): string;
134
+ /**
135
+ * Finds the merge-base between two commit SHAs safely using --end-of-options.
136
+ */
137
+ declare function findMergeBase(cwd: string, baseSha: string, headSha: string): string;
138
+ declare class GitResolutionError extends Error {
139
+ constructor(message: string);
140
+ }
63
141
 
142
+ declare function isCodeFile(filePath: string): boolean;
143
+ /**
144
+ * Audits a collection of file diffs (e.g. for PR mode) against detector rules.
145
+ */
146
+ declare function auditFileDiffs(files: ParsedFileDiff[], commitHash?: string, commitDate?: string): {
147
+ infractions: Infraction[];
148
+ hasSupportedChanges: boolean;
149
+ filesInspected: number;
150
+ linesAdded: number;
151
+ };
64
152
  /**
65
153
  * Strips quoted string literals and regex literals from a line so that
66
154
  * test fixtures and string constants (e.g. { line: "it.skip" }) are not flagged.
@@ -75,11 +163,18 @@ declare class MetricsCollector {
75
163
  private typeEscapes;
76
164
  private swallowedErrors;
77
165
  private panicLoops;
78
- private lastFixCommit;
166
+ private skippedLongLines;
167
+ private skippedUnknownFiles;
168
+ private activePanicChain;
79
169
  private includeAll;
80
170
  constructor(options?: {
81
171
  all?: boolean;
82
172
  });
173
+ recordSkipped(stats: {
174
+ skippedLongLines?: number;
175
+ skippedUnknownFiles?: number;
176
+ }): void;
177
+ private flushPanicChain;
83
178
  processCommit(commit: ParsedCommit): void;
84
179
  getSummary(): MetricSummary;
85
180
  }
@@ -89,10 +184,24 @@ declare function formatTerminalCard(summary: MetricSummary, options: {
89
184
  repoName: string;
90
185
  verbose?: boolean;
91
186
  }): string;
187
+ declare function formatPrTerminalCard(result: PrAuditResult): string;
188
+ /**
189
+ * Generates GitHub Actions workflow commands (::error and ::warning)
190
+ * respecting GitHub's limits (max 10 annotations per level per step).
191
+ */
192
+ declare function formatGithubAnnotations(result: PrAuditResult): string;
193
+ /**
194
+ * Generates GitHub Step Summary Markdown
195
+ */
196
+ declare function formatGithubStepSummary(result: PrAuditResult): string;
92
197
 
198
+ declare class InvalidDetectorError extends Error {
199
+ constructor(message: string);
200
+ }
93
201
  declare function roastRepository(options?: RoastOptions): Promise<{
94
202
  summary: MetricSummary;
95
203
  output: string;
96
204
  }>;
205
+ declare function auditPullRequest(options: PrAuditOptions): Promise<PrAuditResult>;
97
206
 
98
- export { type AiAttributionResult, type CommitInfo, type Infraction, type MetricSummary, MetricsCollector, type ParsedCommit, type ParsedDiffLine, type ParsedFileDiff, type RoastOptions, detectAiAttribution, formatTerminalCard, generateShareUrl, parseGitLogStream, roastRepository, streamGitLog, stripStringAndRegexLiterals };
207
+ export { type AiAttributionResult, type CommitInfo, type DetectorType, FIELD_SEPARATOR, GitError, GitResolutionError, HERMETIC_GIT_FLAGS, type Infraction, InvalidDetectorError, type MetricSummary, MetricsCollector, type ParseDiffResult, type ParsedCommit, type ParsedDiffLine, type ParsedFileDiff, type PrAuditOptions, type PrAuditResult, RECORD_SEPARATOR, type RoastOptions, VALID_DETECTORS, auditFileDiffs, auditPullRequest, detectAiAttribution, findMergeBase, formatGithubAnnotations, formatGithubStepSummary, formatPrTerminalCard, formatTerminalCard, generateShareUrl, getHermeticGitEnv, isCodeFile, parseDiff, parseGitLogStream, resolveCommitSha, roastRepository, streamGitDiff, streamGitLog, stripStringAndRegexLiterals, unquoteGitPath };
package/dist/index.js CHANGED
@@ -1,21 +1,59 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ FIELD_SEPARATOR,
4
+ GitError,
5
+ GitResolutionError,
6
+ HERMETIC_GIT_FLAGS,
7
+ InvalidDetectorError,
3
8
  MetricsCollector,
9
+ RECORD_SEPARATOR,
10
+ VALID_DETECTORS,
11
+ auditFileDiffs,
12
+ auditPullRequest,
4
13
  detectAiAttribution,
14
+ findMergeBase,
15
+ formatGithubAnnotations,
16
+ formatGithubStepSummary,
17
+ formatPrTerminalCard,
5
18
  formatTerminalCard,
6
19
  generateShareUrl,
20
+ getHermeticGitEnv,
21
+ isCodeFile,
22
+ parseDiff,
7
23
  parseGitLogStream,
24
+ resolveCommitSha,
8
25
  roastRepository,
26
+ streamGitDiff,
9
27
  streamGitLog,
10
- stripStringAndRegexLiterals
11
- } from "./chunk-EPPMO4JE.js";
28
+ stripStringAndRegexLiterals,
29
+ unquoteGitPath
30
+ } from "./chunk-FHRPVXCI.js";
12
31
  export {
32
+ FIELD_SEPARATOR,
33
+ GitError,
34
+ GitResolutionError,
35
+ HERMETIC_GIT_FLAGS,
36
+ InvalidDetectorError,
13
37
  MetricsCollector,
38
+ RECORD_SEPARATOR,
39
+ VALID_DETECTORS,
40
+ auditFileDiffs,
41
+ auditPullRequest,
14
42
  detectAiAttribution,
43
+ findMergeBase,
44
+ formatGithubAnnotations,
45
+ formatGithubStepSummary,
46
+ formatPrTerminalCard,
15
47
  formatTerminalCard,
16
48
  generateShareUrl,
49
+ getHermeticGitEnv,
50
+ isCodeFile,
51
+ parseDiff,
17
52
  parseGitLogStream,
53
+ resolveCommitSha,
18
54
  roastRepository,
55
+ streamGitDiff,
19
56
  streamGitLog,
20
- stripStringAndRegexLiterals
57
+ stripStringAndRegexLiterals,
58
+ unquoteGitPath
21
59
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-roast",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
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",