agent-roast 0.6.0 → 0.8.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/README.md +119 -19
- package/dist/{chunk-RU5OYHLR.js → chunk-C423TSZF.js} +1119 -612
- package/dist/cli.js +93 -56
- package/dist/index.d.ts +51 -5
- package/dist/index.js +13 -1
- package/package.json +8 -3
- package/schema.json +138 -0
package/dist/cli.js
CHANGED
|
@@ -13,12 +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,
|
|
21
|
+
reviewStagedChanges,
|
|
19
22
|
roastRepository,
|
|
23
|
+
terminalText,
|
|
20
24
|
writeBaseline
|
|
21
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-C423TSZF.js";
|
|
22
26
|
|
|
23
27
|
// src/cli.ts
|
|
24
28
|
import { Command } from "commander";
|
|
@@ -27,8 +31,47 @@ import * as fs from "fs";
|
|
|
27
31
|
import * as path from "path";
|
|
28
32
|
var require2 = createRequire(import.meta.url);
|
|
29
33
|
var pkg = require2("../package.json");
|
|
34
|
+
var errorMessage = (error) => terminalText(error instanceof Error ? error.message : String(error));
|
|
35
|
+
async function runContextReview(repoPath, options, staged) {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const abort = () => controller.abort();
|
|
38
|
+
process.once("SIGINT", abort);
|
|
39
|
+
process.once("SIGTERM", abort);
|
|
40
|
+
try {
|
|
41
|
+
const common = {
|
|
42
|
+
cwd: repoPath,
|
|
43
|
+
failOn: options.failOn !== void 0 ? options.failOn.split(",").map((rule) => rule.trim()).filter(Boolean) : void 0,
|
|
44
|
+
ignoreExceptions: options.ignoreExceptions,
|
|
45
|
+
format: options.json ? "json" : options.format,
|
|
46
|
+
signal: controller.signal
|
|
47
|
+
};
|
|
48
|
+
const result = staged ? await reviewStagedChanges(common) : await reviewChanges({ ...common, base: options.base, head: options.head });
|
|
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}
|
|
54
|
+
infractions-count=${result.infractions.length}
|
|
55
|
+
has-supported-changes=${result.filesInspected > 0}
|
|
56
|
+
complete=${result.complete}
|
|
57
|
+
existing-count=${result.unchangedCount}
|
|
58
|
+
removed-count=${result.removedFindings.length}
|
|
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
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
process.removeListener("SIGINT", abort);
|
|
70
|
+
process.removeListener("SIGTERM", abort);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
30
73
|
var program = new Command();
|
|
31
|
-
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) => {
|
|
32
75
|
try {
|
|
33
76
|
if (process.env.GITHUB_EVENT_NAME === "pull_request_target") {
|
|
34
77
|
console.warn(
|
|
@@ -36,6 +79,14 @@ program.name("agent-roast").description("Audit your git history or PR diffs for
|
|
|
36
79
|
);
|
|
37
80
|
}
|
|
38
81
|
if (options.staged && options.base) throw new ConfigError("--staged cannot be combined with --base.");
|
|
82
|
+
if (options.staged) {
|
|
83
|
+
if (options.all || options.failUnder !== void 0 || program.getOptionValueSource("since") === "cli" || program.getOptionValueSource("head") === "cli") {
|
|
84
|
+
throw new ConfigError("--staged compares HEAD and index; --all, --since, --head and --fail-under do not apply.");
|
|
85
|
+
}
|
|
86
|
+
await runContextReview(repoPath, options, true);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (options.ignoreExceptions) throw new ConfigError("--ignore-exceptions applies to check, review and --staged context checks.");
|
|
39
90
|
if (!["terminal", "json", "github", "markdown"].includes(options.format)) {
|
|
40
91
|
throw new ConfigError("--format must be terminal, json, github, or markdown.");
|
|
41
92
|
}
|
|
@@ -48,7 +99,8 @@ program.name("agent-roast").description("Audit your git history or PR diffs for
|
|
|
48
99
|
if (!options.base && !options.staged && !options.json && !["terminal", "json"].includes(options.format)) {
|
|
49
100
|
throw new ConfigError("Repository audits support terminal or json format; use --base or --staged for github/markdown.");
|
|
50
101
|
}
|
|
51
|
-
const
|
|
102
|
+
const configRoot = gitOutputPath((await readGit(path.resolve(repoPath), ["rev-parse", "--show-toplevel"])).toString("utf8"));
|
|
103
|
+
const config = loadConfig(configRoot);
|
|
52
104
|
if (options.base || options.staged) {
|
|
53
105
|
const failOnRules = options.failOn !== void 0 ? options.failOn.split(",").map((s) => s.trim()).filter(Boolean) : config.failOn;
|
|
54
106
|
const chosenFormat = options.json ? "json" : options.format || "terminal";
|
|
@@ -71,7 +123,7 @@ program.name("agent-roast").description("Audit your git history or PR diffs for
|
|
|
71
123
|
formatGithubStepSummary(prResult) + "\n"
|
|
72
124
|
);
|
|
73
125
|
} catch (err) {
|
|
74
|
-
console.error(`Warning: Failed to write to GITHUB_STEP_SUMMARY: ${err
|
|
126
|
+
console.error(`Warning: Failed to write to GITHUB_STEP_SUMMARY: ${errorMessage(err)}`);
|
|
75
127
|
}
|
|
76
128
|
}
|
|
77
129
|
if (process.env.GITHUB_OUTPUT) {
|
|
@@ -84,7 +136,7 @@ has-supported-changes=${prResult.hasSupportedChanges}
|
|
|
84
136
|
`
|
|
85
137
|
);
|
|
86
138
|
} catch (err) {
|
|
87
|
-
console.error(`Warning: Failed to write to GITHUB_OUTPUT: ${err
|
|
139
|
+
console.error(`Warning: Failed to write to GITHUB_OUTPUT: ${errorMessage(err)}`);
|
|
88
140
|
}
|
|
89
141
|
}
|
|
90
142
|
console.log(prResult.output);
|
|
@@ -106,7 +158,9 @@ has-supported-changes=${prResult.hasSupportedChanges}
|
|
|
106
158
|
console.log(result.output);
|
|
107
159
|
}
|
|
108
160
|
const scoreThreshold = options.failUnder !== void 0 ? Number(options.failUnder) : config.scoreThreshold;
|
|
109
|
-
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) {
|
|
110
164
|
process.exitCode = 1;
|
|
111
165
|
} else {
|
|
112
166
|
process.exitCode = 0;
|
|
@@ -114,53 +168,26 @@ has-supported-changes=${prResult.hasSupportedChanges}
|
|
|
114
168
|
}
|
|
115
169
|
} catch (err) {
|
|
116
170
|
if (err instanceof ConfigError || err instanceof GitResolutionError || err instanceof InvalidDetectorError || err instanceof GitError || err.name === "GitError") {
|
|
117
|
-
console.error(`agent-roast error: ${err
|
|
171
|
+
console.error(`agent-roast error: ${errorMessage(err)}`);
|
|
118
172
|
process.exitCode = 2;
|
|
119
173
|
} else {
|
|
120
|
-
console.error(`Error auditing repository: ${err
|
|
174
|
+
console.error(`Error auditing repository: ${errorMessage(err)}`);
|
|
121
175
|
process.exitCode = 1;
|
|
122
176
|
}
|
|
123
177
|
}
|
|
124
178
|
});
|
|
125
|
-
program.command("review").description("Compare full changed source at merge-base and head; gate only new findings").argument("[path]", "Path to the Git repository", ".").addHelpText("after", "\nRequired: --base <trusted-ref> Optional: --head <ref> (default HEAD)\nOutput: --format terminal|json|github|markdown|sarif, --json, --fail-on <rules>\nExample: agent-roast review --base origin/main --format markdown").action(async (repoPath) => {
|
|
179
|
+
program.command("review").description("Compare full changed source at merge-base and head; gate only new findings").argument("[path]", "Path to the Git repository", ".").addHelpText("after", "\nRequired: --base <trusted-ref> Optional: --head <ref> (default HEAD), or use --staged for HEAD/index\nOutput: --format terminal|json|github|markdown|sarif, --json, --fail-on <rules>\nExample: agent-roast review --base origin/main --format markdown").action(async (repoPath) => {
|
|
126
180
|
try {
|
|
127
181
|
const options = program.opts();
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
throw new ConfigError("review compares committed refs; --staged, --all, --since and --fail-under do not apply.");
|
|
182
|
+
if (options.staged ? options.base || program.getOptionValueSource("head") === "cli" : !options.base) {
|
|
183
|
+
throw new ConfigError("Use review --base <trusted-ref> or review --staged; --base/--head cannot be combined with --staged.");
|
|
131
184
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
process.once("SIGINT", abort);
|
|
135
|
-
process.once("SIGTERM", abort);
|
|
136
|
-
try {
|
|
137
|
-
const result = await reviewChanges({
|
|
138
|
-
cwd: repoPath,
|
|
139
|
-
base: options.base,
|
|
140
|
-
head: options.head,
|
|
141
|
-
failOn: options.failOn !== void 0 ? options.failOn.split(",").map((rule) => rule.trim()).filter(Boolean) : void 0,
|
|
142
|
-
format: options.json ? "json" : options.format,
|
|
143
|
-
signal: controller.signal
|
|
144
|
-
});
|
|
145
|
-
if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, formatReviewMarkdown(result) + "\n");
|
|
146
|
-
if (process.env.GITHUB_OUTPUT) fs.appendFileSync(
|
|
147
|
-
process.env.GITHUB_OUTPUT,
|
|
148
|
-
`passed=${result.passed}
|
|
149
|
-
infractions-count=${result.infractions.length}
|
|
150
|
-
has-supported-changes=${result.filesInspected > 0}
|
|
151
|
-
complete=${result.complete}
|
|
152
|
-
existing-count=${result.unchangedCount}
|
|
153
|
-
removed-count=${result.removedFindings.length}
|
|
154
|
-
`
|
|
155
|
-
);
|
|
156
|
-
console.log(result.output);
|
|
157
|
-
process.exitCode = result.exitCode;
|
|
158
|
-
} finally {
|
|
159
|
-
process.removeListener("SIGINT", abort);
|
|
160
|
-
process.removeListener("SIGTERM", abort);
|
|
185
|
+
if (options.all || options.failUnder !== void 0 || program.getOptionValueSource("since") === "cli") {
|
|
186
|
+
throw new ConfigError("review compares source snapshots; --all, --since and --fail-under do not apply.");
|
|
161
187
|
}
|
|
188
|
+
await runContextReview(repoPath, options, options.staged);
|
|
162
189
|
} catch (error) {
|
|
163
|
-
console.error(`agent-roast review error: ${
|
|
190
|
+
console.error(`agent-roast review error: ${errorMessage(error)}`);
|
|
164
191
|
process.exitCode = 2;
|
|
165
192
|
}
|
|
166
193
|
});
|
|
@@ -174,6 +201,7 @@ program.command("check").description("Check current tracked and non-ignored untr
|
|
|
174
201
|
const result = await checkProject({
|
|
175
202
|
cwd: repoPath,
|
|
176
203
|
baseline: opts.baseline,
|
|
204
|
+
ignoreExceptions: global.ignoreExceptions,
|
|
177
205
|
failOn: global.failOn !== void 0 ? global.failOn.split(",").map((rule) => rule.trim()).filter(Boolean) : void 0,
|
|
178
206
|
format: global.json ? "json" : global.format
|
|
179
207
|
});
|
|
@@ -182,22 +210,27 @@ program.command("check").description("Check current tracked and non-ignored untr
|
|
|
182
210
|
const basePath = path.resolve(repoPath, opts.writeBaseline);
|
|
183
211
|
writeBaseline(basePath, result.infractions);
|
|
184
212
|
const message = `Baseline created: ${basePath}
|
|
185
|
-
Accepted ${result.
|
|
213
|
+
Accepted ${result.infractions.length} existing findings. The code has not been fixed.
|
|
186
214
|
Review and commit the baseline, then run check --baseline <file> to catch new findings.`;
|
|
187
|
-
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);
|
|
188
216
|
process.exitCode = 0;
|
|
189
217
|
return;
|
|
190
218
|
}
|
|
191
219
|
console.log(result.output);
|
|
192
220
|
process.exitCode = result.exitCode;
|
|
193
221
|
} catch (err) {
|
|
194
|
-
console.error(`agent-roast check error: ${
|
|
222
|
+
console.error(`agent-roast check error: ${errorMessage(err)}`);
|
|
195
223
|
process.exitCode = 2;
|
|
196
224
|
}
|
|
197
225
|
});
|
|
198
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
|
+
}
|
|
199
232
|
if (!VALID_DETECTORS.includes(rule)) {
|
|
200
|
-
console.error(`Unknown rule '${rule}'. Valid rules: ${VALID_DETECTORS.join(", ")}`);
|
|
233
|
+
console.error(`Unknown rule '${terminalText(rule)}'. Valid rules: ${VALID_DETECTORS.join(", ")}`);
|
|
201
234
|
process.exitCode = 2;
|
|
202
235
|
return;
|
|
203
236
|
}
|
|
@@ -213,6 +246,7 @@ Example: ${guide.example}
|
|
|
213
246
|
});
|
|
214
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) => {
|
|
215
248
|
try {
|
|
249
|
+
if (program.opts().ignoreExceptions) throw new ConfigError("--ignore-exceptions applies to check, review and --staged context checks.");
|
|
216
250
|
const res = await initProject({
|
|
217
251
|
cwd: targetDir,
|
|
218
252
|
yes: opts.yes,
|
|
@@ -220,31 +254,33 @@ program.command("init").description("Initialize .agentroastrc.json and CI/hook c
|
|
|
220
254
|
hook: opts.hook
|
|
221
255
|
});
|
|
222
256
|
if (res.created.length > 0) {
|
|
223
|
-
console.log(`Initialized agent-roast in ${targetDir}:`);
|
|
257
|
+
console.log(`Initialized agent-roast in ${terminalText(targetDir)}:`);
|
|
224
258
|
for (const file of res.created) {
|
|
225
|
-
console.log(` + Created ${file}`);
|
|
259
|
+
console.log(` + Created ${terminalText(file)}`);
|
|
226
260
|
}
|
|
227
261
|
}
|
|
228
262
|
if (res.skipped.length > 0) {
|
|
229
263
|
for (const file of res.skipped) {
|
|
230
|
-
console.log(` - Skipped ${file} (already exists or not applicable)`);
|
|
264
|
+
console.log(` - Skipped ${terminalText(file)} (already exists or not applicable)`);
|
|
231
265
|
}
|
|
232
266
|
}
|
|
233
267
|
if (res.created.length === 0 && res.skipped.length === 0) {
|
|
234
268
|
console.log("No files were created.");
|
|
235
269
|
}
|
|
236
270
|
} catch (err) {
|
|
237
|
-
console.error(`Failed to initialize agent-roast: ${err
|
|
271
|
+
console.error(`Failed to initialize agent-roast: ${errorMessage(err)}`);
|
|
238
272
|
process.exitCode = 1;
|
|
239
273
|
}
|
|
240
274
|
});
|
|
241
|
-
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) => {
|
|
242
276
|
try {
|
|
243
277
|
const rootOpts = program.opts();
|
|
244
278
|
const isJson = Boolean(opts.json || rootOpts.json);
|
|
245
279
|
const isAll = Boolean(opts.all || rootOpts.all);
|
|
246
|
-
|
|
247
|
-
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);
|
|
248
284
|
const result = await roastRepository({
|
|
249
285
|
cwd: repoPath,
|
|
250
286
|
since,
|
|
@@ -253,6 +289,7 @@ program.command("badge").description("Generate an AI discipline badge (SVG, Mark
|
|
|
253
289
|
rules: config.rules,
|
|
254
290
|
panicLoopWindowMinutes: config.panicLoopWindowMinutes
|
|
255
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.");
|
|
256
293
|
const score = result.summary.score;
|
|
257
294
|
const label = opts.label || "AI Discipline";
|
|
258
295
|
if (isJson) {
|
|
@@ -266,7 +303,7 @@ program.command("badge").description("Generate an AI discipline badge (SVG, Mark
|
|
|
266
303
|
const outPath = path.resolve(opts.out);
|
|
267
304
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
268
305
|
fs.writeFileSync(outPath, svg, "utf-8");
|
|
269
|
-
console.log(`Generated SVG badge at: ${opts.out}`);
|
|
306
|
+
console.log(`Generated SVG badge at: ${terminalText(opts.out)}`);
|
|
270
307
|
process.exitCode = 0;
|
|
271
308
|
return;
|
|
272
309
|
}
|
|
@@ -278,8 +315,8 @@ ${markdown}
|
|
|
278
315
|
`);
|
|
279
316
|
process.exitCode = 0;
|
|
280
317
|
} catch (err) {
|
|
281
|
-
console.error(`Error generating badge: ${err
|
|
282
|
-
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;
|
|
283
320
|
}
|
|
284
321
|
});
|
|
285
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;
|
|
@@ -9,9 +32,11 @@ interface CommitInfo {
|
|
|
9
32
|
isAiAttributed: boolean;
|
|
10
33
|
aiSignatures: string[];
|
|
11
34
|
}
|
|
12
|
-
type DetectorType = "test-skip" | "type-escape" | "swallowed-error" | "panic-loop" | "trivial-assert" | "stub-abandonment";
|
|
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,12 +47,14 @@ 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;
|
|
28
54
|
totalCommits: number;
|
|
29
55
|
aiCommits: number;
|
|
30
56
|
testSkips: Infraction[];
|
|
57
|
+
focusedTests?: Infraction[];
|
|
31
58
|
typeEscapes: Infraction[];
|
|
32
59
|
swallowedErrors: Infraction[];
|
|
33
60
|
panicLoops: Infraction[];
|
|
@@ -40,6 +67,7 @@ interface MetricSummary {
|
|
|
40
67
|
archetypeDescription: string;
|
|
41
68
|
skippedLongLines?: number;
|
|
42
69
|
skippedUnknownFiles?: number;
|
|
70
|
+
skippedBinaryFiles?: number;
|
|
43
71
|
}
|
|
44
72
|
interface RoastOptions {
|
|
45
73
|
cwd?: string;
|
|
@@ -78,8 +106,10 @@ interface PrAuditResult {
|
|
|
78
106
|
infractions: Infraction[];
|
|
79
107
|
failedRules: string[];
|
|
80
108
|
passed: boolean;
|
|
109
|
+
complete?: boolean;
|
|
81
110
|
skippedLongLines: number;
|
|
82
111
|
skippedUnknownFiles: number;
|
|
112
|
+
skippedBinaryFiles?: number;
|
|
83
113
|
output: string;
|
|
84
114
|
exitCode: number;
|
|
85
115
|
}
|
|
@@ -88,6 +118,7 @@ interface CheckOptions {
|
|
|
88
118
|
failOn?: string[];
|
|
89
119
|
format?: ReviewFormat;
|
|
90
120
|
baseline?: string;
|
|
121
|
+
ignoreExceptions?: boolean;
|
|
91
122
|
signal?: AbortSignal;
|
|
92
123
|
}
|
|
93
124
|
interface CheckResult {
|
|
@@ -103,6 +134,8 @@ interface CheckResult {
|
|
|
103
134
|
file: string;
|
|
104
135
|
reason: string;
|
|
105
136
|
}[];
|
|
137
|
+
suppressedFindings?: SuppressedFinding[];
|
|
138
|
+
expiredExceptionCount?: number;
|
|
106
139
|
complete: boolean;
|
|
107
140
|
failedRules: string[];
|
|
108
141
|
passed: boolean;
|
|
@@ -116,11 +149,14 @@ interface ReviewOptions {
|
|
|
116
149
|
head?: string;
|
|
117
150
|
failOn?: string[];
|
|
118
151
|
format?: ReviewFormat;
|
|
152
|
+
ignoreExceptions?: boolean;
|
|
119
153
|
signal?: AbortSignal;
|
|
120
154
|
}
|
|
155
|
+
type StagedReviewOptions = Omit<ReviewOptions, "base" | "head">;
|
|
121
156
|
interface ReviewResult {
|
|
122
157
|
schemaVersion: string;
|
|
123
158
|
mode: "review";
|
|
159
|
+
source?: "commits" | "index";
|
|
124
160
|
baseSha: string;
|
|
125
161
|
headSha: string;
|
|
126
162
|
mergeBaseSha: string;
|
|
@@ -142,6 +178,8 @@ interface ReviewResult {
|
|
|
142
178
|
file: string;
|
|
143
179
|
reason: string;
|
|
144
180
|
}[];
|
|
181
|
+
suppressedFindings?: SuppressedFinding[];
|
|
182
|
+
expiredExceptionCount?: number;
|
|
145
183
|
complete: boolean;
|
|
146
184
|
failedRules: string[];
|
|
147
185
|
passed: boolean;
|
|
@@ -163,6 +201,7 @@ interface ParsedFileDiff {
|
|
|
163
201
|
path: string;
|
|
164
202
|
addedLines: ParsedDiffLine[];
|
|
165
203
|
deleted?: boolean;
|
|
204
|
+
binary?: boolean;
|
|
166
205
|
}
|
|
167
206
|
interface ParsedCommit extends CommitInfo {
|
|
168
207
|
files: ParsedFileDiff[];
|
|
@@ -183,6 +222,9 @@ declare class GitError extends Error {
|
|
|
183
222
|
}
|
|
184
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"];
|
|
185
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;
|
|
186
228
|
declare function parseGitLogStream(stream: Readable, onCommit: (commit: ParsedCommit, stats: {
|
|
187
229
|
skippedLongLines: number;
|
|
188
230
|
skippedUnknownFiles: number;
|
|
@@ -240,6 +282,7 @@ declare class MetricsCollector {
|
|
|
240
282
|
private totalCommits;
|
|
241
283
|
private aiCommits;
|
|
242
284
|
private testSkips;
|
|
285
|
+
private focusedTests;
|
|
243
286
|
private typeEscapes;
|
|
244
287
|
private swallowedErrors;
|
|
245
288
|
private panicLoops;
|
|
@@ -247,6 +290,7 @@ declare class MetricsCollector {
|
|
|
247
290
|
private stubAbandonments;
|
|
248
291
|
private skippedLongLines;
|
|
249
292
|
private skippedUnknownFiles;
|
|
293
|
+
private skippedBinaryFiles;
|
|
250
294
|
private activePanicChain;
|
|
251
295
|
private includeAll;
|
|
252
296
|
private ignorePaths?;
|
|
@@ -282,13 +326,17 @@ declare function formatGithubStepSummary(result: PrAuditResult): string;
|
|
|
282
326
|
* Generates rich GitHub PR Comment Markdown (for PR comments / sticky bots)
|
|
283
327
|
*/
|
|
284
328
|
declare function formatPrMarkdownComment(result: PrAuditResult): string;
|
|
329
|
+
declare function terminalText(value: string): string;
|
|
285
330
|
/** Current-tree report emphasizes next actions and explicitly accounts for baseline debt. */
|
|
286
331
|
declare function formatCheckTerminal(result: CheckResult): string;
|
|
287
332
|
declare function formatCheckMarkdown(result: CheckResult): string;
|
|
288
333
|
declare function formatReviewTerminal(result: ReviewResult): string;
|
|
289
334
|
declare function formatReviewMarkdown(result: ReviewResult): string;
|
|
290
335
|
|
|
336
|
+
declare const MAX_CONFIG_BYTES: number;
|
|
291
337
|
interface AgentRoastConfig {
|
|
338
|
+
/** Reviewed, occurrence-limited allowances that expire on a calendar date. */
|
|
339
|
+
exceptions?: FindingException[];
|
|
292
340
|
/** Detector rules to fail CI on in PR mode */
|
|
293
341
|
failOn?: string[];
|
|
294
342
|
/** Glob patterns or prefixes to ignore from analysis */
|
|
@@ -309,9 +357,6 @@ declare class ConfigError extends Error {
|
|
|
309
357
|
declare function configFromText(text: string, source: string): AgentRoastConfig;
|
|
310
358
|
/** Invalid policy must stop the audit instead of silently changing CI behavior. */
|
|
311
359
|
declare function loadConfig(cwd?: string): AgentRoastConfig;
|
|
312
|
-
/**
|
|
313
|
-
* Matches a relative or normalized file path against an array of glob ignore patterns.
|
|
314
|
-
*/
|
|
315
360
|
declare function matchIgnorePath(filePath: string, ignorePatterns?: string[]): boolean;
|
|
316
361
|
|
|
317
362
|
interface InitOptions {
|
|
@@ -361,6 +406,7 @@ declare function compareFindings(before: Infraction[], after: Infraction[], newP
|
|
|
361
406
|
};
|
|
362
407
|
/** Compare full source blobs in changed files without executing or checking out the candidate. */
|
|
363
408
|
declare function reviewChanges(options: ReviewOptions): Promise<ReviewResult>;
|
|
409
|
+
declare function reviewStagedChanges(options?: StagedReviewOptions): Promise<ReviewResult>;
|
|
364
410
|
|
|
365
411
|
/** Offline SARIF 2.1.0, with relative URIs and explicit incomplete-run status.
|
|
366
412
|
* Snippets are omitted to avoid embedding source code in integration artifacts. */
|
|
@@ -404,4 +450,4 @@ declare function roastRepository(options?: RoastOptions): Promise<{
|
|
|
404
450
|
}>;
|
|
405
451
|
declare function auditPullRequest(options: PrAuditOptions): Promise<PrAuditResult>;
|
|
406
452
|
|
|
407
|
-
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, 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, roastRepository, streamGitDiff, streamGitLog, stripStringAndRegexLiterals, unquoteGitPath, writeBaseline };
|
|
453
|
+
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,
|
|
@@ -51,13 +54,16 @@ import {
|
|
|
51
54
|
parseGitLogStream,
|
|
52
55
|
resolveCommitSha,
|
|
53
56
|
reviewChanges,
|
|
57
|
+
reviewStagedChanges,
|
|
54
58
|
roastRepository,
|
|
55
59
|
streamGitDiff,
|
|
56
60
|
streamGitLog,
|
|
57
61
|
stripStringAndRegexLiterals,
|
|
62
|
+
terminalText,
|
|
58
63
|
unquoteGitPath,
|
|
64
|
+
validateExceptions,
|
|
59
65
|
writeBaseline
|
|
60
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-C423TSZF.js";
|
|
61
67
|
export {
|
|
62
68
|
CONFIG_FILENAMES,
|
|
63
69
|
ConfigError,
|
|
@@ -69,12 +75,14 @@ export {
|
|
|
69
75
|
GitResolutionError,
|
|
70
76
|
HERMETIC_GIT_FLAGS,
|
|
71
77
|
InvalidDetectorError,
|
|
78
|
+
MAX_CONFIG_BYTES,
|
|
72
79
|
MetricsCollector,
|
|
73
80
|
PRE_COMMIT_HOOK_TEMPLATE,
|
|
74
81
|
RECORD_SEPARATOR,
|
|
75
82
|
RULE_GUIDES,
|
|
76
83
|
VALID_DETECTORS,
|
|
77
84
|
applyBaseline,
|
|
85
|
+
applyExceptions,
|
|
78
86
|
auditFileDiffs,
|
|
79
87
|
auditPullRequest,
|
|
80
88
|
checkProject,
|
|
@@ -100,6 +108,7 @@ export {
|
|
|
100
108
|
generateSvgBadge,
|
|
101
109
|
getHermeticGitEnv,
|
|
102
110
|
getScoreColor,
|
|
111
|
+
gitOutputPath,
|
|
103
112
|
initProject,
|
|
104
113
|
isCodeFile,
|
|
105
114
|
isTestFile,
|
|
@@ -110,10 +119,13 @@ export {
|
|
|
110
119
|
parseGitLogStream,
|
|
111
120
|
resolveCommitSha,
|
|
112
121
|
reviewChanges,
|
|
122
|
+
reviewStagedChanges,
|
|
113
123
|
roastRepository,
|
|
114
124
|
streamGitDiff,
|
|
115
125
|
streamGitLog,
|
|
116
126
|
stripStringAndRegexLiterals,
|
|
127
|
+
terminalText,
|
|
117
128
|
unquoteGitPath,
|
|
129
|
+
validateExceptions,
|
|
118
130
|
writeBaseline
|
|
119
131
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-roast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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
|
}
|