agent-roast 0.11.0 → 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 +97 -492
- package/dist/{chunk-WMHB65AL.js → chunk-AP5Y4CH6.js} +712 -63
- package/dist/cli.js +243 -14
- package/dist/index.d.ts +105 -2
- package/dist/index.js +43 -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,22 +16,35 @@ 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,
|
|
20
28
|
replayHistory,
|
|
29
|
+
repositoryRoot,
|
|
30
|
+
resumeHook,
|
|
21
31
|
reviewChanges,
|
|
22
32
|
reviewStagedChanges,
|
|
33
|
+
reviewWorktreeChanges,
|
|
23
34
|
roastRepository,
|
|
35
|
+
runDemo,
|
|
36
|
+
runHook,
|
|
24
37
|
terminalText,
|
|
38
|
+
uninstallHooks,
|
|
25
39
|
writeBaseline
|
|
26
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-AP5Y4CH6.js";
|
|
27
41
|
|
|
28
42
|
// src/cli.ts
|
|
29
43
|
import { Command } from "commander";
|
|
30
44
|
import { createRequire } from "module";
|
|
31
45
|
import * as fs from "fs";
|
|
32
46
|
import * as path from "path";
|
|
47
|
+
import pc from "picocolors";
|
|
33
48
|
var require2 = createRequire(import.meta.url);
|
|
34
49
|
var pkg = require2("../package.json");
|
|
35
50
|
var errorMessage = (error) => terminalText(error instanceof Error ? error.message : String(error));
|
|
@@ -40,7 +55,7 @@ function failOnOption(options) {
|
|
|
40
55
|
}
|
|
41
56
|
return options.failOn?.split(",").map((rule) => rule.trim()).filter(Boolean);
|
|
42
57
|
}
|
|
43
|
-
async function runContextReview(repoPath, options,
|
|
58
|
+
async function runContextReview(repoPath, options, source) {
|
|
44
59
|
const controller = new AbortController();
|
|
45
60
|
const abort = () => controller.abort();
|
|
46
61
|
process.once("SIGINT", abort);
|
|
@@ -53,7 +68,7 @@ async function runContextReview(repoPath, options, staged) {
|
|
|
53
68
|
format: options.json ? "json" : options.format,
|
|
54
69
|
signal: controller.signal
|
|
55
70
|
};
|
|
56
|
-
const result =
|
|
71
|
+
const result = source === "index" ? await reviewStagedChanges(common) : source === "worktree" ? await reviewWorktreeChanges(common) : await reviewChanges({ ...common, base: options.base, head: options.head });
|
|
57
72
|
console.log(result.output);
|
|
58
73
|
process.exitCode = result.exitCode;
|
|
59
74
|
for (const [filename, content] of [
|
|
@@ -78,9 +93,117 @@ removed-count=${result.removedFindings.length}
|
|
|
78
93
|
process.removeListener("SIGTERM", abort);
|
|
79
94
|
}
|
|
80
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
|
+
}
|
|
81
191
|
var program = new Command();
|
|
82
|
-
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("--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(
|
|
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) {
|
|
83
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
|
+
}
|
|
84
207
|
if (process.env.GITHUB_EVENT_NAME === "pull_request_target") {
|
|
85
208
|
console.warn(
|
|
86
209
|
"Warning: pull_request_target runs with elevated repository privileges. agent-roast recommends running on 'pull_request'."
|
|
@@ -91,7 +214,7 @@ program.name("agent-roast").description("Check current code, review changes and
|
|
|
91
214
|
if (options.all || options.failUnder !== void 0 || program.getOptionValueSource("since") === "cli" || program.getOptionValueSource("head") === "cli") {
|
|
92
215
|
throw new ConfigError("--staged compares HEAD and index; --all, --since, --head and --fail-under do not apply.");
|
|
93
216
|
}
|
|
94
|
-
await runContextReview(repoPath, options,
|
|
217
|
+
await runContextReview(repoPath, options, "index");
|
|
95
218
|
return;
|
|
96
219
|
}
|
|
97
220
|
if (options.ignoreExceptions) throw new ConfigError("--ignore-exceptions applies to check, review and --staged context checks.");
|
|
@@ -183,17 +306,18 @@ has-supported-changes=${prResult.hasSupportedChanges}
|
|
|
183
306
|
process.exitCode = 1;
|
|
184
307
|
}
|
|
185
308
|
}
|
|
186
|
-
}
|
|
187
|
-
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>, --advisory\nExamples:\n agent-roast review --base origin/main --format markdown\n agent-roast review --base HEAD~50 --advisory").action(async (repoPath) => {
|
|
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) => {
|
|
188
311
|
try {
|
|
189
312
|
const options = program.opts();
|
|
190
|
-
if (options.
|
|
191
|
-
|
|
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.");
|
|
192
316
|
}
|
|
193
317
|
if (options.all || options.failUnder !== void 0 || program.getOptionValueSource("since") === "cli") {
|
|
194
318
|
throw new ConfigError("review compares source snapshots; --all, --since and --fail-under do not apply.");
|
|
195
319
|
}
|
|
196
|
-
await runContextReview(repoPath, options, options.staged);
|
|
320
|
+
await runContextReview(repoPath, options, options.staged ? "index" : options.worktree ? "worktree" : "commits");
|
|
197
321
|
} catch (error) {
|
|
198
322
|
console.error(`agent-roast review error: ${errorMessage(error)}`);
|
|
199
323
|
process.exitCode = 2;
|
|
@@ -206,8 +330,8 @@ program.command("replay").description("Review each of the newest commits on its
|
|
|
206
330
|
process.once("SIGTERM", abort);
|
|
207
331
|
try {
|
|
208
332
|
const global = program.opts();
|
|
209
|
-
if (global.base || global.staged || global.failOn !== void 0 || global.advisory || global.failUnder !== void 0 || global.all || global.ignoreExceptions || program.getOptionValueSource("head") === "cli" || program.getOptionValueSource("since") === "cli") {
|
|
210
|
-
throw new ConfigError("replay reviews each commit on its own; --base, --head, --staged, --fail-on, --advisory, --fail-under, --all, --since and --ignore-exceptions do not apply.");
|
|
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.");
|
|
211
335
|
}
|
|
212
336
|
const format = global.json ? "json" : global.format;
|
|
213
337
|
if (format !== "terminal" && format !== "json") throw new ConfigError("replay supports --format terminal or json.");
|
|
@@ -236,8 +360,8 @@ program.command("replay").description("Review each of the newest commits on its
|
|
|
236
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) => {
|
|
237
361
|
try {
|
|
238
362
|
const global = program.opts();
|
|
239
|
-
if (global.base || global.staged || global.failUnder !== void 0 || global.all || program.getOptionValueSource("head") === "cli" || program.getOptionValueSource("since") === "cli") {
|
|
240
|
-
throw new ConfigError("check inspects current files; history, --base, --head, --staged and --fail-under options do not apply.");
|
|
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.");
|
|
241
365
|
}
|
|
242
366
|
if (opts.baseline && opts.writeBaseline) throw new ConfigError("--baseline and --write-baseline cannot be combined.");
|
|
243
367
|
const result = await checkProject({
|
|
@@ -361,4 +485,109 @@ ${markdown}
|
|
|
361
485
|
process.exitCode = err instanceof ConfigError || err instanceof GitError || err instanceof GitResolutionError ? 2 : 1;
|
|
362
486
|
}
|
|
363
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
|
+
});
|
|
364
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;
|
|
@@ -418,6 +418,8 @@ declare function compareFindings(before: Infraction[], after: Infraction[], newP
|
|
|
418
418
|
/** Compare full source blobs in changed files without executing or checking out the candidate. */
|
|
419
419
|
declare function reviewChanges(options: ReviewOptions): Promise<ReviewResult>;
|
|
420
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>;
|
|
421
423
|
|
|
422
424
|
type ReplayStatus = "blocked" | "warned" | "noted" | "clean" | "untouched" | "incomplete" | "failed";
|
|
423
425
|
interface ReplayCommit {
|
|
@@ -468,6 +470,107 @@ interface ReplayResult {
|
|
|
468
470
|
declare function replayHistory(options?: ReplayOptions): Promise<ReplayResult>;
|
|
469
471
|
declare function formatReplayTerminal(result: ReplayResult, allRules?: boolean): string;
|
|
470
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>;
|
|
573
|
+
|
|
471
574
|
/** Offline SARIF 2.1.0, with relative URIs and explicit incomplete-run status.
|
|
472
575
|
* Snippets are omitted to avoid embedding source code in integration artifacts. */
|
|
473
576
|
declare function formatSarif(report: CheckResult | ReviewResult): string;
|
|
@@ -510,4 +613,4 @@ declare function roastRepository(options?: RoastOptions): Promise<{
|
|
|
510
613
|
}>;
|
|
511
614
|
declare function auditPullRequest(options: PrAuditOptions): Promise<PrAuditResult>;
|
|
512
615
|
|
|
513
|
-
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 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, formatCheckMarkdown, formatCheckTerminal, formatGithubAnnotations, formatGithubStepSummary, formatPrMarkdownComment, formatPrTerminalCard, formatReplayTerminal, formatReviewMarkdown, formatReviewTerminal, formatSarif, formatTerminalCard, generateShareUrl, generateShieldsJson, generateShieldsMarkdown, generateSvgBadge, getHermeticGitEnv, getScoreColor, gitOutputPath, initProject, isCheckRule, isCodeFile, isTestFile, loadBaseline, loadConfig, matchIgnorePath, mostImportantFirst, parseDiff, parseGitLogStream, replayHistory, 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 };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
AGENT_LABELS,
|
|
3
4
|
CONFIG_FILENAMES,
|
|
4
5
|
ConfigError,
|
|
5
6
|
DEFAULT_CONFIG,
|
|
@@ -9,6 +10,10 @@ import {
|
|
|
9
10
|
GitError,
|
|
10
11
|
GitResolutionError,
|
|
11
12
|
HERMETIC_GIT_FLAGS,
|
|
13
|
+
HOOK_AGENTS,
|
|
14
|
+
INSTRUCTIONS_BLOCK,
|
|
15
|
+
INSTRUCTIONS_END,
|
|
16
|
+
INSTRUCTIONS_START,
|
|
12
17
|
InvalidDetectorError,
|
|
13
18
|
MAX_CONFIG_BYTES,
|
|
14
19
|
MetricsCollector,
|
|
@@ -27,6 +32,7 @@ import {
|
|
|
27
32
|
detectAiAttribution,
|
|
28
33
|
findMergeBase,
|
|
29
34
|
findingFingerprint,
|
|
35
|
+
formatAgentFeedback,
|
|
30
36
|
formatCheckMarkdown,
|
|
31
37
|
formatCheckTerminal,
|
|
32
38
|
formatGithubAnnotations,
|
|
@@ -45,7 +51,13 @@ import {
|
|
|
45
51
|
getHermeticGitEnv,
|
|
46
52
|
getScoreColor,
|
|
47
53
|
gitOutputPath,
|
|
54
|
+
hookCommand,
|
|
55
|
+
hookConfigPath,
|
|
56
|
+
hookPausedUntil,
|
|
57
|
+
hookStatus,
|
|
48
58
|
initProject,
|
|
59
|
+
installHooks,
|
|
60
|
+
installInstructions,
|
|
49
61
|
isCheckRule,
|
|
50
62
|
isCodeFile,
|
|
51
63
|
isTestFile,
|
|
@@ -53,22 +65,32 @@ import {
|
|
|
53
65
|
loadConfig,
|
|
54
66
|
matchIgnorePath,
|
|
55
67
|
mostImportantFirst,
|
|
68
|
+
packageVersion,
|
|
56
69
|
parseDiff,
|
|
70
|
+
parseDuration,
|
|
57
71
|
parseGitLogStream,
|
|
72
|
+
pauseHook,
|
|
58
73
|
replayHistory,
|
|
74
|
+
repositoryRoot,
|
|
59
75
|
resolveCommitSha,
|
|
76
|
+
resumeHook,
|
|
60
77
|
reviewChanges,
|
|
61
78
|
reviewStagedChanges,
|
|
79
|
+
reviewWorktreeChanges,
|
|
62
80
|
roastRepository,
|
|
81
|
+
runDemo,
|
|
82
|
+
runHook,
|
|
63
83
|
streamGitDiff,
|
|
64
84
|
streamGitLog,
|
|
65
85
|
stripStringAndRegexLiterals,
|
|
66
86
|
terminalText,
|
|
87
|
+
uninstallHooks,
|
|
67
88
|
unquoteGitPath,
|
|
68
89
|
validateExceptions,
|
|
69
90
|
writeBaseline
|
|
70
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-AP5Y4CH6.js";
|
|
71
92
|
export {
|
|
93
|
+
AGENT_LABELS,
|
|
72
94
|
CONFIG_FILENAMES,
|
|
73
95
|
ConfigError,
|
|
74
96
|
DEFAULT_CONFIG,
|
|
@@ -78,6 +100,10 @@ export {
|
|
|
78
100
|
GitError,
|
|
79
101
|
GitResolutionError,
|
|
80
102
|
HERMETIC_GIT_FLAGS,
|
|
103
|
+
HOOK_AGENTS,
|
|
104
|
+
INSTRUCTIONS_BLOCK,
|
|
105
|
+
INSTRUCTIONS_END,
|
|
106
|
+
INSTRUCTIONS_START,
|
|
81
107
|
InvalidDetectorError,
|
|
82
108
|
MAX_CONFIG_BYTES,
|
|
83
109
|
MetricsCollector,
|
|
@@ -96,6 +122,7 @@ export {
|
|
|
96
122
|
detectAiAttribution,
|
|
97
123
|
findMergeBase,
|
|
98
124
|
findingFingerprint,
|
|
125
|
+
formatAgentFeedback,
|
|
99
126
|
formatCheckMarkdown,
|
|
100
127
|
formatCheckTerminal,
|
|
101
128
|
formatGithubAnnotations,
|
|
@@ -114,7 +141,13 @@ export {
|
|
|
114
141
|
getHermeticGitEnv,
|
|
115
142
|
getScoreColor,
|
|
116
143
|
gitOutputPath,
|
|
144
|
+
hookCommand,
|
|
145
|
+
hookConfigPath,
|
|
146
|
+
hookPausedUntil,
|
|
147
|
+
hookStatus,
|
|
117
148
|
initProject,
|
|
149
|
+
installHooks,
|
|
150
|
+
installInstructions,
|
|
118
151
|
isCheckRule,
|
|
119
152
|
isCodeFile,
|
|
120
153
|
isTestFile,
|
|
@@ -122,17 +155,26 @@ export {
|
|
|
122
155
|
loadConfig,
|
|
123
156
|
matchIgnorePath,
|
|
124
157
|
mostImportantFirst,
|
|
158
|
+
packageVersion,
|
|
125
159
|
parseDiff,
|
|
160
|
+
parseDuration,
|
|
126
161
|
parseGitLogStream,
|
|
162
|
+
pauseHook,
|
|
127
163
|
replayHistory,
|
|
164
|
+
repositoryRoot,
|
|
128
165
|
resolveCommitSha,
|
|
166
|
+
resumeHook,
|
|
129
167
|
reviewChanges,
|
|
130
168
|
reviewStagedChanges,
|
|
169
|
+
reviewWorktreeChanges,
|
|
131
170
|
roastRepository,
|
|
171
|
+
runDemo,
|
|
172
|
+
runHook,
|
|
132
173
|
streamGitDiff,
|
|
133
174
|
streamGitLog,
|
|
134
175
|
stripStringAndRegexLiterals,
|
|
135
176
|
terminalText,
|
|
177
|
+
uninstallHooks,
|
|
136
178
|
unquoteGitPath,
|
|
137
179
|
validateExceptions,
|
|
138
180
|
writeBaseline
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-roast",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "Stops AI coding agents (and rushed humans) from turning a build green by weakening tests, coverage gates or CI. Hook for Claude Code, Codex and Cursor, pre-commit, GitHub Action. Offline.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -26,14 +26,22 @@
|
|
|
26
26
|
},
|
|
27
27
|
"keywords": [
|
|
28
28
|
"ai",
|
|
29
|
-
"agents",
|
|
30
29
|
"ai-agents",
|
|
30
|
+
"ai-coding",
|
|
31
|
+
"llm",
|
|
31
32
|
"claude-code",
|
|
32
|
-
"cursor",
|
|
33
33
|
"codex",
|
|
34
|
+
"cursor",
|
|
35
|
+
"copilot",
|
|
36
|
+
"hooks",
|
|
37
|
+
"guardrails",
|
|
34
38
|
"code-review",
|
|
39
|
+
"testing",
|
|
35
40
|
"test-quality",
|
|
41
|
+
"github-actions",
|
|
42
|
+
"pre-commit",
|
|
36
43
|
"ci",
|
|
44
|
+
"static-analysis",
|
|
37
45
|
"sarif",
|
|
38
46
|
"git",
|
|
39
47
|
"cli"
|