agent-roast 0.10.1 → 0.12.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 +94 -464
- package/dist/{chunk-FFKEBW6D.js → chunk-AP5Y4CH6.js} +901 -64
- package/dist/cli.js +289 -18
- package/dist/index.d.ts +163 -2
- package/dist/index.js +51 -1
- package/package.json +12 -4
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
AGENT_LABELS,
|
|
3
4
|
ConfigError,
|
|
4
5
|
GitError,
|
|
5
6
|
GitResolutionError,
|
|
7
|
+
HOOK_AGENTS,
|
|
6
8
|
InvalidDetectorError,
|
|
7
9
|
RULE_GUIDES,
|
|
8
10
|
VALID_DETECTORS,
|
|
@@ -14,25 +16,46 @@ import {
|
|
|
14
16
|
generateShieldsMarkdown,
|
|
15
17
|
generateSvgBadge,
|
|
16
18
|
gitOutputPath,
|
|
19
|
+
hookPausedUntil,
|
|
20
|
+
hookStatus,
|
|
17
21
|
initProject,
|
|
22
|
+
installHooks,
|
|
23
|
+
installInstructions,
|
|
18
24
|
loadConfig,
|
|
25
|
+
parseDuration,
|
|
26
|
+
pauseHook,
|
|
19
27
|
readGit,
|
|
28
|
+
replayHistory,
|
|
29
|
+
repositoryRoot,
|
|
30
|
+
resumeHook,
|
|
20
31
|
reviewChanges,
|
|
21
32
|
reviewStagedChanges,
|
|
33
|
+
reviewWorktreeChanges,
|
|
22
34
|
roastRepository,
|
|
35
|
+
runDemo,
|
|
36
|
+
runHook,
|
|
23
37
|
terminalText,
|
|
38
|
+
uninstallHooks,
|
|
24
39
|
writeBaseline
|
|
25
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-AP5Y4CH6.js";
|
|
26
41
|
|
|
27
42
|
// src/cli.ts
|
|
28
43
|
import { Command } from "commander";
|
|
29
44
|
import { createRequire } from "module";
|
|
30
45
|
import * as fs from "fs";
|
|
31
46
|
import * as path from "path";
|
|
47
|
+
import pc from "picocolors";
|
|
32
48
|
var require2 = createRequire(import.meta.url);
|
|
33
49
|
var pkg = require2("../package.json");
|
|
34
50
|
var errorMessage = (error) => terminalText(error instanceof Error ? error.message : String(error));
|
|
35
|
-
|
|
51
|
+
function failOnOption(options) {
|
|
52
|
+
if (options.advisory) {
|
|
53
|
+
if (options.failOn !== void 0) throw new ConfigError("--advisory cannot be combined with --fail-on.");
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
return options.failOn?.split(",").map((rule) => rule.trim()).filter(Boolean);
|
|
57
|
+
}
|
|
58
|
+
async function runContextReview(repoPath, options, source) {
|
|
36
59
|
const controller = new AbortController();
|
|
37
60
|
const abort = () => controller.abort();
|
|
38
61
|
process.once("SIGINT", abort);
|
|
@@ -40,12 +63,12 @@ async function runContextReview(repoPath, options, staged) {
|
|
|
40
63
|
try {
|
|
41
64
|
const common = {
|
|
42
65
|
cwd: repoPath,
|
|
43
|
-
failOn: options
|
|
66
|
+
failOn: failOnOption(options),
|
|
44
67
|
ignoreExceptions: options.ignoreExceptions,
|
|
45
68
|
format: options.json ? "json" : options.format,
|
|
46
69
|
signal: controller.signal
|
|
47
70
|
};
|
|
48
|
-
const result =
|
|
71
|
+
const result = source === "index" ? await reviewStagedChanges(common) : source === "worktree" ? await reviewWorktreeChanges(common) : await reviewChanges({ ...common, base: options.base, head: options.head });
|
|
49
72
|
console.log(result.output);
|
|
50
73
|
process.exitCode = result.exitCode;
|
|
51
74
|
for (const [filename, content] of [
|
|
@@ -70,9 +93,117 @@ removed-count=${result.removedFindings.length}
|
|
|
70
93
|
process.removeListener("SIGTERM", abort);
|
|
71
94
|
}
|
|
72
95
|
}
|
|
96
|
+
function isDefaultInvocation(options) {
|
|
97
|
+
return !(options.all || options.base || options.staged || options.worktree || options.failUnder !== void 0 || options.verbose || options.authors || program.getOptionValueSource("since") === "cli" || program.getOptionValueSource("head") === "cli");
|
|
98
|
+
}
|
|
99
|
+
var NEXT_STEPS = [
|
|
100
|
+
"",
|
|
101
|
+
pc.bold("Next steps"),
|
|
102
|
+
` ${pc.cyan("npx agent-roast hook install")} stop your coding agent from weakening checks (Claude Code, Codex, Cursor)`,
|
|
103
|
+
` ${pc.cyan("npx agent-roast init --ci")} add the pull-request check (GitHub Actions)`,
|
|
104
|
+
` ${pc.cyan("npx agent-roast demo")} see a scripted example`,
|
|
105
|
+
""
|
|
106
|
+
].join("\n");
|
|
107
|
+
async function runDefault(repoPath, options) {
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
const abort = () => controller.abort();
|
|
110
|
+
process.once("SIGINT", abort);
|
|
111
|
+
process.once("SIGTERM", abort);
|
|
112
|
+
try {
|
|
113
|
+
const format = options.json ? "json" : options.format;
|
|
114
|
+
if (!["terminal", "json", "github", "markdown", "sarif"].includes(format)) throw new ConfigError("--format must be terminal, json, github, markdown or sarif.");
|
|
115
|
+
try {
|
|
116
|
+
await readGit(path.resolve(repoPath), ["rev-parse", "--show-toplevel"], controller.signal, 8192);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (!(error instanceof GitError)) throw error;
|
|
119
|
+
console.error(`agent-roast reviews Git repositories, and ${terminalText(path.resolve(repoPath))} is not inside one.
|
|
120
|
+
To see what it does without one: npx agent-roast demo`);
|
|
121
|
+
process.exitCode = 2;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const review = await reviewWorktreeChanges({ cwd: repoPath, failOn: failOnOption(options), ignoreExceptions: options.ignoreExceptions, format, signal: controller.signal });
|
|
125
|
+
if (format === "terminal" && review.filesChanged === 0) {
|
|
126
|
+
console.log(pc.dim("\nNothing is uncommitted. Here is what a pull-request check would have said about your recent commits:"));
|
|
127
|
+
const progress = Boolean(process.stderr.isTTY);
|
|
128
|
+
try {
|
|
129
|
+
const replay = await replayHistory({
|
|
130
|
+
cwd: repoPath,
|
|
131
|
+
last: 50,
|
|
132
|
+
format: "terminal",
|
|
133
|
+
signal: controller.signal,
|
|
134
|
+
onProgress: progress ? (done, total) => process.stderr.write(`\r reviewed ${done}/${total} commits`) : void 0
|
|
135
|
+
});
|
|
136
|
+
if (progress) process.stderr.write("\r\x1B[K");
|
|
137
|
+
console.log(replay.summary.commits ? replay.output.replace(/\s+$/, "") : "\n There is no earlier commit to compare with yet, so there is nothing to replay.");
|
|
138
|
+
console.log(NEXT_STEPS);
|
|
139
|
+
process.exitCode = replay.exitCode;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (progress) process.stderr.write("\r\x1B[K");
|
|
142
|
+
if (controller.signal.aborted) throw error;
|
|
143
|
+
console.log(`
|
|
144
|
+
${errorMessage(error)}
|
|
145
|
+
To see what agent-roast does: npx agent-roast demo
|
|
146
|
+
`);
|
|
147
|
+
process.exitCode = 0;
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
console.log(review.output);
|
|
152
|
+
process.exitCode = review.exitCode;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(`agent-roast error: ${errorMessage(error)}`);
|
|
155
|
+
process.exitCode = error instanceof ConfigError || error instanceof GitError || error instanceof GitResolutionError || error instanceof InvalidDetectorError ? 2 : 1;
|
|
156
|
+
} finally {
|
|
157
|
+
process.removeListener("SIGINT", abort);
|
|
158
|
+
process.removeListener("SIGTERM", abort);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function parseAgents(names) {
|
|
162
|
+
return names.map((name) => {
|
|
163
|
+
if (!HOOK_AGENTS.includes(name.toLowerCase())) throw new ConfigError(`Unknown agent '${name}'. Use ${HOOK_AGENTS.join(", ")}.`);
|
|
164
|
+
return name.toLowerCase();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function readStdin(timeoutMs = 5e3) {
|
|
168
|
+
if (process.stdin.isTTY) return "";
|
|
169
|
+
return new Promise((resolve2) => {
|
|
170
|
+
const chunks = [];
|
|
171
|
+
let settled = false;
|
|
172
|
+
const finish = () => {
|
|
173
|
+
if (settled) return;
|
|
174
|
+
settled = true;
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
process.stdin.pause();
|
|
177
|
+
resolve2(Buffer.concat(chunks).toString("utf8"));
|
|
178
|
+
};
|
|
179
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
180
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
181
|
+
process.stdin.on("end", finish);
|
|
182
|
+
process.stdin.on("error", finish);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function displayPath(file) {
|
|
186
|
+
const relative2 = path.relative(process.cwd(), file);
|
|
187
|
+
if (relative2 && !relative2.startsWith("..") && !path.isAbsolute(relative2)) return relative2;
|
|
188
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
189
|
+
return home && file.startsWith(home) ? "~" + file.slice(home.length) : file;
|
|
190
|
+
}
|
|
73
191
|
var program = new Command();
|
|
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(
|
|
192
|
+
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("--worktree", "Compare HEAD with everything not committed yet: staged, unstaged and untracked files", 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("--advisory", "Report findings without failing the run, the same as an empty --fail-on (setup errors and incomplete scans still fail)", false).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((repoPath, options) => rootAction(repoPath, options));
|
|
193
|
+
async function rootAction(repoPath, options) {
|
|
75
194
|
try {
|
|
195
|
+
if (!options.forceLegacy && isDefaultInvocation(options)) {
|
|
196
|
+
await runDefault(repoPath, options);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (options.worktree && (options.staged || options.base)) throw new ConfigError("--worktree compares HEAD with the working tree; --staged and --base do not apply.");
|
|
200
|
+
if (options.worktree) {
|
|
201
|
+
if (options.all || options.failUnder !== void 0 || program.getOptionValueSource("since") === "cli" || program.getOptionValueSource("head") === "cli") {
|
|
202
|
+
throw new ConfigError("--worktree compares HEAD and the working tree; --all, --since, --head and --fail-under do not apply.");
|
|
203
|
+
}
|
|
204
|
+
await runContextReview(repoPath, options, "worktree");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
76
207
|
if (process.env.GITHUB_EVENT_NAME === "pull_request_target") {
|
|
77
208
|
console.warn(
|
|
78
209
|
"Warning: pull_request_target runs with elevated repository privileges. agent-roast recommends running on 'pull_request'."
|
|
@@ -83,7 +214,7 @@ program.name("agent-roast").description("Check current code, review changes and
|
|
|
83
214
|
if (options.all || options.failUnder !== void 0 || program.getOptionValueSource("since") === "cli" || program.getOptionValueSource("head") === "cli") {
|
|
84
215
|
throw new ConfigError("--staged compares HEAD and index; --all, --since, --head and --fail-under do not apply.");
|
|
85
216
|
}
|
|
86
|
-
await runContextReview(repoPath, options,
|
|
217
|
+
await runContextReview(repoPath, options, "index");
|
|
87
218
|
return;
|
|
88
219
|
}
|
|
89
220
|
if (options.ignoreExceptions) throw new ConfigError("--ignore-exceptions applies to check, review and --staged context checks.");
|
|
@@ -93,8 +224,8 @@ program.name("agent-roast").description("Check current code, review changes and
|
|
|
93
224
|
if (options.failUnder !== void 0 && (options.failUnder.trim() === "" || !Number.isFinite(Number(options.failUnder)) || Number(options.failUnder) < 0 || Number(options.failUnder) > 100)) {
|
|
94
225
|
throw new ConfigError("--fail-under must be a number between 0 and 100.");
|
|
95
226
|
}
|
|
96
|
-
if (!options.base && !options.staged && options.failOn !== void 0) {
|
|
97
|
-
throw new ConfigError("--fail-on requires --base or --staged
|
|
227
|
+
if (!options.base && !options.staged && (options.failOn !== void 0 || options.advisory)) {
|
|
228
|
+
throw new ConfigError(`${options.advisory ? "--advisory" : "--fail-on"} requires --base or --staged.`);
|
|
98
229
|
}
|
|
99
230
|
if (!options.base && !options.staged && !options.json && !["terminal", "json"].includes(options.format)) {
|
|
100
231
|
throw new ConfigError("Repository audits support terminal or json format; use --base or --staged for github/markdown.");
|
|
@@ -102,7 +233,7 @@ program.name("agent-roast").description("Check current code, review changes and
|
|
|
102
233
|
const configRoot = gitOutputPath((await readGit(path.resolve(repoPath), ["rev-parse", "--show-toplevel"])).toString("utf8"));
|
|
103
234
|
const config = loadConfig(configRoot);
|
|
104
235
|
if (options.base || options.staged) {
|
|
105
|
-
const failOnRules = options
|
|
236
|
+
const failOnRules = failOnOption(options) ?? config.failOn;
|
|
106
237
|
const chosenFormat = options.json ? "json" : options.format || "terminal";
|
|
107
238
|
const prResult = await auditPullRequest({
|
|
108
239
|
cwd: repoPath,
|
|
@@ -175,34 +306,69 @@ has-supported-changes=${prResult.hasSupportedChanges}
|
|
|
175
306
|
process.exitCode = 1;
|
|
176
307
|
}
|
|
177
308
|
}
|
|
178
|
-
}
|
|
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
|
|
309
|
+
}
|
|
310
|
+
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, or --worktree for HEAD/everything uncommitted\nOutput: --format terminal|json|github|markdown|sarif, --json, --fail-on <rules>, --advisory\nExamples:\n agent-roast review --base origin/main --format markdown\n agent-roast review --worktree\n agent-roast review --base HEAD~50 --advisory").action(async (repoPath) => {
|
|
180
311
|
try {
|
|
181
312
|
const options = program.opts();
|
|
182
|
-
if (options.
|
|
183
|
-
|
|
313
|
+
if (options.worktree && options.staged) throw new ConfigError("--worktree and --staged cannot be combined.");
|
|
314
|
+
if (options.staged || options.worktree ? options.base || program.getOptionValueSource("head") === "cli" : !options.base) {
|
|
315
|
+
throw new ConfigError("Use review --base <trusted-ref>, review --staged or review --worktree; --base/--head cannot be combined with --staged or --worktree.");
|
|
184
316
|
}
|
|
185
317
|
if (options.all || options.failUnder !== void 0 || program.getOptionValueSource("since") === "cli") {
|
|
186
318
|
throw new ConfigError("review compares source snapshots; --all, --since and --fail-under do not apply.");
|
|
187
319
|
}
|
|
188
|
-
await runContextReview(repoPath, options, options.staged);
|
|
320
|
+
await runContextReview(repoPath, options, options.staged ? "index" : options.worktree ? "worktree" : "commits");
|
|
189
321
|
} catch (error) {
|
|
190
322
|
console.error(`agent-roast review error: ${errorMessage(error)}`);
|
|
191
323
|
process.exitCode = 2;
|
|
192
324
|
}
|
|
193
325
|
});
|
|
194
|
-
program.command("
|
|
326
|
+
program.command("replay").description("Review each of the newest commits on its own, to see what a pull-request check would have said").argument("[path]", "Path to the Git repository", ".").option("--last <n>", "How many of the newest commits to review (default 50, at most 1000)").option("--all-rules", "List line-pattern warnings (type-escape, swallowed-error, stub-abandonment) as well", false).addHelpText("after", "\nOutput: --format terminal|json, --json\nExamples:\n agent-roast replay\n agent-roast replay --last 200 --json").action(async (repoPath, opts) => {
|
|
327
|
+
const controller = new AbortController();
|
|
328
|
+
const abort = () => controller.abort();
|
|
329
|
+
process.once("SIGINT", abort);
|
|
330
|
+
process.once("SIGTERM", abort);
|
|
195
331
|
try {
|
|
196
332
|
const global = program.opts();
|
|
197
|
-
if (global.base || global.staged || global.failUnder !== void 0 || global.all || program.getOptionValueSource("head") === "cli" || program.getOptionValueSource("since") === "cli") {
|
|
198
|
-
throw new ConfigError("
|
|
333
|
+
if (global.base || global.staged || global.worktree || global.failOn !== void 0 || global.advisory || global.failUnder !== void 0 || global.all || global.ignoreExceptions || program.getOptionValueSource("head") === "cli" || program.getOptionValueSource("since") === "cli") {
|
|
334
|
+
throw new ConfigError("replay reviews each commit on its own; --base, --head, --staged, --worktree, --fail-on, --advisory, --fail-under, --all, --since and --ignore-exceptions do not apply.");
|
|
335
|
+
}
|
|
336
|
+
const format = global.json ? "json" : global.format;
|
|
337
|
+
if (format !== "terminal" && format !== "json") throw new ConfigError("replay supports --format terminal or json.");
|
|
338
|
+
const last = opts.last === void 0 ? 50 : Number(opts.last);
|
|
339
|
+
if (!/^\d+$/.test(opts.last ?? "50") || last < 1 || last > 1e3) throw new ConfigError("--last must be a whole number between 1 and 1000.");
|
|
340
|
+
const progress = process.stderr.isTTY && format === "terminal";
|
|
341
|
+
const result = await replayHistory({
|
|
342
|
+
cwd: repoPath,
|
|
343
|
+
last,
|
|
344
|
+
allRules: opts.allRules,
|
|
345
|
+
format,
|
|
346
|
+
signal: controller.signal,
|
|
347
|
+
onProgress: progress ? (done, total) => process.stderr.write(`\r reviewed ${done}/${total} commits`) : void 0
|
|
348
|
+
});
|
|
349
|
+
if (progress) process.stderr.write("\r\x1B[K");
|
|
350
|
+
console.log(result.output);
|
|
351
|
+
process.exitCode = result.exitCode;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error(`agent-roast replay error: ${errorMessage(error)}`);
|
|
354
|
+
process.exitCode = 2;
|
|
355
|
+
} finally {
|
|
356
|
+
process.removeListener("SIGINT", abort);
|
|
357
|
+
process.removeListener("SIGTERM", abort);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
program.command("check").description("Check current tracked and non-ignored untracked source files").argument("[path]", "Path to a git repository", ".").option("--baseline <file>", "Report only findings not accepted in this baseline").option("--write-baseline <file>", "Save current findings for adoption (refuses to overwrite)").addHelpText("after", "\nOutput options: --format terminal|json|github|markdown|sarif, --json, --fail-on <rules>, --advisory\nExamples:\n agent-roast check\n agent-roast check --write-baseline .agentroast-baseline.json\n agent-roast check --baseline .agentroast-baseline.json --format json").action(async (repoPath, opts) => {
|
|
361
|
+
try {
|
|
362
|
+
const global = program.opts();
|
|
363
|
+
if (global.base || global.staged || global.worktree || global.failUnder !== void 0 || global.all || program.getOptionValueSource("head") === "cli" || program.getOptionValueSource("since") === "cli") {
|
|
364
|
+
throw new ConfigError("check inspects current files; history, --base, --head, --staged, --worktree and --fail-under options do not apply.");
|
|
199
365
|
}
|
|
200
366
|
if (opts.baseline && opts.writeBaseline) throw new ConfigError("--baseline and --write-baseline cannot be combined.");
|
|
201
367
|
const result = await checkProject({
|
|
202
368
|
cwd: repoPath,
|
|
203
369
|
baseline: opts.baseline,
|
|
204
370
|
ignoreExceptions: global.ignoreExceptions,
|
|
205
|
-
failOn: global
|
|
371
|
+
failOn: failOnOption(global),
|
|
206
372
|
format: global.json ? "json" : global.format
|
|
207
373
|
});
|
|
208
374
|
if (opts.writeBaseline) {
|
|
@@ -319,4 +485,109 @@ ${markdown}
|
|
|
319
485
|
process.exitCode = err instanceof ConfigError || err instanceof GitError || err instanceof GitResolutionError ? 2 : 1;
|
|
320
486
|
}
|
|
321
487
|
});
|
|
488
|
+
program.command("demo").description('See what agent-roast reports for a scripted "fix" that weakens the checks (a temporary repository; nothing of yours is read)').action(async () => {
|
|
489
|
+
const controller = new AbortController();
|
|
490
|
+
const abort = () => controller.abort();
|
|
491
|
+
process.once("SIGINT", abort);
|
|
492
|
+
process.once("SIGTERM", abort);
|
|
493
|
+
try {
|
|
494
|
+
if (program.opts().worktree || program.opts().staged || program.opts().base) throw new ConfigError("demo takes no review options.");
|
|
495
|
+
const demo = await runDemo({ signal: controller.signal });
|
|
496
|
+
console.log(demo.output);
|
|
497
|
+
process.exitCode = demo.exitCode;
|
|
498
|
+
} catch (error) {
|
|
499
|
+
console.error(`agent-roast demo error: ${errorMessage(error)}`);
|
|
500
|
+
process.exitCode = 2;
|
|
501
|
+
} finally {
|
|
502
|
+
process.removeListener("SIGINT", abort);
|
|
503
|
+
process.removeListener("SIGTERM", abort);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
program.command("audit").description("Score the Git history by the shortcuts it adds (the original agent-roast report)").argument("[path]", "Path to git repository (default: current directory)", ".").action((repoPath) => rootAction(repoPath, { ...program.opts(), forceLegacy: true }));
|
|
507
|
+
var hook = program.command("hook").description("Guard a coding agent: send it back to fix the code when it weakens a check (Claude Code, Codex, Cursor)");
|
|
508
|
+
hook.command("run").description("Run by the agent when it is about to finish; reads the agent's JSON on stdin and answers in its dialect").option("--agent <agent>", `${HOOK_AGENTS.join(", ")} (detected from the input when omitted)`).action(async (opts) => {
|
|
509
|
+
let agent;
|
|
510
|
+
try {
|
|
511
|
+
agent = opts.agent === void 0 ? void 0 : parseAgents([opts.agent])[0];
|
|
512
|
+
} catch (error) {
|
|
513
|
+
console.error(`agent-roast hook: ${errorMessage(error)}`);
|
|
514
|
+
process.exitCode = 1;
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
const outcome = await runHook(await readStdin(), { agent });
|
|
519
|
+
if (outcome.stdout) process.stdout.write(outcome.stdout);
|
|
520
|
+
if (outcome.stderr) process.stderr.write(outcome.stderr);
|
|
521
|
+
process.exitCode = outcome.exitCode;
|
|
522
|
+
} catch (error) {
|
|
523
|
+
console.error(`agent-roast hook: ${errorMessage(error)}`);
|
|
524
|
+
process.exitCode = 0;
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
hook.command("install").description("Add the hook to Claude Code, Codex or Cursor in this repository").argument("[agents...]", `${HOOK_AGENTS.join(", ")} (default: the agents that already have a folder in this repository)`).option("--global", "install for every repository of this user instead", false).option("--local", "Claude Code only: use .claude/settings.local.json, which stays out of Git", false).option("--command <program>", "program to run instead of 'npx --yes agent-roast@<version>', for example 'agent-roast' when installed globally").option("--instructions", "also write a short 'keep the checks honest' paragraph into AGENTS.md / CLAUDE.md", false).option("--dry-run", "show what would change without writing anything", false).action((names, opts) => {
|
|
528
|
+
try {
|
|
529
|
+
if (opts.global && opts.local) throw new ConfigError("Choose --global or --local, not both.");
|
|
530
|
+
let agents = parseAgents(names);
|
|
531
|
+
const root = repositoryRoot(process.cwd());
|
|
532
|
+
if (!agents.length) agents = HOOK_AGENTS.filter((agent) => fs.existsSync(path.join(root, { claude: ".claude", codex: ".codex", cursor: ".cursor" }[agent])));
|
|
533
|
+
if (!agents.length) throw new ConfigError("Name the agent to guard: agent-roast hook install claude (or codex, cursor).");
|
|
534
|
+
const scope = opts.global ? "global" : opts.local ? "local" : "project";
|
|
535
|
+
const changes = installHooks({ cwd: process.cwd(), agents, scope, command: opts.command, dryRun: opts.dryRun });
|
|
536
|
+
console.log(`${opts.dryRun ? "Would install" : "Installed"} the agent-roast hook (${scope}):`);
|
|
537
|
+
for (const change of changes) console.log(` ${change.action === "unchanged" ? "=" : "+"} ${AGENT_LABELS[change.agent]}: ${displayPath(change.file)} (${change.action})
|
|
538
|
+
${change.command}`);
|
|
539
|
+
if (opts.instructions) {
|
|
540
|
+
for (const change of installInstructions({ cwd: process.cwd(), dryRun: opts.dryRun })) console.log(` + ${displayPath(change.file)} (${change.action}): keep-the-checks-honest paragraph`);
|
|
541
|
+
}
|
|
542
|
+
if (agents.includes("codex")) console.log("\nCodex runs a new hook only after you trust it: open Codex and review it with /hooks.");
|
|
543
|
+
if (!opts.dryRun) console.log("\nWhen the agent is about to finish with weakened checks it is sent back once to fix the code.\nTurn it off for a while: agent-roast hook pause \xB7 remove it: agent-roast hook uninstall");
|
|
544
|
+
} catch (error) {
|
|
545
|
+
console.error(`agent-roast hook install error: ${errorMessage(error)}`);
|
|
546
|
+
process.exitCode = 2;
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
hook.command("uninstall").description("Remove the hook from Claude Code, Codex or Cursor").argument("[agents...]", `${HOOK_AGENTS.join(", ")} (default: all)`).option("--global", "remove it from the user-level files", false).option("--local", "Claude Code only: remove it from .claude/settings.local.json", false).option("--dry-run", "show what would change without writing anything", false).action((names, opts) => {
|
|
550
|
+
try {
|
|
551
|
+
if (opts.global && opts.local) throw new ConfigError("Choose --global or --local, not both.");
|
|
552
|
+
const agents = names.length ? parseAgents(names) : [...HOOK_AGENTS].filter((agent) => !(opts.local && agent !== "claude"));
|
|
553
|
+
const scope = opts.global ? "global" : opts.local ? "local" : "project";
|
|
554
|
+
for (const change of uninstallHooks({ cwd: process.cwd(), agents, scope, dryRun: opts.dryRun })) {
|
|
555
|
+
console.log(` ${change.action === "removed" ? "-" : "="} ${AGENT_LABELS[change.agent]}: ${displayPath(change.file)} (${change.action === "removed" ? opts.dryRun ? "would be removed" : "removed" : "no agent-roast hook there"})`);
|
|
556
|
+
}
|
|
557
|
+
} catch (error) {
|
|
558
|
+
console.error(`agent-roast hook uninstall error: ${errorMessage(error)}`);
|
|
559
|
+
process.exitCode = 2;
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
hook.command("status").description("Show where the hook is installed").action(async () => {
|
|
563
|
+
try {
|
|
564
|
+
const rows = hookStatus({ cwd: process.cwd() });
|
|
565
|
+
for (const row of rows) console.log(` ${row.installed ? pc.green("installed") : pc.dim("not set ")} ${AGENT_LABELS[row.agent].padEnd(11)} ${row.scope.padEnd(8)} ${displayPath(row.file)}`);
|
|
566
|
+
const paused = await hookPausedUntil(process.cwd());
|
|
567
|
+
if (paused) console.log(`
|
|
568
|
+
Paused in this repository until ${paused.toLocaleString()} (agent-roast hook resume).`);
|
|
569
|
+
if (!rows.some((row) => row.installed)) console.log("\n Install it with: agent-roast hook install claude (or codex, cursor)");
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.error(`agent-roast hook status error: ${errorMessage(error)}`);
|
|
572
|
+
process.exitCode = 2;
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
hook.command("pause").description("Let the agent finish without the check in this repository for a while (for a change you have approved)").option("--for <duration>", "how long: 30m, 2h, 1d", "4h").action(async (opts) => {
|
|
576
|
+
try {
|
|
577
|
+
const until = new Date(Date.now() + parseDuration(opts.for));
|
|
578
|
+
await pauseHook(process.cwd(), until);
|
|
579
|
+
console.log(`The hook is paused in this repository until ${until.toLocaleString()}. Resume now: agent-roast hook resume`);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
console.error(`agent-roast hook pause error: ${errorMessage(error)}`);
|
|
582
|
+
process.exitCode = 2;
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
hook.command("resume").description("End a pause").action(async () => {
|
|
586
|
+
try {
|
|
587
|
+
console.log(await resumeHook(process.cwd()) ? "The hook is active again." : "The hook was not paused.");
|
|
588
|
+
} catch (error) {
|
|
589
|
+
console.error(`agent-roast hook resume error: ${errorMessage(error)}`);
|
|
590
|
+
process.exitCode = 2;
|
|
591
|
+
}
|
|
592
|
+
});
|
|
322
593
|
program.parse(process.argv);
|
package/dist/index.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ type StagedReviewOptions = Omit<ReviewOptions, "base" | "head">;
|
|
|
156
156
|
interface ReviewResult {
|
|
157
157
|
schemaVersion: string;
|
|
158
158
|
mode: "review";
|
|
159
|
-
source?: "commits" | "index";
|
|
159
|
+
source?: "commits" | "index" | "worktree";
|
|
160
160
|
baseSha: string;
|
|
161
161
|
headSha: string;
|
|
162
162
|
mergeBaseSha: string;
|
|
@@ -317,6 +317,15 @@ declare function formatTerminalCard(summary: MetricSummary, options: {
|
|
|
317
317
|
verbose?: boolean;
|
|
318
318
|
}): string;
|
|
319
319
|
declare function formatPrTerminalCard(result: PrAuditResult): string;
|
|
320
|
+
/** Rules about the checks themselves (tests, assertions, quality gates), as opposed to line patterns such as `as any`. */
|
|
321
|
+
declare function isCheckRule(type: string): boolean;
|
|
322
|
+
/**
|
|
323
|
+
* Puts the findings that matter first, so a long list of warnings cannot push them below a
|
|
324
|
+
* display cap. The order inside each rank is unchanged.
|
|
325
|
+
*/
|
|
326
|
+
declare function mostImportantFirst<T extends {
|
|
327
|
+
type: string;
|
|
328
|
+
}>(findings: readonly T[], failedRules: readonly string[]): T[];
|
|
320
329
|
declare function formatGithubAnnotations(result: Pick<PrAuditResult, "infractions" | "failedRules">): string;
|
|
321
330
|
/**
|
|
322
331
|
* Generates GitHub Step Summary Markdown
|
|
@@ -409,6 +418,158 @@ declare function compareFindings(before: Infraction[], after: Infraction[], newP
|
|
|
409
418
|
/** Compare full source blobs in changed files without executing or checking out the candidate. */
|
|
410
419
|
declare function reviewChanges(options: ReviewOptions): Promise<ReviewResult>;
|
|
411
420
|
declare function reviewStagedChanges(options?: StagedReviewOptions): Promise<ReviewResult>;
|
|
421
|
+
/** Everything uncommitted (staged, unstaged and untracked files) against HEAD, under the policy committed at HEAD. */
|
|
422
|
+
declare function reviewWorktreeChanges(options?: StagedReviewOptions): Promise<ReviewResult>;
|
|
423
|
+
|
|
424
|
+
type ReplayStatus = "blocked" | "warned" | "noted" | "clean" | "untouched" | "incomplete" | "failed";
|
|
425
|
+
interface ReplayCommit {
|
|
426
|
+
sha: string;
|
|
427
|
+
date: string;
|
|
428
|
+
subject: string;
|
|
429
|
+
/** blocked: a rule that blocks by default fired; warned: a check rule fired without blocking; noted: only line patterns. */
|
|
430
|
+
status: ReplayStatus;
|
|
431
|
+
failedRules: string[];
|
|
432
|
+
findings: Infraction[];
|
|
433
|
+
filesInspected: number;
|
|
434
|
+
skipped: {
|
|
435
|
+
file: string;
|
|
436
|
+
reason: string;
|
|
437
|
+
}[];
|
|
438
|
+
error?: string;
|
|
439
|
+
}
|
|
440
|
+
interface ReplayOptions {
|
|
441
|
+
cwd?: string;
|
|
442
|
+
/** How many of the newest commits to review (default 50). */
|
|
443
|
+
last?: number;
|
|
444
|
+
/** List the line-pattern findings (type-escape, swallowed-error, ...) as well as the check rules. */
|
|
445
|
+
allRules?: boolean;
|
|
446
|
+
format?: "terminal" | "json";
|
|
447
|
+
signal?: AbortSignal;
|
|
448
|
+
onProgress?: (done: number, total: number) => void;
|
|
449
|
+
}
|
|
450
|
+
interface ReplayResult {
|
|
451
|
+
schemaVersion: string;
|
|
452
|
+
mode: "replay";
|
|
453
|
+
head: string;
|
|
454
|
+
requested: number;
|
|
455
|
+
/** Commits without a parent (the first commit, or the edge of a shallow clone) have nothing to compare with. */
|
|
456
|
+
commitsWithoutParent: number;
|
|
457
|
+
/** A shallow clone holds only part of the history, so fewer commits than requested may have been replayed. */
|
|
458
|
+
shallow: boolean;
|
|
459
|
+
summary: Record<ReplayStatus | "commits", number> & {
|
|
460
|
+
findingsByRule: Record<string, number>;
|
|
461
|
+
};
|
|
462
|
+
commits: ReplayCommit[];
|
|
463
|
+
exitCode: number;
|
|
464
|
+
output: string;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Reviews each of the newest commits of the first-parent history on its own against its parent, which is what a
|
|
468
|
+
* pull-request check would have seen: one change per squash, merge or direct commit. Reads Git objects only.
|
|
469
|
+
*/
|
|
470
|
+
declare function replayHistory(options?: ReplayOptions): Promise<ReplayResult>;
|
|
471
|
+
declare function formatReplayTerminal(result: ReplayResult, allRules?: boolean): string;
|
|
472
|
+
|
|
473
|
+
/** Coding agents that can run a command when they are about to finish and feed its answer back. */
|
|
474
|
+
type HookAgent = "claude" | "codex" | "cursor";
|
|
475
|
+
declare const HOOK_AGENTS: readonly HookAgent[];
|
|
476
|
+
interface HookOptions {
|
|
477
|
+
/** Which agent is calling. Detected from its input when omitted. */
|
|
478
|
+
agent?: HookAgent;
|
|
479
|
+
/** Repository to review. Defaults to the directory the agent reports. */
|
|
480
|
+
cwd?: string;
|
|
481
|
+
env?: NodeJS.ProcessEnv;
|
|
482
|
+
/** The agent is waiting, so a slow review gives up and lets it finish. */
|
|
483
|
+
timeoutMs?: number;
|
|
484
|
+
now?: Date;
|
|
485
|
+
}
|
|
486
|
+
interface HookOutcome {
|
|
487
|
+
/** pass: nothing to say; block: the agent was sent back; warn: the user is told, the agent is not; skip: nothing could be checked. */
|
|
488
|
+
action: "pass" | "block" | "warn" | "skip";
|
|
489
|
+
stdout: string;
|
|
490
|
+
stderr: string;
|
|
491
|
+
exitCode: number;
|
|
492
|
+
result?: ReviewResult;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* What the agent reads when it is sent back: the offending changes, then the instruction to fix the code rather than the
|
|
496
|
+
* checks. Short on purpose, since it is added to the agent's context.
|
|
497
|
+
*/
|
|
498
|
+
declare function formatAgentFeedback(result: ReviewResult): string;
|
|
499
|
+
/** The time a pause lasts until, or null when the hook is active. Expired pauses are cleaned up. */
|
|
500
|
+
declare function hookPausedUntil(cwd: string, now?: Date): Promise<Date | null>;
|
|
501
|
+
declare function pauseHook(cwd: string, until: Date): Promise<string>;
|
|
502
|
+
declare function resumeHook(cwd: string): Promise<boolean>;
|
|
503
|
+
/** "30m", "2h", "1d": how long a pause lasts. */
|
|
504
|
+
declare function parseDuration(text: string): number;
|
|
505
|
+
/**
|
|
506
|
+
* The command an agent runs when it is about to finish. It reviews everything uncommitted against HEAD and, when that
|
|
507
|
+
* weakens the checks, sends the agent back once with a plain account of what to undo. It never fails the agent's run:
|
|
508
|
+
* anything it cannot check lets the agent finish, and an agent that already had its chance is not blocked a second time,
|
|
509
|
+
* so the person decides instead of the two looping.
|
|
510
|
+
*/
|
|
511
|
+
declare function runHook(rawInput: string, options?: HookOptions): Promise<HookOutcome>;
|
|
512
|
+
|
|
513
|
+
/** project: shared with the team (committed); local: this checkout only (Claude Code); global: every repository of this user. */
|
|
514
|
+
type HookScope = "project" | "local" | "global";
|
|
515
|
+
interface HookInstallOptions {
|
|
516
|
+
/** Anywhere inside the repository. */
|
|
517
|
+
cwd?: string;
|
|
518
|
+
agents: readonly HookAgent[];
|
|
519
|
+
scope?: HookScope;
|
|
520
|
+
dryRun?: boolean;
|
|
521
|
+
/** Replaces `npx --yes agent-roast@<version>`, for example `agent-roast` when it is installed globally. */
|
|
522
|
+
command?: string;
|
|
523
|
+
home?: string;
|
|
524
|
+
env?: NodeJS.ProcessEnv;
|
|
525
|
+
}
|
|
526
|
+
interface HookInstallChange {
|
|
527
|
+
agent: HookAgent;
|
|
528
|
+
scope: HookScope;
|
|
529
|
+
file: string;
|
|
530
|
+
action: "created" | "updated" | "unchanged" | "removed" | "absent";
|
|
531
|
+
/** The command line the agent runs. */
|
|
532
|
+
command?: string;
|
|
533
|
+
}
|
|
534
|
+
declare const AGENT_LABELS: Record<HookAgent, string>;
|
|
535
|
+
/** Version of this package, which the hook pins so an agent never runs unreviewed newer code. */
|
|
536
|
+
declare function packageVersion(): string;
|
|
537
|
+
/** The command an agent runs for this agent's Stop event. */
|
|
538
|
+
declare function hookCommand(agent: HookAgent, command?: string): string;
|
|
539
|
+
/** The top level of the repository containing `cwd`, or `cwd` itself outside a repository. */
|
|
540
|
+
declare function repositoryRoot(cwd: string): string;
|
|
541
|
+
/** Where an agent reads its hooks from. */
|
|
542
|
+
declare function hookConfigPath(agent: HookAgent, scope: HookScope, root: string, home: string, env?: NodeJS.ProcessEnv): string;
|
|
543
|
+
declare function installHooks(options: HookInstallOptions): HookInstallChange[];
|
|
544
|
+
declare function uninstallHooks(options: Omit<HookInstallOptions, "command">): HookInstallChange[];
|
|
545
|
+
/** Where the hook is installed right now, for every agent and scope that can hold one. */
|
|
546
|
+
declare function hookStatus(options: Pick<HookInstallOptions, "cwd" | "home" | "env">): (HookInstallChange & {
|
|
547
|
+
installed: boolean;
|
|
548
|
+
})[];
|
|
549
|
+
declare const INSTRUCTIONS_START = "<!-- agent-roast:start -->";
|
|
550
|
+
declare const INSTRUCTIONS_END = "<!-- agent-roast:end -->";
|
|
551
|
+
declare const INSTRUCTIONS_BLOCK = "<!-- agent-roast:start -->\n## Keep the checks honest\n\nWhen a test, type check, linter or CI step fails, fix the code. Do not make the build green by skipping or deleting tests,\nweakening assertions, lowering coverage thresholds, switching off `strict`, or letting a CI step fail. If you think a check\nis wrong, say so and ask before changing it. Before you finish, run `npx agent-roast review --worktree`: it lists uncommitted\nchanges that weaken the checks.\n<!-- agent-roast:end -->\n";
|
|
552
|
+
/** Writes the guidance into AGENTS.md and CLAUDE.md (whichever exist; AGENTS.md is created when neither does). Idempotent. */
|
|
553
|
+
declare function installInstructions(options?: {
|
|
554
|
+
cwd?: string;
|
|
555
|
+
dryRun?: boolean;
|
|
556
|
+
}): {
|
|
557
|
+
file: string;
|
|
558
|
+
action: "created" | "updated" | "unchanged";
|
|
559
|
+
}[];
|
|
560
|
+
|
|
561
|
+
interface DemoResult {
|
|
562
|
+
exitCode: number;
|
|
563
|
+
output: string;
|
|
564
|
+
result: ReviewResult;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* A scripted example in a throwaway repository: a project with passing tests, then the kind of "fix" that turns a red build
|
|
568
|
+
* green by changing the checks. The review is the real one; nothing of the user's is read, and the repository is deleted.
|
|
569
|
+
*/
|
|
570
|
+
declare function runDemo(options?: {
|
|
571
|
+
signal?: AbortSignal;
|
|
572
|
+
}): Promise<DemoResult>;
|
|
412
573
|
|
|
413
574
|
/** Offline SARIF 2.1.0, with relative URIs and explicit incomplete-run status.
|
|
414
575
|
* Snippets are omitted to avoid embedding source code in integration artifacts. */
|
|
@@ -452,4 +613,4 @@ declare function roastRepository(options?: RoastOptions): Promise<{
|
|
|
452
613
|
}>;
|
|
453
614
|
declare function auditPullRequest(options: PrAuditOptions): Promise<PrAuditResult>;
|
|
454
615
|
|
|
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 };
|
|
616
|
+
export { AGENT_LABELS, type AgentRoastConfig, type AiAttributionResult, CONFIG_FILENAMES, type CheckOptions, type CheckResult, type CommitInfo, ConfigError, DEFAULT_CONFIG, DEFAULT_CONFIG_TEMPLATE, type DemoResult, type DetectorType, FIELD_SEPARATOR, type FindingBaseline, type FindingException, GITHUB_WORKFLOW_TEMPLATE, GitError, GitResolutionError, HERMETIC_GIT_FLAGS, HOOK_AGENTS, type HookAgent, type HookInstallChange, type HookInstallOptions, type HookOptions, type HookOutcome, type HookScope, INSTRUCTIONS_BLOCK, INSTRUCTIONS_END, INSTRUCTIONS_START, 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 ReplayCommit, type ReplayOptions, type ReplayResult, type ReplayStatus, 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, formatAgentFeedback, formatCheckMarkdown, formatCheckTerminal, formatGithubAnnotations, formatGithubStepSummary, formatPrMarkdownComment, formatPrTerminalCard, formatReplayTerminal, formatReviewMarkdown, formatReviewTerminal, formatSarif, formatTerminalCard, generateShareUrl, generateShieldsJson, generateShieldsMarkdown, generateSvgBadge, getHermeticGitEnv, getScoreColor, gitOutputPath, hookCommand, hookConfigPath, hookPausedUntil, hookStatus, initProject, installHooks, installInstructions, isCheckRule, isCodeFile, isTestFile, loadBaseline, loadConfig, matchIgnorePath, mostImportantFirst, packageVersion, parseDiff, parseDuration, parseGitLogStream, pauseHook, replayHistory, repositoryRoot, resolveCommitSha, resumeHook, reviewChanges, reviewStagedChanges, reviewWorktreeChanges, roastRepository, runDemo, runHook, streamGitDiff, streamGitLog, stripStringAndRegexLiterals, terminalText, uninstallHooks, unquoteGitPath, validateExceptions, writeBaseline };
|