agent-roast 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,17 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ ConfigError,
3
4
  GitError,
4
5
  GitResolutionError,
5
6
  InvalidDetectorError,
7
+ RULE_GUIDES,
8
+ VALID_DETECTORS,
6
9
  auditPullRequest,
10
+ checkProject,
7
11
  formatGithubStepSummary,
12
+ formatReviewMarkdown,
8
13
  generateShieldsJson,
9
14
  generateShieldsMarkdown,
10
15
  generateSvgBadge,
11
16
  initProject,
12
17
  loadConfig,
13
- roastRepository
14
- } from "./chunk-O4SB2FCT.js";
18
+ reviewChanges,
19
+ roastRepository,
20
+ writeBaseline
21
+ } from "./chunk-RU5OYHLR.js";
15
22
 
16
23
  // src/cli.ts
17
24
  import { Command } from "commander";
@@ -21,32 +28,41 @@ import * as path from "path";
21
28
  var require2 = createRequire(import.meta.url);
22
29
  var pkg = require2("../package.json");
23
30
  var program = new Command();
24
- program.name("agent-roast").description("Audit your git history or PR diffs for AI coding shortcuts and panic loops").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("--base <ref>", "Base git ref/branch for PR mode audit (e.g. origin/main)").option("--head <ref>", "Head git ref/SHA for PR mode audit (default: HEAD)", "HEAD").option("--fail-on <rules>", "Comma-separated detectors that trigger CI failure").option("--fail-under <score>", "Fail CI if agent score is below threshold (0-100)").option("--format <format>", "Output format: terminal, json, github, markdown", "terminal").option("--authors", "Display author names and emails in report", false).action(async (repoPath, options) => {
31
+ program.name("agent-roast").description("Audit your git history or PR diffs for AI coding shortcuts and panic loops").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", "Audit staged changes before committing (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("--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) => {
25
32
  try {
26
33
  if (process.env.GITHUB_EVENT_NAME === "pull_request_target") {
27
34
  console.warn(
28
35
  "Warning: pull_request_target runs with elevated repository privileges. agent-roast recommends running on 'pull_request'."
29
36
  );
30
37
  }
38
+ if (options.staged && options.base) throw new ConfigError("--staged cannot be combined with --base.");
39
+ if (!["terminal", "json", "github", "markdown"].includes(options.format)) {
40
+ throw new ConfigError("--format must be terminal, json, github, or markdown.");
41
+ }
42
+ if (options.failUnder !== void 0 && (options.failUnder.trim() === "" || !Number.isFinite(Number(options.failUnder)) || Number(options.failUnder) < 0 || Number(options.failUnder) > 100)) {
43
+ throw new ConfigError("--fail-under must be a number between 0 and 100.");
44
+ }
45
+ if (!options.base && !options.staged && options.failOn !== void 0) {
46
+ throw new ConfigError("--fail-on requires --base or --staged.");
47
+ }
48
+ if (!options.base && !options.staged && !options.json && !["terminal", "json"].includes(options.format)) {
49
+ throw new ConfigError("Repository audits support terminal or json format; use --base or --staged for github/markdown.");
50
+ }
31
51
  const config = await loadConfig(repoPath);
32
- if (options.base) {
33
- let failOnRules;
34
- if (options.failOn) {
35
- failOnRules = options.failOn.split(",").map((s) => s.trim()).filter(Boolean);
36
- } else {
37
- const errorRules = Object.entries(config.rules || {}).filter(([_, level]) => level === "error").map(([rule]) => rule);
38
- failOnRules = errorRules.length > 0 ? errorRules : ["test-skip"];
39
- }
52
+ if (options.base || options.staged) {
53
+ const failOnRules = options.failOn !== void 0 ? options.failOn.split(",").map((s) => s.trim()).filter(Boolean) : config.failOn;
40
54
  const chosenFormat = options.json ? "json" : options.format || "terminal";
41
55
  const prResult = await auditPullRequest({
42
56
  cwd: repoPath,
43
57
  base: options.base,
58
+ staged: options.staged,
44
59
  head: options.head || "HEAD",
45
60
  failOn: failOnRules,
46
61
  format: chosenFormat,
47
62
  verbose: options.verbose,
48
63
  authors: options.authors,
49
- ignorePaths: config.ignorePaths
64
+ ignorePaths: config.ignorePaths,
65
+ rules: config.rules
50
66
  });
51
67
  if (process.env.GITHUB_STEP_SUMMARY) {
52
68
  try {
@@ -80,9 +96,11 @@ has-supported-changes=${prResult.hasSupportedChanges}
80
96
  all: options.all,
81
97
  verbose: options.verbose,
82
98
  json: options.json,
83
- ignorePaths: config.ignorePaths
99
+ panicLoopWindowMinutes: config.panicLoopWindowMinutes,
100
+ ignorePaths: config.ignorePaths,
101
+ rules: config.rules
84
102
  });
85
- if (options.json) {
103
+ if (options.json || options.format === "json") {
86
104
  console.log(JSON.stringify(result.summary, null, 2));
87
105
  } else {
88
106
  console.log(result.output);
@@ -95,7 +113,7 @@ has-supported-changes=${prResult.hasSupportedChanges}
95
113
  }
96
114
  }
97
115
  } catch (err) {
98
- if (err instanceof GitResolutionError || err instanceof InvalidDetectorError || err instanceof GitError || err.name === "GitError") {
116
+ if (err instanceof ConfigError || err instanceof GitResolutionError || err instanceof InvalidDetectorError || err instanceof GitError || err.name === "GitError") {
99
117
  console.error(`agent-roast error: ${err.message}`);
100
118
  process.exitCode = 2;
101
119
  } else {
@@ -104,6 +122,95 @@ has-supported-changes=${prResult.hasSupportedChanges}
104
122
  }
105
123
  }
106
124
  });
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) => {
126
+ try {
127
+ const options = program.opts();
128
+ if (!options.base) throw new ConfigError("review requires --base <trusted-ref>.");
129
+ if (options.staged || options.all || options.failUnder !== void 0 || program.getOptionValueSource("since") === "cli") {
130
+ throw new ConfigError("review compares committed refs; --staged, --all, --since and --fail-under do not apply.");
131
+ }
132
+ const controller = new AbortController();
133
+ const abort = () => controller.abort();
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);
161
+ }
162
+ } catch (error) {
163
+ console.error(`agent-roast review error: ${error instanceof Error ? error.message : String(error)}`);
164
+ process.exitCode = 2;
165
+ }
166
+ });
167
+ 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>\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) => {
168
+ try {
169
+ const global = program.opts();
170
+ if (global.base || global.staged || global.failUnder !== void 0 || global.all || program.getOptionValueSource("head") === "cli" || program.getOptionValueSource("since") === "cli") {
171
+ throw new ConfigError("check inspects current files; history, --base, --head, --staged and --fail-under options do not apply.");
172
+ }
173
+ if (opts.baseline && opts.writeBaseline) throw new ConfigError("--baseline and --write-baseline cannot be combined.");
174
+ const result = await checkProject({
175
+ cwd: repoPath,
176
+ baseline: opts.baseline,
177
+ failOn: global.failOn !== void 0 ? global.failOn.split(",").map((rule) => rule.trim()).filter(Boolean) : void 0,
178
+ format: global.json ? "json" : global.format
179
+ });
180
+ if (opts.writeBaseline) {
181
+ if (!result.complete) throw new ConfigError("Cannot create a baseline from an incomplete scan. Resolve skipped files first.");
182
+ const basePath = path.resolve(repoPath, opts.writeBaseline);
183
+ writeBaseline(basePath, result.infractions);
184
+ const message = `Baseline created: ${basePath}
185
+ Accepted ${result.totalFindings} existing findings. The code has not been fixed.
186
+ 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.totalFindings }, null, 2) : message);
188
+ process.exitCode = 0;
189
+ return;
190
+ }
191
+ console.log(result.output);
192
+ process.exitCode = result.exitCode;
193
+ } catch (err) {
194
+ console.error(`agent-roast check error: ${err instanceof Error ? err.message : String(err)}`);
195
+ process.exitCode = 2;
196
+ }
197
+ });
198
+ program.command("explain <rule>").description("Explain a detector, its limits, and how to address a finding").action((rule) => {
199
+ if (!VALID_DETECTORS.includes(rule)) {
200
+ console.error(`Unknown rule '${rule}'. Valid rules: ${VALID_DETECTORS.join(", ")}`);
201
+ process.exitCode = 2;
202
+ return;
203
+ }
204
+ const guide = RULE_GUIDES[rule];
205
+ console.log(`${rule}: ${guide.title}
206
+
207
+ Why: ${guide.why}
208
+
209
+ Next: ${guide.suggestion}
210
+
211
+ Example: ${guide.example}
212
+ `);
213
+ });
107
214
  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) => {
108
215
  try {
109
216
  const res = await initProject({
@@ -142,7 +249,9 @@ program.command("badge").description("Generate an AI discipline badge (SVG, Mark
142
249
  cwd: repoPath,
143
250
  since,
144
251
  all: isAll,
145
- ignorePaths: config.ignorePaths
252
+ ignorePaths: config.ignorePaths,
253
+ rules: config.rules,
254
+ panicLoopWindowMinutes: config.panicLoopWindowMinutes
146
255
  });
147
256
  const score = result.summary.score;
148
257
  const label = opts.label || "AI Discipline";
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ interface Infraction {
19
19
  commitHash: string;
20
20
  commitDate: string;
21
21
  reason: string;
22
+ suggestion?: string;
22
23
  }
23
24
  interface MetricSummary {
24
25
  schemaVersion: string;
@@ -49,18 +50,23 @@ interface RoastOptions {
49
50
  authors?: boolean;
50
51
  ignorePaths?: string[];
51
52
  scoreThreshold?: number;
53
+ rules?: Record<string, "error" | "warn" | "off">;
54
+ panicLoopWindowMinutes?: number;
52
55
  }
53
56
  interface PrAuditOptions {
54
57
  cwd?: string;
55
- base: string;
56
- head: string;
58
+ base?: string;
59
+ head?: string;
60
+ staged?: boolean;
57
61
  failOn?: string[];
58
62
  format?: "terminal" | "json" | "github" | "markdown";
59
63
  verbose?: boolean;
60
64
  authors?: boolean;
61
65
  ignorePaths?: string[];
66
+ rules?: Record<string, "error" | "warn" | "off">;
62
67
  }
63
68
  interface PrAuditResult {
69
+ mode?: "pr" | "staged";
64
70
  schemaVersion: string;
65
71
  baseSha: string;
66
72
  headSha: string;
@@ -77,6 +83,71 @@ interface PrAuditResult {
77
83
  output: string;
78
84
  exitCode: number;
79
85
  }
86
+ interface CheckOptions {
87
+ cwd?: string;
88
+ failOn?: string[];
89
+ format?: ReviewFormat;
90
+ baseline?: string;
91
+ signal?: AbortSignal;
92
+ }
93
+ interface CheckResult {
94
+ schemaVersion: string;
95
+ mode: "check";
96
+ filesInspected: number;
97
+ linesInspected: number;
98
+ totalFindings: number;
99
+ infractions: Infraction[];
100
+ baselineCount: number;
101
+ staleBaselineCount: number;
102
+ skippedFiles: {
103
+ file: string;
104
+ reason: string;
105
+ }[];
106
+ complete: boolean;
107
+ failedRules: string[];
108
+ passed: boolean;
109
+ exitCode: number;
110
+ output: string;
111
+ }
112
+ type ReviewFormat = "terminal" | "json" | "github" | "markdown" | "sarif";
113
+ interface ReviewOptions {
114
+ cwd?: string;
115
+ base: string;
116
+ head?: string;
117
+ failOn?: string[];
118
+ format?: ReviewFormat;
119
+ signal?: AbortSignal;
120
+ }
121
+ interface ReviewResult {
122
+ schemaVersion: string;
123
+ mode: "review";
124
+ baseSha: string;
125
+ headSha: string;
126
+ mergeBaseSha: string;
127
+ policySha: string;
128
+ policyFile: string | null;
129
+ policyChanges: string[];
130
+ filesChanged: number;
131
+ filesInspected: number;
132
+ unsupportedFiles: number;
133
+ excludedFiles: number;
134
+ renamedFiles: number;
135
+ linesInspected: number;
136
+ beforeFindings: number;
137
+ afterFindings: number;
138
+ unchangedCount: number;
139
+ infractions: Infraction[];
140
+ removedFindings: Infraction[];
141
+ skippedFiles: {
142
+ file: string;
143
+ reason: string;
144
+ }[];
145
+ complete: boolean;
146
+ failedRules: string[];
147
+ passed: boolean;
148
+ exitCode: number;
149
+ output: string;
150
+ }
80
151
 
81
152
  interface AiAttributionResult {
82
153
  isAiAttributed: boolean;
@@ -110,7 +181,7 @@ declare class GitError extends Error {
110
181
  code: number | null;
111
182
  constructor(stderr: string, code: number | null);
112
183
  }
113
- declare const HERMETIC_GIT_FLAGS: readonly ["-c", "core.quotepath=false", "-c", "diff.noprefix=false", "-c", "diff.mnemonicPrefix=false", "--no-pager"];
184
+ declare const HERMETIC_GIT_FLAGS: readonly ["-c", "core.quotepath=false", "-c", "core.fsmonitor=false", "-c", "diff.noprefix=false", "-c", "diff.mnemonicPrefix=false", "--no-pager"];
114
185
  declare function getHermeticGitEnv(extraEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
115
186
  declare function parseGitLogStream(stream: Readable, onCommit: (commit: ParsedCommit, stats: {
116
187
  skippedLongLines: number;
@@ -130,7 +201,7 @@ declare function unquoteGitPath(pathStr: string): string;
130
201
  */
131
202
  declare function parseDiff(rawDiff: string): ParseDiffResult;
132
203
  declare function streamGitLog(cwd?: string, since?: string): Readable;
133
- declare function streamGitDiff(cwd: string, baseSha: string, headSha: string): Readable;
204
+ declare function streamGitDiff(cwd: string, baseSha: string, headSha: string, staged?: boolean): Readable;
134
205
  /**
135
206
  * Validates and resolves a git ref to a full commit SHA safely using --end-of-options.
136
207
  * Exits with code 2 on failure.
@@ -151,6 +222,7 @@ declare function isTestFile(filePath: string): boolean;
151
222
  */
152
223
  declare function auditFileDiffs(files: ParsedFileDiff[], commitHash?: string, commitDate?: string, options?: {
153
224
  ignorePaths?: string[];
225
+ rules?: Record<string, "error" | "warn" | "off">;
154
226
  }): {
155
227
  infractions: Infraction[];
156
228
  hasSupportedChanges: boolean;
@@ -178,9 +250,13 @@ declare class MetricsCollector {
178
250
  private activePanicChain;
179
251
  private includeAll;
180
252
  private ignorePaths?;
253
+ private rules?;
254
+ private panicLoopWindowMinutes;
181
255
  constructor(options?: {
182
256
  all?: boolean;
183
257
  ignorePaths?: string[];
258
+ rules?: Record<string, "error" | "warn" | "off">;
259
+ panicLoopWindowMinutes?: number;
184
260
  });
185
261
  recordSkipped(stats: {
186
262
  skippedLongLines?: number;
@@ -197,11 +273,7 @@ declare function formatTerminalCard(summary: MetricSummary, options: {
197
273
  verbose?: boolean;
198
274
  }): string;
199
275
  declare function formatPrTerminalCard(result: PrAuditResult): string;
200
- /**
201
- * Generates GitHub Actions workflow commands (::error and ::warning)
202
- * respecting GitHub's limits (max 10 annotations per level per step).
203
- */
204
- declare function formatGithubAnnotations(result: PrAuditResult): string;
276
+ declare function formatGithubAnnotations(result: Pick<PrAuditResult, "infractions" | "failedRules">): string;
205
277
  /**
206
278
  * Generates GitHub Step Summary Markdown
207
279
  */
@@ -210,6 +282,11 @@ declare function formatGithubStepSummary(result: PrAuditResult): string;
210
282
  * Generates rich GitHub PR Comment Markdown (for PR comments / sticky bots)
211
283
  */
212
284
  declare function formatPrMarkdownComment(result: PrAuditResult): string;
285
+ /** Current-tree report emphasizes next actions and explicitly accounts for baseline debt. */
286
+ declare function formatCheckTerminal(result: CheckResult): string;
287
+ declare function formatCheckMarkdown(result: CheckResult): string;
288
+ declare function formatReviewTerminal(result: ReviewResult): string;
289
+ declare function formatReviewMarkdown(result: ReviewResult): string;
213
290
 
214
291
  interface AgentRoastConfig {
215
292
  /** Detector rules to fail CI on in PR mode */
@@ -224,10 +301,13 @@ interface AgentRoastConfig {
224
301
  panicLoopWindowMinutes?: number;
225
302
  }
226
303
  declare const DEFAULT_CONFIG: AgentRoastConfig;
227
- /**
228
- * Discovers and parses repository configuration file.
229
- * Returns DEFAULT_CONFIG if no configuration file is found or if parsing fails.
230
- */
304
+ declare const CONFIG_FILENAMES: string[];
305
+ declare class ConfigError extends Error {
306
+ constructor(message: string);
307
+ }
308
+ /** Parse one explicit policy source; shared by worktree and trusted Git reviews. */
309
+ declare function configFromText(text: string, source: string): AgentRoastConfig;
310
+ /** Invalid policy must stop the audit instead of silently changing CI behavior. */
231
311
  declare function loadConfig(cwd?: string): AgentRoastConfig;
232
312
  /**
233
313
  * Matches a relative or normalized file path against an array of glob ignore patterns.
@@ -245,19 +325,14 @@ interface InitResult {
245
325
  skipped: string[];
246
326
  }
247
327
  declare const DEFAULT_CONFIG_TEMPLATE: {
248
- $schema: string;
249
328
  ignorePaths: string[];
250
329
  rules: {
251
- "hallucinated-package": string;
252
- "empty-catch": string;
253
- "trivial-assert": string;
254
- "stub-abandonment": string;
255
- "mock-leak": string;
330
+ [x: string]: "error" | "warn" | "off";
256
331
  };
257
332
  scoreThreshold: number;
258
333
  };
259
- declare const GITHUB_WORKFLOW_TEMPLATE = "name: Agent Roast\non:\n pull_request:\n branches: [main, master]\n push:\n branches: [main, master]\n\njobs:\n audit:\n name: Audit AI Shortcuts\n runs-on: ubuntu-latest\n steps:\n - name: Checkout repository\n uses: actions/checkout@v4\n with:\n fetch-depth: 0\n\n - name: Setup Node.js\n uses: actions/setup-node@v4\n with:\n node-version: 20\n\n - name: Run agent-roast PR check\n if: github.event_name == 'pull_request'\n run: npx agent-roast --base ${{ github.event.pull_request.base.sha }} --head ${{ github.event.pull_request.head.sha }} --format github\n\n - name: Run agent-roast repo audit\n if: github.event_name != 'pull_request'\n run: npx agent-roast --fail-under 70\n";
260
- declare const PRE_COMMIT_HOOK_TEMPLATE = "#!/bin/sh\n# Run agent-roast pre-commit verification\nnpx agent-roast --fail-under 70\n";
334
+ declare const GITHUB_WORKFLOW_TEMPLATE = "name: Agent Roast\non:\n pull_request:\n branches: [main, master]\n\npermissions:\n contents: read\n\njobs:\n audit:\n name: Audit AI Shortcuts\n runs-on: ubuntu-latest\n steps:\n - name: Checkout repository\n uses: actions/checkout@v4\n with:\n fetch-depth: 0\n\n - name: Run agent-roast PR check\n if: github.event_name == 'pull_request'\n uses: Nari-Ab/agent-roast@v1\n with:\n base: ${{ github.event.pull_request.base.sha }}\n head: ${{ github.event.pull_request.head.sha }}\n format: github\n mode: review\n";
335
+ declare const PRE_COMMIT_HOOK_TEMPLATE = "#!/bin/sh\n# Run agent-roast pre-commit verification\nnpx --no-install agent-roast --staged\n";
261
336
  declare function initProject(options?: InitOptions): Promise<InitResult>;
262
337
 
263
338
  interface ShieldsEndpointResponse {
@@ -274,6 +349,52 @@ declare function generateSvgBadge(score: number | null, label?: string): string;
274
349
  declare function generateShieldsMarkdown(score: number | null, label?: string, repoUrl?: string): string;
275
350
  declare function generateShieldsJson(score: number | null, label?: string): ShieldsEndpointResponse;
276
351
 
352
+ /** Read current tracked and non-ignored untracked source; never run project code. */
353
+ declare function checkProject(options?: CheckOptions): Promise<CheckResult>;
354
+
355
+ /** Match only within the same changed-file pair; Git renames remap old paths.
356
+ * Counts matter: an identical second shortcut is still a new finding. */
357
+ declare function compareFindings(before: Infraction[], after: Infraction[], newPath: string): {
358
+ introduced: Infraction[];
359
+ removed: Infraction[];
360
+ unchanged: number;
361
+ };
362
+ /** Compare full source blobs in changed files without executing or checking out the candidate. */
363
+ declare function reviewChanges(options: ReviewOptions): Promise<ReviewResult>;
364
+
365
+ /** Offline SARIF 2.1.0, with relative URIs and explicit incomplete-run status.
366
+ * Snippets are omitted to avoid embedding source code in integration artifacts. */
367
+ declare function formatSarif(report: CheckResult | ReviewResult): string;
368
+
369
+ interface FindingBaseline {
370
+ schemaVersion: "1.0.0";
371
+ fingerprintVersion: 1;
372
+ entries: {
373
+ fingerprint: string;
374
+ count: number;
375
+ }[];
376
+ }
377
+ /** Line-independent but multiplicity-aware; adding the same shortcut twice is new. */
378
+ declare function findingFingerprint(finding: Infraction): string;
379
+ declare function createBaseline(findings: Infraction[]): FindingBaseline;
380
+ declare function loadBaseline(filename: string): FindingBaseline;
381
+ declare function applyBaseline(findings: Infraction[], baseline: FindingBaseline): {
382
+ findings: Infraction[];
383
+ baselineCount: number;
384
+ staleBaselineCount: number;
385
+ };
386
+ /** Publish a complete file atomically and refuse to replace an existing baseline. */
387
+ declare function writeBaseline(filename: string, findings: Infraction[]): void;
388
+
389
+ interface RuleGuide {
390
+ title: string;
391
+ why: string;
392
+ suggestion: string;
393
+ example: string;
394
+ }
395
+ /** Review guidance, not automatic edits: legitimate exceptions need context. */
396
+ declare const RULE_GUIDES: Record<DetectorType, RuleGuide>;
397
+
277
398
  declare class InvalidDetectorError extends Error {
278
399
  constructor(message: string);
279
400
  }
@@ -283,4 +404,4 @@ declare function roastRepository(options?: RoastOptions): Promise<{
283
404
  }>;
284
405
  declare function auditPullRequest(options: PrAuditOptions): Promise<PrAuditResult>;
285
406
 
286
- export { type AgentRoastConfig, type AiAttributionResult, type CommitInfo, DEFAULT_CONFIG, DEFAULT_CONFIG_TEMPLATE, type DetectorType, FIELD_SEPARATOR, 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, type RoastOptions, type ShieldsEndpointResponse, VALID_DETECTORS, auditFileDiffs, auditPullRequest, detectAiAttribution, findMergeBase, formatGithubAnnotations, formatGithubStepSummary, formatPrMarkdownComment, formatPrTerminalCard, formatTerminalCard, generateShareUrl, generateShieldsJson, generateShieldsMarkdown, generateSvgBadge, getHermeticGitEnv, getScoreColor, initProject, isCodeFile, isTestFile, loadConfig, matchIgnorePath, parseDiff, parseGitLogStream, resolveCommitSha, roastRepository, streamGitDiff, streamGitLog, stripStringAndRegexLiterals, unquoteGitPath };
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 };
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CONFIG_FILENAMES,
4
+ ConfigError,
3
5
  DEFAULT_CONFIG,
4
6
  DEFAULT_CONFIG_TEMPLATE,
5
7
  FIELD_SEPARATOR,
@@ -11,15 +13,27 @@ import {
11
13
  MetricsCollector,
12
14
  PRE_COMMIT_HOOK_TEMPLATE,
13
15
  RECORD_SEPARATOR,
16
+ RULE_GUIDES,
14
17
  VALID_DETECTORS,
18
+ applyBaseline,
15
19
  auditFileDiffs,
16
20
  auditPullRequest,
21
+ checkProject,
22
+ compareFindings,
23
+ configFromText,
24
+ createBaseline,
17
25
  detectAiAttribution,
18
26
  findMergeBase,
27
+ findingFingerprint,
28
+ formatCheckMarkdown,
29
+ formatCheckTerminal,
19
30
  formatGithubAnnotations,
20
31
  formatGithubStepSummary,
21
32
  formatPrMarkdownComment,
22
33
  formatPrTerminalCard,
34
+ formatReviewMarkdown,
35
+ formatReviewTerminal,
36
+ formatSarif,
23
37
  formatTerminalCard,
24
38
  generateShareUrl,
25
39
  generateShieldsJson,
@@ -30,18 +44,23 @@ import {
30
44
  initProject,
31
45
  isCodeFile,
32
46
  isTestFile,
47
+ loadBaseline,
33
48
  loadConfig,
34
49
  matchIgnorePath,
35
50
  parseDiff,
36
51
  parseGitLogStream,
37
52
  resolveCommitSha,
53
+ reviewChanges,
38
54
  roastRepository,
39
55
  streamGitDiff,
40
56
  streamGitLog,
41
57
  stripStringAndRegexLiterals,
42
- unquoteGitPath
43
- } from "./chunk-O4SB2FCT.js";
58
+ unquoteGitPath,
59
+ writeBaseline
60
+ } from "./chunk-RU5OYHLR.js";
44
61
  export {
62
+ CONFIG_FILENAMES,
63
+ ConfigError,
45
64
  DEFAULT_CONFIG,
46
65
  DEFAULT_CONFIG_TEMPLATE,
47
66
  FIELD_SEPARATOR,
@@ -53,15 +72,27 @@ export {
53
72
  MetricsCollector,
54
73
  PRE_COMMIT_HOOK_TEMPLATE,
55
74
  RECORD_SEPARATOR,
75
+ RULE_GUIDES,
56
76
  VALID_DETECTORS,
77
+ applyBaseline,
57
78
  auditFileDiffs,
58
79
  auditPullRequest,
80
+ checkProject,
81
+ compareFindings,
82
+ configFromText,
83
+ createBaseline,
59
84
  detectAiAttribution,
60
85
  findMergeBase,
86
+ findingFingerprint,
87
+ formatCheckMarkdown,
88
+ formatCheckTerminal,
61
89
  formatGithubAnnotations,
62
90
  formatGithubStepSummary,
63
91
  formatPrMarkdownComment,
64
92
  formatPrTerminalCard,
93
+ formatReviewMarkdown,
94
+ formatReviewTerminal,
95
+ formatSarif,
65
96
  formatTerminalCard,
66
97
  generateShareUrl,
67
98
  generateShieldsJson,
@@ -72,14 +103,17 @@ export {
72
103
  initProject,
73
104
  isCodeFile,
74
105
  isTestFile,
106
+ loadBaseline,
75
107
  loadConfig,
76
108
  matchIgnorePath,
77
109
  parseDiff,
78
110
  parseGitLogStream,
79
111
  resolveCommitSha,
112
+ reviewChanges,
80
113
  roastRepository,
81
114
  streamGitDiff,
82
115
  streamGitLog,
83
116
  stripStringAndRegexLiterals,
84
- unquoteGitPath
117
+ unquoteGitPath,
118
+ writeBaseline
85
119
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-roast",
3
- "version": "0.4.0",
4
- "description": "Zero-telemetry CLI scorecard analyzing git history for AI coding agent infractions and panic loops",
3
+ "version": "0.6.0",
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",
7
7
  "types": "./dist/index.d.ts",
@@ -17,7 +17,9 @@
17
17
  "build": "tsup",
18
18
  "dev": "tsup --watch",
19
19
  "test": "vitest run",
20
- "prepublishOnly": "npm run build"
20
+ "prepublishOnly": "npm run build",
21
+ "pretest": "npm run build",
22
+ "typecheck": "tsc --noEmit"
21
23
  },
22
24
  "keywords": [
23
25
  "ai",