agent-roast 0.7.0 → 0.8.1
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 +100 -7
- package/dist/{chunk-YGHNTWHX.js → chunk-U7OCVA3D.js} +1061 -648
- package/dist/cli.js +56 -31
- package/dist/index.d.ts +47 -4
- package/dist/index.js +11 -1
- package/package.json +8 -3
- package/schema.json +138 -0
package/dist/cli.js
CHANGED
|
@@ -13,13 +13,16 @@ import {
|
|
|
13
13
|
generateShieldsJson,
|
|
14
14
|
generateShieldsMarkdown,
|
|
15
15
|
generateSvgBadge,
|
|
16
|
+
gitOutputPath,
|
|
16
17
|
initProject,
|
|
17
18
|
loadConfig,
|
|
19
|
+
readGit,
|
|
18
20
|
reviewChanges,
|
|
19
21
|
reviewStagedChanges,
|
|
20
22
|
roastRepository,
|
|
23
|
+
terminalText,
|
|
21
24
|
writeBaseline
|
|
22
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-U7OCVA3D.js";
|
|
23
26
|
|
|
24
27
|
// src/cli.ts
|
|
25
28
|
import { Command } from "commander";
|
|
@@ -28,6 +31,7 @@ import * as fs from "fs";
|
|
|
28
31
|
import * as path from "path";
|
|
29
32
|
var require2 = createRequire(import.meta.url);
|
|
30
33
|
var pkg = require2("../package.json");
|
|
34
|
+
var errorMessage = (error) => terminalText(error instanceof Error ? error.message : String(error));
|
|
31
35
|
async function runContextReview(repoPath, options, staged) {
|
|
32
36
|
const controller = new AbortController();
|
|
33
37
|
const abort = () => controller.abort();
|
|
@@ -37,30 +41,37 @@ async function runContextReview(repoPath, options, staged) {
|
|
|
37
41
|
const common = {
|
|
38
42
|
cwd: repoPath,
|
|
39
43
|
failOn: options.failOn !== void 0 ? options.failOn.split(",").map((rule) => rule.trim()).filter(Boolean) : void 0,
|
|
44
|
+
ignoreExceptions: options.ignoreExceptions,
|
|
40
45
|
format: options.json ? "json" : options.format,
|
|
41
46
|
signal: controller.signal
|
|
42
47
|
};
|
|
43
48
|
const result = staged ? await reviewStagedChanges(common) : await reviewChanges({ ...common, base: options.base, head: options.head });
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
console.log(result.output);
|
|
50
|
+
process.exitCode = result.exitCode;
|
|
51
|
+
for (const [filename, content] of [
|
|
52
|
+
[process.env.GITHUB_STEP_SUMMARY, formatReviewMarkdown(result) + "\n"],
|
|
53
|
+
[process.env.GITHUB_OUTPUT, `passed=${result.passed}
|
|
48
54
|
infractions-count=${result.infractions.length}
|
|
49
55
|
has-supported-changes=${result.filesInspected > 0}
|
|
50
56
|
complete=${result.complete}
|
|
51
57
|
existing-count=${result.unchangedCount}
|
|
52
58
|
removed-count=${result.removedFindings.length}
|
|
53
|
-
`
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
`]
|
|
60
|
+
]) {
|
|
61
|
+
if (!filename) continue;
|
|
62
|
+
try {
|
|
63
|
+
fs.appendFileSync(filename, content);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(`Warning: could not write an optional GitHub report artifact: ${errorMessage(error)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
57
68
|
} finally {
|
|
58
69
|
process.removeListener("SIGINT", abort);
|
|
59
70
|
process.removeListener("SIGTERM", abort);
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
73
|
var program = new Command();
|
|
63
|
-
program.name("agent-roast").description("
|
|
74
|
+
program.name("agent-roast").description("Check current code, review changes and audit Git history for coding shortcuts").version(pkg.version || "0.4.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("--staged", "Compare full HEAD/index source and gate new staged findings (including the first commit)", 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("--ignore-exceptions", "Report findings even when accepted by reviewed, unexpired exceptions", false).option("--fail-on <rules>", "Comma-separated detectors that trigger CI failure").option("--fail-under <score>", "Fail CI if agent score is below threshold (0-100)").option("--format <format>", "Output format: terminal, json, github, markdown (check/review also support sarif)", "terminal").option("--authors", "Display author names and emails in report", false).action(async (repoPath, options) => {
|
|
64
75
|
try {
|
|
65
76
|
if (process.env.GITHUB_EVENT_NAME === "pull_request_target") {
|
|
66
77
|
console.warn(
|
|
@@ -75,6 +86,7 @@ program.name("agent-roast").description("Audit your git history or PR diffs for
|
|
|
75
86
|
await runContextReview(repoPath, options, true);
|
|
76
87
|
return;
|
|
77
88
|
}
|
|
89
|
+
if (options.ignoreExceptions) throw new ConfigError("--ignore-exceptions applies to check, review and --staged context checks.");
|
|
78
90
|
if (!["terminal", "json", "github", "markdown"].includes(options.format)) {
|
|
79
91
|
throw new ConfigError("--format must be terminal, json, github, or markdown.");
|
|
80
92
|
}
|
|
@@ -87,7 +99,8 @@ program.name("agent-roast").description("Audit your git history or PR diffs for
|
|
|
87
99
|
if (!options.base && !options.staged && !options.json && !["terminal", "json"].includes(options.format)) {
|
|
88
100
|
throw new ConfigError("Repository audits support terminal or json format; use --base or --staged for github/markdown.");
|
|
89
101
|
}
|
|
90
|
-
const
|
|
102
|
+
const configRoot = gitOutputPath((await readGit(path.resolve(repoPath), ["rev-parse", "--show-toplevel"])).toString("utf8"));
|
|
103
|
+
const config = loadConfig(configRoot);
|
|
91
104
|
if (options.base || options.staged) {
|
|
92
105
|
const failOnRules = options.failOn !== void 0 ? options.failOn.split(",").map((s) => s.trim()).filter(Boolean) : config.failOn;
|
|
93
106
|
const chosenFormat = options.json ? "json" : options.format || "terminal";
|
|
@@ -110,7 +123,7 @@ program.name("agent-roast").description("Audit your git history or PR diffs for
|
|
|
110
123
|
formatGithubStepSummary(prResult) + "\n"
|
|
111
124
|
);
|
|
112
125
|
} catch (err) {
|
|
113
|
-
console.error(`Warning: Failed to write to GITHUB_STEP_SUMMARY: ${err
|
|
126
|
+
console.error(`Warning: Failed to write to GITHUB_STEP_SUMMARY: ${errorMessage(err)}`);
|
|
114
127
|
}
|
|
115
128
|
}
|
|
116
129
|
if (process.env.GITHUB_OUTPUT) {
|
|
@@ -123,7 +136,7 @@ has-supported-changes=${prResult.hasSupportedChanges}
|
|
|
123
136
|
`
|
|
124
137
|
);
|
|
125
138
|
} catch (err) {
|
|
126
|
-
console.error(`Warning: Failed to write to GITHUB_OUTPUT: ${err
|
|
139
|
+
console.error(`Warning: Failed to write to GITHUB_OUTPUT: ${errorMessage(err)}`);
|
|
127
140
|
}
|
|
128
141
|
}
|
|
129
142
|
console.log(prResult.output);
|
|
@@ -145,7 +158,9 @@ has-supported-changes=${prResult.hasSupportedChanges}
|
|
|
145
158
|
console.log(result.output);
|
|
146
159
|
}
|
|
147
160
|
const scoreThreshold = options.failUnder !== void 0 ? Number(options.failUnder) : config.scoreThreshold;
|
|
148
|
-
if (
|
|
161
|
+
if (result.summary.skippedLongLines || result.summary.skippedUnknownFiles || result.summary.skippedBinaryFiles) {
|
|
162
|
+
process.exitCode = 2;
|
|
163
|
+
} else if (scoreThreshold !== void 0 && !isNaN(scoreThreshold) && result.summary.score !== null && result.summary.score < scoreThreshold) {
|
|
149
164
|
process.exitCode = 1;
|
|
150
165
|
} else {
|
|
151
166
|
process.exitCode = 0;
|
|
@@ -153,10 +168,10 @@ has-supported-changes=${prResult.hasSupportedChanges}
|
|
|
153
168
|
}
|
|
154
169
|
} catch (err) {
|
|
155
170
|
if (err instanceof ConfigError || err instanceof GitResolutionError || err instanceof InvalidDetectorError || err instanceof GitError || err.name === "GitError") {
|
|
156
|
-
console.error(`agent-roast error: ${err
|
|
171
|
+
console.error(`agent-roast error: ${errorMessage(err)}`);
|
|
157
172
|
process.exitCode = 2;
|
|
158
173
|
} else {
|
|
159
|
-
console.error(`Error auditing repository: ${err
|
|
174
|
+
console.error(`Error auditing repository: ${errorMessage(err)}`);
|
|
160
175
|
process.exitCode = 1;
|
|
161
176
|
}
|
|
162
177
|
}
|
|
@@ -172,7 +187,7 @@ program.command("review").description("Compare full changed source at merge-base
|
|
|
172
187
|
}
|
|
173
188
|
await runContextReview(repoPath, options, options.staged);
|
|
174
189
|
} catch (error) {
|
|
175
|
-
console.error(`agent-roast review error: ${
|
|
190
|
+
console.error(`agent-roast review error: ${errorMessage(error)}`);
|
|
176
191
|
process.exitCode = 2;
|
|
177
192
|
}
|
|
178
193
|
});
|
|
@@ -186,6 +201,7 @@ program.command("check").description("Check current tracked and non-ignored untr
|
|
|
186
201
|
const result = await checkProject({
|
|
187
202
|
cwd: repoPath,
|
|
188
203
|
baseline: opts.baseline,
|
|
204
|
+
ignoreExceptions: global.ignoreExceptions,
|
|
189
205
|
failOn: global.failOn !== void 0 ? global.failOn.split(",").map((rule) => rule.trim()).filter(Boolean) : void 0,
|
|
190
206
|
format: global.json ? "json" : global.format
|
|
191
207
|
});
|
|
@@ -194,22 +210,27 @@ program.command("check").description("Check current tracked and non-ignored untr
|
|
|
194
210
|
const basePath = path.resolve(repoPath, opts.writeBaseline);
|
|
195
211
|
writeBaseline(basePath, result.infractions);
|
|
196
212
|
const message = `Baseline created: ${basePath}
|
|
197
|
-
Accepted ${result.
|
|
213
|
+
Accepted ${result.infractions.length} existing findings. The code has not been fixed.
|
|
198
214
|
Review and commit the baseline, then run check --baseline <file> to catch new findings.`;
|
|
199
|
-
console.log(global.json || global.format === "json" ? JSON.stringify({ operation: "write-baseline", baseline: basePath, acceptedFindings: result.
|
|
215
|
+
console.log(global.json || global.format === "json" ? JSON.stringify({ operation: "write-baseline", baseline: basePath, acceptedFindings: result.infractions.length }, null, 2) : message);
|
|
200
216
|
process.exitCode = 0;
|
|
201
217
|
return;
|
|
202
218
|
}
|
|
203
219
|
console.log(result.output);
|
|
204
220
|
process.exitCode = result.exitCode;
|
|
205
221
|
} catch (err) {
|
|
206
|
-
console.error(`agent-roast check error: ${
|
|
222
|
+
console.error(`agent-roast check error: ${errorMessage(err)}`);
|
|
207
223
|
process.exitCode = 2;
|
|
208
224
|
}
|
|
209
225
|
});
|
|
210
226
|
program.command("explain <rule>").description("Explain a detector, its limits, and how to address a finding").action((rule) => {
|
|
227
|
+
if (program.opts().ignoreExceptions) {
|
|
228
|
+
console.error("--ignore-exceptions applies to check, review and --staged context checks.");
|
|
229
|
+
process.exitCode = 2;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
211
232
|
if (!VALID_DETECTORS.includes(rule)) {
|
|
212
|
-
console.error(`Unknown rule '${rule}'. Valid rules: ${VALID_DETECTORS.join(", ")}`);
|
|
233
|
+
console.error(`Unknown rule '${terminalText(rule)}'. Valid rules: ${VALID_DETECTORS.join(", ")}`);
|
|
213
234
|
process.exitCode = 2;
|
|
214
235
|
return;
|
|
215
236
|
}
|
|
@@ -225,6 +246,7 @@ Example: ${guide.example}
|
|
|
225
246
|
});
|
|
226
247
|
program.command("init").description("Initialize .agentroastrc.json and CI/hook configurations").argument("[dir]", "Directory to initialize", ".").option("-y, --yes", "Skip interactive prompts and use defaults", false).option("--ci", "Generate GitHub Actions workflow file", false).option("--hook", "Install Git pre-commit hook", false).action(async (targetDir, opts) => {
|
|
227
248
|
try {
|
|
249
|
+
if (program.opts().ignoreExceptions) throw new ConfigError("--ignore-exceptions applies to check, review and --staged context checks.");
|
|
228
250
|
const res = await initProject({
|
|
229
251
|
cwd: targetDir,
|
|
230
252
|
yes: opts.yes,
|
|
@@ -232,31 +254,33 @@ program.command("init").description("Initialize .agentroastrc.json and CI/hook c
|
|
|
232
254
|
hook: opts.hook
|
|
233
255
|
});
|
|
234
256
|
if (res.created.length > 0) {
|
|
235
|
-
console.log(`Initialized agent-roast in ${
|
|
257
|
+
console.log(`Initialized agent-roast in ${terminalText(res.directory)}:`);
|
|
236
258
|
for (const file of res.created) {
|
|
237
|
-
console.log(` + Created ${file}`);
|
|
259
|
+
console.log(` + Created ${terminalText(file)}`);
|
|
238
260
|
}
|
|
239
261
|
}
|
|
240
262
|
if (res.skipped.length > 0) {
|
|
241
263
|
for (const file of res.skipped) {
|
|
242
|
-
console.log(` - Skipped ${file} (already exists or not applicable)`);
|
|
264
|
+
console.log(` - Skipped ${terminalText(file)} (already exists or not applicable)`);
|
|
243
265
|
}
|
|
244
266
|
}
|
|
245
267
|
if (res.created.length === 0 && res.skipped.length === 0) {
|
|
246
268
|
console.log("No files were created.");
|
|
247
269
|
}
|
|
248
270
|
} catch (err) {
|
|
249
|
-
console.error(`Failed to initialize agent-roast: ${err
|
|
271
|
+
console.error(`Failed to initialize agent-roast: ${errorMessage(err)}`);
|
|
250
272
|
process.exitCode = 1;
|
|
251
273
|
}
|
|
252
274
|
});
|
|
253
|
-
program.command("badge").description("Generate an AI discipline badge (SVG, Markdown, or Shields JSON)").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("--label <text>", "Badge label text", "AI Discipline").option("--out <file>", "Output SVG file path (e.g. ./badge.svg)").action(async (repoPath, opts) => {
|
|
275
|
+
program.command("badge").description("Generate an AI discipline badge (SVG, Markdown, or Shields JSON)").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("--label <text>", "Badge label text", "AI Discipline").option("--out <file>", "Output SVG file path (e.g. ./badge.svg)").action(async (repoPath, opts, command) => {
|
|
254
276
|
try {
|
|
255
277
|
const rootOpts = program.opts();
|
|
256
278
|
const isJson = Boolean(opts.json || rootOpts.json);
|
|
257
279
|
const isAll = Boolean(opts.all || rootOpts.all);
|
|
258
|
-
|
|
259
|
-
const
|
|
280
|
+
if (rootOpts.ignoreExceptions) throw new ConfigError("--ignore-exceptions applies to check, review and --staged context checks.");
|
|
281
|
+
const since = command.getOptionValueSource("since") === "cli" ? opts.since : rootOpts.since || "90 days ago";
|
|
282
|
+
const configRoot = gitOutputPath((await readGit(path.resolve(repoPath), ["rev-parse", "--show-toplevel"])).toString("utf8"));
|
|
283
|
+
const config = loadConfig(configRoot);
|
|
260
284
|
const result = await roastRepository({
|
|
261
285
|
cwd: repoPath,
|
|
262
286
|
since,
|
|
@@ -265,6 +289,7 @@ program.command("badge").description("Generate an AI discipline badge (SVG, Mark
|
|
|
265
289
|
rules: config.rules,
|
|
266
290
|
panicLoopWindowMinutes: config.panicLoopWindowMinutes
|
|
267
291
|
});
|
|
292
|
+
if (result.summary.skippedLongLines || result.summary.skippedUnknownFiles || result.summary.skippedBinaryFiles) throw new ConfigError("Cannot generate a badge from an incomplete historical audit; resolve skipped source first.");
|
|
268
293
|
const score = result.summary.score;
|
|
269
294
|
const label = opts.label || "AI Discipline";
|
|
270
295
|
if (isJson) {
|
|
@@ -278,7 +303,7 @@ program.command("badge").description("Generate an AI discipline badge (SVG, Mark
|
|
|
278
303
|
const outPath = path.resolve(opts.out);
|
|
279
304
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
280
305
|
fs.writeFileSync(outPath, svg, "utf-8");
|
|
281
|
-
console.log(`Generated SVG badge at: ${opts.out}`);
|
|
306
|
+
console.log(`Generated SVG badge at: ${terminalText(opts.out)}`);
|
|
282
307
|
process.exitCode = 0;
|
|
283
308
|
return;
|
|
284
309
|
}
|
|
@@ -290,8 +315,8 @@ ${markdown}
|
|
|
290
315
|
`);
|
|
291
316
|
process.exitCode = 0;
|
|
292
317
|
} catch (err) {
|
|
293
|
-
console.error(`Error generating badge: ${err
|
|
294
|
-
process.exitCode = 1;
|
|
318
|
+
console.error(`Error generating badge: ${errorMessage(err)}`);
|
|
319
|
+
process.exitCode = err instanceof ConfigError || err instanceof GitError || err instanceof GitResolutionError ? 2 : 1;
|
|
295
320
|
}
|
|
296
321
|
});
|
|
297
322
|
program.parse(process.argv);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import { Readable } from 'stream';
|
|
2
2
|
|
|
3
|
+
/** A reviewed allowance for a precise pattern and number of occurrences.
|
|
4
|
+
* No wildcard, inline bypass, source snippet or executable policy is needed. */
|
|
5
|
+
interface FindingException {
|
|
6
|
+
fingerprint: string;
|
|
7
|
+
count: number;
|
|
8
|
+
reason: string;
|
|
9
|
+
expires: string;
|
|
10
|
+
}
|
|
11
|
+
interface SuppressedFinding extends Infraction {
|
|
12
|
+
suppression: {
|
|
13
|
+
reason: string;
|
|
14
|
+
expires: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
declare function validateExceptions(value: unknown): asserts value is FindingException[] | undefined;
|
|
18
|
+
/** Line shifts are tolerated; duplicate occurrences beyond the approved count
|
|
19
|
+
* remain actionable. Expiry uses UTC calendar days, inclusive through that day. */
|
|
20
|
+
declare function applyExceptions(findings: Infraction[], exceptions?: FindingException[], now?: Date, priorOccurrences?: ReadonlyMap<string, number>): {
|
|
21
|
+
findings: Infraction[];
|
|
22
|
+
suppressedFindings: SuppressedFinding[];
|
|
23
|
+
expiredExceptionCount: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
3
26
|
interface CommitInfo {
|
|
4
27
|
hash: string;
|
|
5
28
|
date: string;
|
|
@@ -12,6 +35,8 @@ interface CommitInfo {
|
|
|
12
35
|
type DetectorType = "test-skip" | "type-escape" | "swallowed-error" | "panic-loop" | "trivial-assert" | "stub-abandonment" | "test-focus";
|
|
13
36
|
declare const VALID_DETECTORS: readonly DetectorType[];
|
|
14
37
|
interface Infraction {
|
|
38
|
+
fingerprint?: string;
|
|
39
|
+
occurrence?: number;
|
|
15
40
|
type: DetectorType;
|
|
16
41
|
file?: string;
|
|
17
42
|
lineNumber?: number;
|
|
@@ -22,6 +47,7 @@ interface Infraction {
|
|
|
22
47
|
suggestion?: string;
|
|
23
48
|
}
|
|
24
49
|
interface MetricSummary {
|
|
50
|
+
complete?: boolean;
|
|
25
51
|
schemaVersion: string;
|
|
26
52
|
totalLinesAdded: number;
|
|
27
53
|
aiLinesAdded: number;
|
|
@@ -41,6 +67,7 @@ interface MetricSummary {
|
|
|
41
67
|
archetypeDescription: string;
|
|
42
68
|
skippedLongLines?: number;
|
|
43
69
|
skippedUnknownFiles?: number;
|
|
70
|
+
skippedBinaryFiles?: number;
|
|
44
71
|
}
|
|
45
72
|
interface RoastOptions {
|
|
46
73
|
cwd?: string;
|
|
@@ -79,8 +106,10 @@ interface PrAuditResult {
|
|
|
79
106
|
infractions: Infraction[];
|
|
80
107
|
failedRules: string[];
|
|
81
108
|
passed: boolean;
|
|
109
|
+
complete?: boolean;
|
|
82
110
|
skippedLongLines: number;
|
|
83
111
|
skippedUnknownFiles: number;
|
|
112
|
+
skippedBinaryFiles?: number;
|
|
84
113
|
output: string;
|
|
85
114
|
exitCode: number;
|
|
86
115
|
}
|
|
@@ -89,6 +118,7 @@ interface CheckOptions {
|
|
|
89
118
|
failOn?: string[];
|
|
90
119
|
format?: ReviewFormat;
|
|
91
120
|
baseline?: string;
|
|
121
|
+
ignoreExceptions?: boolean;
|
|
92
122
|
signal?: AbortSignal;
|
|
93
123
|
}
|
|
94
124
|
interface CheckResult {
|
|
@@ -104,6 +134,8 @@ interface CheckResult {
|
|
|
104
134
|
file: string;
|
|
105
135
|
reason: string;
|
|
106
136
|
}[];
|
|
137
|
+
suppressedFindings?: SuppressedFinding[];
|
|
138
|
+
expiredExceptionCount?: number;
|
|
107
139
|
complete: boolean;
|
|
108
140
|
failedRules: string[];
|
|
109
141
|
passed: boolean;
|
|
@@ -117,6 +149,7 @@ interface ReviewOptions {
|
|
|
117
149
|
head?: string;
|
|
118
150
|
failOn?: string[];
|
|
119
151
|
format?: ReviewFormat;
|
|
152
|
+
ignoreExceptions?: boolean;
|
|
120
153
|
signal?: AbortSignal;
|
|
121
154
|
}
|
|
122
155
|
type StagedReviewOptions = Omit<ReviewOptions, "base" | "head">;
|
|
@@ -145,6 +178,8 @@ interface ReviewResult {
|
|
|
145
178
|
file: string;
|
|
146
179
|
reason: string;
|
|
147
180
|
}[];
|
|
181
|
+
suppressedFindings?: SuppressedFinding[];
|
|
182
|
+
expiredExceptionCount?: number;
|
|
148
183
|
complete: boolean;
|
|
149
184
|
failedRules: string[];
|
|
150
185
|
passed: boolean;
|
|
@@ -166,6 +201,7 @@ interface ParsedFileDiff {
|
|
|
166
201
|
path: string;
|
|
167
202
|
addedLines: ParsedDiffLine[];
|
|
168
203
|
deleted?: boolean;
|
|
204
|
+
binary?: boolean;
|
|
169
205
|
}
|
|
170
206
|
interface ParsedCommit extends CommitInfo {
|
|
171
207
|
files: ParsedFileDiff[];
|
|
@@ -186,6 +222,9 @@ declare class GitError extends Error {
|
|
|
186
222
|
}
|
|
187
223
|
declare const HERMETIC_GIT_FLAGS: readonly ["-c", "core.quotepath=false", "-c", "core.fsmonitor=false", "-c", "diff.noprefix=false", "-c", "diff.mnemonicPrefix=false", "--no-pager"];
|
|
188
224
|
declare function getHermeticGitEnv(extraEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
225
|
+
/** Git path output has one record-terminating LF. Spaces, CR and extra LFs may
|
|
226
|
+
* belong to the actual pathname and must not be removed with trim(). */
|
|
227
|
+
declare function gitOutputPath(value: string): string;
|
|
189
228
|
declare function parseGitLogStream(stream: Readable, onCommit: (commit: ParsedCommit, stats: {
|
|
190
229
|
skippedLongLines: number;
|
|
191
230
|
skippedUnknownFiles: number;
|
|
@@ -251,6 +290,7 @@ declare class MetricsCollector {
|
|
|
251
290
|
private stubAbandonments;
|
|
252
291
|
private skippedLongLines;
|
|
253
292
|
private skippedUnknownFiles;
|
|
293
|
+
private skippedBinaryFiles;
|
|
254
294
|
private activePanicChain;
|
|
255
295
|
private includeAll;
|
|
256
296
|
private ignorePaths?;
|
|
@@ -286,13 +326,17 @@ declare function formatGithubStepSummary(result: PrAuditResult): string;
|
|
|
286
326
|
* Generates rich GitHub PR Comment Markdown (for PR comments / sticky bots)
|
|
287
327
|
*/
|
|
288
328
|
declare function formatPrMarkdownComment(result: PrAuditResult): string;
|
|
329
|
+
declare function terminalText(value: string): string;
|
|
289
330
|
/** Current-tree report emphasizes next actions and explicitly accounts for baseline debt. */
|
|
290
331
|
declare function formatCheckTerminal(result: CheckResult): string;
|
|
291
332
|
declare function formatCheckMarkdown(result: CheckResult): string;
|
|
292
333
|
declare function formatReviewTerminal(result: ReviewResult): string;
|
|
293
334
|
declare function formatReviewMarkdown(result: ReviewResult): string;
|
|
294
335
|
|
|
336
|
+
declare const MAX_CONFIG_BYTES: number;
|
|
295
337
|
interface AgentRoastConfig {
|
|
338
|
+
/** Reviewed, occurrence-limited allowances that expire on a calendar date. */
|
|
339
|
+
exceptions?: FindingException[];
|
|
296
340
|
/** Detector rules to fail CI on in PR mode */
|
|
297
341
|
failOn?: string[];
|
|
298
342
|
/** Glob patterns or prefixes to ignore from analysis */
|
|
@@ -313,9 +357,6 @@ declare class ConfigError extends Error {
|
|
|
313
357
|
declare function configFromText(text: string, source: string): AgentRoastConfig;
|
|
314
358
|
/** Invalid policy must stop the audit instead of silently changing CI behavior. */
|
|
315
359
|
declare function loadConfig(cwd?: string): AgentRoastConfig;
|
|
316
|
-
/**
|
|
317
|
-
* Matches a relative or normalized file path against an array of glob ignore patterns.
|
|
318
|
-
*/
|
|
319
360
|
declare function matchIgnorePath(filePath: string, ignorePatterns?: string[]): boolean;
|
|
320
361
|
|
|
321
362
|
interface InitOptions {
|
|
@@ -325,6 +366,8 @@ interface InitOptions {
|
|
|
325
366
|
hook?: boolean;
|
|
326
367
|
}
|
|
327
368
|
interface InitResult {
|
|
369
|
+
/** Actual project root containing the initialized files. */
|
|
370
|
+
directory: string;
|
|
328
371
|
created: string[];
|
|
329
372
|
skipped: string[];
|
|
330
373
|
}
|
|
@@ -409,4 +452,4 @@ declare function roastRepository(options?: RoastOptions): Promise<{
|
|
|
409
452
|
}>;
|
|
410
453
|
declare function auditPullRequest(options: PrAuditOptions): Promise<PrAuditResult>;
|
|
411
454
|
|
|
412
|
-
export { type AgentRoastConfig, type AiAttributionResult, CONFIG_FILENAMES, type CheckOptions, type CheckResult, type CommitInfo, ConfigError, DEFAULT_CONFIG, DEFAULT_CONFIG_TEMPLATE, type DetectorType, FIELD_SEPARATOR, type FindingBaseline, GITHUB_WORKFLOW_TEMPLATE, GitError, GitResolutionError, HERMETIC_GIT_FLAGS, type Infraction, type InitOptions, type InitResult, InvalidDetectorError, type MetricSummary, MetricsCollector, PRE_COMMIT_HOOK_TEMPLATE, type ParseDiffResult, type ParsedCommit, type ParsedDiffLine, type ParsedFileDiff, type PrAuditOptions, type PrAuditResult, RECORD_SEPARATOR, RULE_GUIDES, type ReviewFormat, type ReviewOptions, type ReviewResult, type RoastOptions, type RuleGuide, type ShieldsEndpointResponse, type StagedReviewOptions, VALID_DETECTORS, applyBaseline, auditFileDiffs, auditPullRequest, checkProject, compareFindings, configFromText, createBaseline, detectAiAttribution, findMergeBase, findingFingerprint, formatCheckMarkdown, formatCheckTerminal, formatGithubAnnotations, formatGithubStepSummary, formatPrMarkdownComment, formatPrTerminalCard, formatReviewMarkdown, formatReviewTerminal, formatSarif, formatTerminalCard, generateShareUrl, generateShieldsJson, generateShieldsMarkdown, generateSvgBadge, getHermeticGitEnv, getScoreColor, initProject, isCodeFile, isTestFile, loadBaseline, loadConfig, matchIgnorePath, parseDiff, parseGitLogStream, resolveCommitSha, reviewChanges, reviewStagedChanges, roastRepository, streamGitDiff, streamGitLog, stripStringAndRegexLiterals, unquoteGitPath, writeBaseline };
|
|
455
|
+
export { type AgentRoastConfig, type AiAttributionResult, CONFIG_FILENAMES, type CheckOptions, type CheckResult, type CommitInfo, ConfigError, DEFAULT_CONFIG, DEFAULT_CONFIG_TEMPLATE, type DetectorType, FIELD_SEPARATOR, type FindingBaseline, type FindingException, GITHUB_WORKFLOW_TEMPLATE, GitError, GitResolutionError, HERMETIC_GIT_FLAGS, type Infraction, type InitOptions, type InitResult, InvalidDetectorError, MAX_CONFIG_BYTES, type MetricSummary, MetricsCollector, PRE_COMMIT_HOOK_TEMPLATE, type ParseDiffResult, type ParsedCommit, type ParsedDiffLine, type ParsedFileDiff, type PrAuditOptions, type PrAuditResult, RECORD_SEPARATOR, RULE_GUIDES, type ReviewFormat, type ReviewOptions, type ReviewResult, type RoastOptions, type RuleGuide, type ShieldsEndpointResponse, type StagedReviewOptions, type SuppressedFinding, VALID_DETECTORS, applyBaseline, applyExceptions, auditFileDiffs, auditPullRequest, checkProject, compareFindings, configFromText, createBaseline, detectAiAttribution, findMergeBase, findingFingerprint, formatCheckMarkdown, formatCheckTerminal, formatGithubAnnotations, formatGithubStepSummary, formatPrMarkdownComment, formatPrTerminalCard, formatReviewMarkdown, formatReviewTerminal, formatSarif, formatTerminalCard, generateShareUrl, generateShieldsJson, generateShieldsMarkdown, generateSvgBadge, getHermeticGitEnv, getScoreColor, gitOutputPath, initProject, isCodeFile, isTestFile, loadBaseline, loadConfig, matchIgnorePath, parseDiff, parseGitLogStream, resolveCommitSha, reviewChanges, reviewStagedChanges, roastRepository, streamGitDiff, streamGitLog, stripStringAndRegexLiterals, terminalText, unquoteGitPath, validateExceptions, writeBaseline };
|
package/dist/index.js
CHANGED
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
GitResolutionError,
|
|
11
11
|
HERMETIC_GIT_FLAGS,
|
|
12
12
|
InvalidDetectorError,
|
|
13
|
+
MAX_CONFIG_BYTES,
|
|
13
14
|
MetricsCollector,
|
|
14
15
|
PRE_COMMIT_HOOK_TEMPLATE,
|
|
15
16
|
RECORD_SEPARATOR,
|
|
16
17
|
RULE_GUIDES,
|
|
17
18
|
VALID_DETECTORS,
|
|
18
19
|
applyBaseline,
|
|
20
|
+
applyExceptions,
|
|
19
21
|
auditFileDiffs,
|
|
20
22
|
auditPullRequest,
|
|
21
23
|
checkProject,
|
|
@@ -41,6 +43,7 @@ import {
|
|
|
41
43
|
generateSvgBadge,
|
|
42
44
|
getHermeticGitEnv,
|
|
43
45
|
getScoreColor,
|
|
46
|
+
gitOutputPath,
|
|
44
47
|
initProject,
|
|
45
48
|
isCodeFile,
|
|
46
49
|
isTestFile,
|
|
@@ -56,9 +59,11 @@ import {
|
|
|
56
59
|
streamGitDiff,
|
|
57
60
|
streamGitLog,
|
|
58
61
|
stripStringAndRegexLiterals,
|
|
62
|
+
terminalText,
|
|
59
63
|
unquoteGitPath,
|
|
64
|
+
validateExceptions,
|
|
60
65
|
writeBaseline
|
|
61
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-U7OCVA3D.js";
|
|
62
67
|
export {
|
|
63
68
|
CONFIG_FILENAMES,
|
|
64
69
|
ConfigError,
|
|
@@ -70,12 +75,14 @@ export {
|
|
|
70
75
|
GitResolutionError,
|
|
71
76
|
HERMETIC_GIT_FLAGS,
|
|
72
77
|
InvalidDetectorError,
|
|
78
|
+
MAX_CONFIG_BYTES,
|
|
73
79
|
MetricsCollector,
|
|
74
80
|
PRE_COMMIT_HOOK_TEMPLATE,
|
|
75
81
|
RECORD_SEPARATOR,
|
|
76
82
|
RULE_GUIDES,
|
|
77
83
|
VALID_DETECTORS,
|
|
78
84
|
applyBaseline,
|
|
85
|
+
applyExceptions,
|
|
79
86
|
auditFileDiffs,
|
|
80
87
|
auditPullRequest,
|
|
81
88
|
checkProject,
|
|
@@ -101,6 +108,7 @@ export {
|
|
|
101
108
|
generateSvgBadge,
|
|
102
109
|
getHermeticGitEnv,
|
|
103
110
|
getScoreColor,
|
|
111
|
+
gitOutputPath,
|
|
104
112
|
initProject,
|
|
105
113
|
isCodeFile,
|
|
106
114
|
isTestFile,
|
|
@@ -116,6 +124,8 @@ export {
|
|
|
116
124
|
streamGitDiff,
|
|
117
125
|
streamGitLog,
|
|
118
126
|
stripStringAndRegexLiterals,
|
|
127
|
+
terminalText,
|
|
119
128
|
unquoteGitPath,
|
|
129
|
+
validateExceptions,
|
|
120
130
|
writeBaseline
|
|
121
131
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-roast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Offline code checks for skipped tests, swallowed errors, unfinished stubs and AI coding shortcuts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"README.md",
|
|
14
|
-
"LICENSE"
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"schema.json"
|
|
15
16
|
],
|
|
16
17
|
"scripts": {
|
|
17
18
|
"build": "tsup",
|
|
@@ -51,9 +52,13 @@
|
|
|
51
52
|
"gifenc": "^1.0.3",
|
|
52
53
|
"tsup": "^8.0.0",
|
|
53
54
|
"typescript": "^5.0.0",
|
|
54
|
-
"vitest": "^
|
|
55
|
+
"vitest": "^4.1.12"
|
|
55
56
|
},
|
|
56
57
|
"engines": {
|
|
57
58
|
"node": ">=18"
|
|
59
|
+
},
|
|
60
|
+
"overrides": {
|
|
61
|
+
"esbuild": "^0.28.1",
|
|
62
|
+
"vite": "^7.3.7"
|
|
58
63
|
}
|
|
59
64
|
}
|
package/schema.json
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/Nari-Ab/agent-roast/main/schema.json",
|
|
4
|
+
"title": "Agent Roast configuration",
|
|
5
|
+
"description": "Offline review policy. Changes are policy changes and require human review.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"failOn": {
|
|
13
|
+
"description": "Enabled rules which block; an empty array is advisory.",
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": {
|
|
16
|
+
"enum": [
|
|
17
|
+
"test-skip",
|
|
18
|
+
"type-escape",
|
|
19
|
+
"swallowed-error",
|
|
20
|
+
"panic-loop",
|
|
21
|
+
"trivial-assert",
|
|
22
|
+
"stub-abandonment",
|
|
23
|
+
"test-focus"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"ignorePaths": {
|
|
28
|
+
"description": "Repository-relative simple glob or directory-prefix exclusions.",
|
|
29
|
+
"type": "array",
|
|
30
|
+
"items": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"maxLength": 1024
|
|
33
|
+
},
|
|
34
|
+
"maxItems": 1000
|
|
35
|
+
},
|
|
36
|
+
"scoreThreshold": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"minimum": 0,
|
|
39
|
+
"maximum": 100
|
|
40
|
+
},
|
|
41
|
+
"panicLoopWindowMinutes": {
|
|
42
|
+
"type": "number",
|
|
43
|
+
"exclusiveMinimum": 0
|
|
44
|
+
},
|
|
45
|
+
"rules": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"additionalProperties": false,
|
|
48
|
+
"properties": {
|
|
49
|
+
"test-skip": {
|
|
50
|
+
"enum": [
|
|
51
|
+
"error",
|
|
52
|
+
"warn",
|
|
53
|
+
"off"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"type-escape": {
|
|
57
|
+
"enum": [
|
|
58
|
+
"error",
|
|
59
|
+
"warn",
|
|
60
|
+
"off"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"swallowed-error": {
|
|
64
|
+
"enum": [
|
|
65
|
+
"error",
|
|
66
|
+
"warn",
|
|
67
|
+
"off"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"panic-loop": {
|
|
71
|
+
"enum": [
|
|
72
|
+
"error",
|
|
73
|
+
"warn",
|
|
74
|
+
"off"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"trivial-assert": {
|
|
78
|
+
"enum": [
|
|
79
|
+
"error",
|
|
80
|
+
"warn",
|
|
81
|
+
"off"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
"stub-abandonment": {
|
|
85
|
+
"enum": [
|
|
86
|
+
"error",
|
|
87
|
+
"warn",
|
|
88
|
+
"off"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"test-focus": {
|
|
92
|
+
"enum": [
|
|
93
|
+
"error",
|
|
94
|
+
"warn",
|
|
95
|
+
"off"
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"exceptions": {
|
|
101
|
+
"description": "Precise, reviewed allowances. A new duplicate beyond count remains actionable; expiry includes its UTC date.",
|
|
102
|
+
"type": "array",
|
|
103
|
+
"maxItems": 1000,
|
|
104
|
+
"items": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"required": [
|
|
108
|
+
"fingerprint",
|
|
109
|
+
"count",
|
|
110
|
+
"reason",
|
|
111
|
+
"expires"
|
|
112
|
+
],
|
|
113
|
+
"properties": {
|
|
114
|
+
"fingerprint": {
|
|
115
|
+
"type": "string",
|
|
116
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
117
|
+
},
|
|
118
|
+
"count": {
|
|
119
|
+
"type": "integer",
|
|
120
|
+
"minimum": 1,
|
|
121
|
+
"maximum": 100000
|
|
122
|
+
},
|
|
123
|
+
"reason": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"minLength": 1,
|
|
126
|
+
"maxLength": 1000,
|
|
127
|
+
"pattern": "^(?=.*\\S)[^\\u0000-\\u001f\\u007f-\\u009f\\u061c\\u200e\\u200f\\u2028-\\u202e\\u2066-\\u2069]*$"
|
|
128
|
+
},
|
|
129
|
+
"expires": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"format": "date",
|
|
132
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|