secondpair 0.1.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.
Files changed (62) hide show
  1. package/README.md +119 -0
  2. package/dist/bitbucket/comments.d.ts +49 -0
  3. package/dist/bitbucket/comments.js +198 -0
  4. package/dist/bitbucket/comments.js.map +1 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +208 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/config.d.ts +33 -0
  9. package/dist/config.js +87 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/diff/github.d.ts +14 -0
  12. package/dist/diff/github.js +27 -0
  13. package/dist/diff/github.js.map +1 -0
  14. package/dist/diff/local.d.ts +10 -0
  15. package/dist/diff/local.js +39 -0
  16. package/dist/diff/local.js.map +1 -0
  17. package/dist/diff/parse.d.ts +9 -0
  18. package/dist/diff/parse.js +141 -0
  19. package/dist/diff/parse.js.map +1 -0
  20. package/dist/finding-id.d.ts +28 -0
  21. package/dist/finding-id.js +96 -0
  22. package/dist/finding-id.js.map +1 -0
  23. package/dist/github/comments.d.ts +25 -0
  24. package/dist/github/comments.js +193 -0
  25. package/dist/github/comments.js.map +1 -0
  26. package/dist/gitlab/comments.d.ts +46 -0
  27. package/dist/gitlab/comments.js +213 -0
  28. package/dist/gitlab/comments.js.map +1 -0
  29. package/dist/index.d.ts +10 -0
  30. package/dist/index.js +11 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/llm/prompt.d.ts +23 -0
  33. package/dist/llm/prompt.js +53 -0
  34. package/dist/llm/prompt.js.map +1 -0
  35. package/dist/llm/schema.d.ts +61 -0
  36. package/dist/llm/schema.js +73 -0
  37. package/dist/llm/schema.js.map +1 -0
  38. package/dist/reconcile.d.ts +20 -0
  39. package/dist/reconcile.js +70 -0
  40. package/dist/reconcile.js.map +1 -0
  41. package/dist/redact.d.ts +3 -0
  42. package/dist/redact.js +25 -0
  43. package/dist/redact.js.map +1 -0
  44. package/dist/report/cli.d.ts +5 -0
  45. package/dist/report/cli.js +63 -0
  46. package/dist/report/cli.js.map +1 -0
  47. package/dist/report/json.d.ts +27 -0
  48. package/dist/report/json.js +53 -0
  49. package/dist/report/json.js.map +1 -0
  50. package/dist/review.d.ts +41 -0
  51. package/dist/review.js +274 -0
  52. package/dist/review.js.map +1 -0
  53. package/dist/suppress-signals.d.ts +7 -0
  54. package/dist/suppress-signals.js +19 -0
  55. package/dist/suppress-signals.js.map +1 -0
  56. package/dist/suppressions.d.ts +6 -0
  57. package/dist/suppressions.js +48 -0
  58. package/dist/suppressions.js.map +1 -0
  59. package/dist/types.d.ts +92 -0
  60. package/dist/types.js +14 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +49 -0
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+ import type { FileDiff, ReviewConfig, ReviewResult } from "../types.js";
3
+ export declare const findingSchema: z.ZodObject<{
4
+ file: z.ZodString;
5
+ start_line: z.ZodNumber;
6
+ end_line: z.ZodNumber;
7
+ severity: z.ZodEnum<{
8
+ critical: "critical";
9
+ high: "high";
10
+ medium: "medium";
11
+ low: "low";
12
+ info: "info";
13
+ }>;
14
+ category: z.ZodEnum<{
15
+ bug: "bug";
16
+ security: "security";
17
+ "missing-tests": "missing-tests";
18
+ naming: "naming";
19
+ complexity: "complexity";
20
+ custom: "custom";
21
+ }>;
22
+ confidence: z.ZodNumber;
23
+ title: z.ZodString;
24
+ body: z.ZodString;
25
+ suggestion: z.ZodNullable<z.ZodString>;
26
+ }, z.core.$strip>;
27
+ export declare const reviewOutputSchema: z.ZodObject<{
28
+ summary: z.ZodString;
29
+ findings: z.ZodArray<z.ZodObject<{
30
+ file: z.ZodString;
31
+ start_line: z.ZodNumber;
32
+ end_line: z.ZodNumber;
33
+ severity: z.ZodEnum<{
34
+ critical: "critical";
35
+ high: "high";
36
+ medium: "medium";
37
+ low: "low";
38
+ info: "info";
39
+ }>;
40
+ category: z.ZodEnum<{
41
+ bug: "bug";
42
+ security: "security";
43
+ "missing-tests": "missing-tests";
44
+ naming: "naming";
45
+ complexity: "complexity";
46
+ custom: "custom";
47
+ }>;
48
+ confidence: z.ZodNumber;
49
+ title: z.ZodString;
50
+ body: z.ZodString;
51
+ suggestion: z.ZodNullable<z.ZodString>;
52
+ }, z.core.$strip>>;
53
+ }, z.core.$strip>;
54
+ export type ReviewOutput = z.infer<typeof reviewOutputSchema>;
55
+ /**
56
+ * Post-validate model findings against the diff and config.
57
+ * Drops findings on files/lines outside the diff, below the confidence floor,
58
+ * or in disabled categories. Line ranges that merely overlap the changed lines
59
+ * are clamped rather than dropped.
60
+ */
61
+ export declare function validateFindings(raw: ReviewOutput, files: FileDiff[], config: ReviewConfig): ReviewResult;
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+ export const findingSchema = z.object({
3
+ file: z.string().describe("Repo-relative path of the file the finding is in"),
4
+ start_line: z.number().int().describe("First affected line (new-file line numbers)"),
5
+ end_line: z.number().int().describe("Last affected line (inclusive)"),
6
+ severity: z.enum(["critical", "high", "medium", "low", "info"]),
7
+ category: z.enum(["bug", "security", "missing-tests", "naming", "complexity", "custom"]),
8
+ confidence: z.number().describe("Self-rated confidence between 0 and 1"),
9
+ title: z.string().describe("One-line summary of the issue"),
10
+ body: z.string().describe("Markdown explanation with reasoning and impact"),
11
+ suggestion: z
12
+ .string()
13
+ .nullable()
14
+ .describe("Replacement code for exactly the start_line..end_line range, or null. Must be complete lines with correct indentation."),
15
+ });
16
+ export const reviewOutputSchema = z.object({
17
+ summary: z.string().describe("2-4 sentence overall assessment of the change"),
18
+ findings: z.array(findingSchema),
19
+ });
20
+ /**
21
+ * Post-validate model findings against the diff and config.
22
+ * Drops findings on files/lines outside the diff, below the confidence floor,
23
+ * or in disabled categories. Line ranges that merely overlap the changed lines
24
+ * are clamped rather than dropped.
25
+ */
26
+ export function validateFindings(raw, files, config) {
27
+ const changedByFile = new Map(files.map((f) => [f.path, new Set(f.changedLines)]));
28
+ const findings = [];
29
+ const dropped = [];
30
+ for (const f of raw.findings) {
31
+ const finding = { ...f, confidence: clamp01(f.confidence) };
32
+ if (config.categories[finding.category] === false) {
33
+ dropped.push(finding);
34
+ continue;
35
+ }
36
+ if (finding.confidence < config.min_confidence) {
37
+ dropped.push(finding);
38
+ continue;
39
+ }
40
+ const changed = changedByFile.get(finding.file);
41
+ if (!changed || changed.size === 0) {
42
+ dropped.push(finding);
43
+ continue;
44
+ }
45
+ // Clamp the range onto changed lines; drop when there is no overlap.
46
+ const inRange = [...changed].filter((l) => l >= finding.start_line && l <= finding.end_line);
47
+ if (inRange.length === 0) {
48
+ dropped.push(finding);
49
+ continue;
50
+ }
51
+ const start = Math.min(...inRange);
52
+ const end = Math.max(...inRange);
53
+ if (start !== finding.start_line || end !== finding.end_line) {
54
+ // Suggestions are only valid for the exact original range.
55
+ if (finding.suggestion)
56
+ finding.suggestion = null;
57
+ finding.start_line = start;
58
+ finding.end_line = end;
59
+ }
60
+ findings.push(finding);
61
+ }
62
+ findings.sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity) ||
63
+ a.file.localeCompare(b.file) ||
64
+ a.start_line - b.start_line);
65
+ return { findings, summary: raw.summary, dropped };
66
+ }
67
+ function severityOrder(s) {
68
+ return ["critical", "high", "medium", "low", "info"].indexOf(s);
69
+ }
70
+ function clamp01(n) {
71
+ return Math.max(0, Math.min(1, n));
72
+ }
73
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/llm/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Finding, Reconciliation } from "./types.js";
2
+ export type { Reconciliation };
3
+ export interface ReconcileResult {
4
+ /** Findings that still count for display + CI gate (new + persistent). */
5
+ active: Finding[];
6
+ /** Findings that should be posted as new PR comments. */
7
+ toPost: Finding[];
8
+ reconciliation: Reconciliation;
9
+ }
10
+ export type PreviousFinding = Pick<Finding, "id" | "file" | "category" | "title" | "start_line" | "end_line">;
11
+ /**
12
+ * Classify findings against previous/known ids and suppressions.
13
+ * Soft-matches previous report findings when titles were rephrased, and
14
+ * reuses the prior id so PR comment dedupe stays stable.
15
+ */
16
+ export declare function reconcileFindings(findings: Finding[], opts?: {
17
+ previousIds?: Iterable<string>;
18
+ previousFindings?: PreviousFinding[];
19
+ suppressedIds?: Iterable<string>;
20
+ }): ReconcileResult;
@@ -0,0 +1,70 @@
1
+ import { findingsSoftMatch } from "./finding-id.js";
2
+ /**
3
+ * Classify findings against previous/known ids and suppressions.
4
+ * Soft-matches previous report findings when titles were rephrased, and
5
+ * reuses the prior id so PR comment dedupe stays stable.
6
+ */
7
+ export function reconcileFindings(findings, opts = {}) {
8
+ const previous = new Set([...(opts.previousIds ?? [])].map((id) => id.toLowerCase()));
9
+ for (const pf of opts.previousFindings ?? []) {
10
+ if (pf.id)
11
+ previous.add(pf.id.toLowerCase());
12
+ }
13
+ const suppressed = new Set([...(opts.suppressedIds ?? [])].map((id) => id.toLowerCase()));
14
+ const previousFindings = [...(opts.previousFindings ?? [])].filter((p) => p.id);
15
+ const reconciliation = {
16
+ new: [],
17
+ persistent: [],
18
+ resolved: [],
19
+ suppressed: [],
20
+ };
21
+ const active = [];
22
+ const toPost = [];
23
+ const currentIds = new Set();
24
+ const claimedPrevious = new Set();
25
+ for (const raw of findings) {
26
+ let f = raw;
27
+ let id = (f.id ?? "").toLowerCase();
28
+ if (!id)
29
+ continue;
30
+ // Exact id hit, or soft-match a prior finding and adopt its id.
31
+ let matchedPrevious = previous.has(id);
32
+ if (!matchedPrevious) {
33
+ const soft = previousFindings.find((p) => p.id &&
34
+ !claimedPrevious.has(p.id.toLowerCase()) &&
35
+ !suppressed.has(p.id.toLowerCase()) &&
36
+ findingsSoftMatch(f, p));
37
+ if (soft?.id) {
38
+ const priorId = soft.id.toLowerCase();
39
+ f = { ...f, id: priorId };
40
+ id = priorId;
41
+ matchedPrevious = true;
42
+ claimedPrevious.add(priorId);
43
+ }
44
+ }
45
+ else {
46
+ claimedPrevious.add(id);
47
+ }
48
+ currentIds.add(id);
49
+ if (suppressed.has(id)) {
50
+ reconciliation.suppressed.push(id);
51
+ continue;
52
+ }
53
+ if (matchedPrevious) {
54
+ reconciliation.persistent.push(id);
55
+ active.push(f);
56
+ }
57
+ else {
58
+ reconciliation.new.push(id);
59
+ active.push(f);
60
+ toPost.push(f);
61
+ }
62
+ }
63
+ for (const id of previous) {
64
+ if (!currentIds.has(id) && !suppressed.has(id)) {
65
+ reconciliation.resolved.push(id);
66
+ }
67
+ }
68
+ return { active, toPost, reconciliation };
69
+ }
70
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAkBpD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAmB,EACnB,OAII,EAAE;IAEN,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACtF,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,gBAAgB,IAAI,EAAE,EAAE,CAAC;QAC7C,IAAI,EAAE,CAAC,EAAE;YAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC1F,MAAM,gBAAgB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEhF,MAAM,cAAc,GAAmB;QACrC,GAAG,EAAE,EAAE;QACP,UAAU,EAAE,EAAE;QACd,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,EAAE;KACf,CAAC;IAEF,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,GAAG,CAAC;QACZ,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,CAAC,EAAE;YAAE,SAAS;QAElB,gEAAgE;QAChE,IAAI,eAAe,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,EAAE;gBACJ,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;gBACxC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;gBACnC,iBAAiB,CAAC,CAAC,EAAE,CAAY,CAAC,CACrC,CAAC;YACF,IAAI,IAAI,EAAE,EAAE,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;gBACtC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;gBAC1B,EAAE,GAAG,OAAO,CAAC;gBACb,eAAe,GAAG,IAAI,CAAC;gBACvB,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC;QAED,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEnB,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACvB,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnC,SAAS;QACX,CAAC;QAED,IAAI,eAAe,EAAE,CAAC;YACpB,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/C,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const BUILTIN_REDACT_PATTERNS: RegExp[];
2
+ export declare function compileRedactPatterns(sources: string[]): RegExp[];
3
+ export declare function redactSecrets(text: string, extra?: RegExp[]): string;
package/dist/redact.js ADDED
@@ -0,0 +1,25 @@
1
+ export const BUILTIN_REDACT_PATTERNS = [
2
+ /\bAKIA[0-9A-Z]{16}\b/g,
3
+ /\b(sk|ghp|gho|ghu|ghs|ghr|glpat)-[A-Za-z0-9_-]{20,}\b/g,
4
+ /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g,
5
+ /\b(password|api[_-]?key|secret|token)\b\s*[:=]\s*['"]?[^\s'"]{8,}/gi,
6
+ ];
7
+ export function compileRedactPatterns(sources) {
8
+ return sources.map((src, i) => {
9
+ try {
10
+ return new RegExp(src, "g");
11
+ }
12
+ catch (e) {
13
+ throw new Error(`Invalid redact_patterns[${i}]: ${e instanceof Error ? e.message : String(e)}`);
14
+ }
15
+ });
16
+ }
17
+ export function redactSecrets(text, extra = []) {
18
+ let out = text;
19
+ for (const re of [...BUILTIN_REDACT_PATTERNS, ...extra]) {
20
+ const flags = re.flags.includes("g") ? re.flags : re.flags + "g";
21
+ out = out.replace(new RegExp(re.source, flags), "[REDACTED]");
22
+ }
23
+ return out;
24
+ }
25
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAa;IAC/C,uBAAuB;IACvB,wDAAwD;IACxD,2GAA2G;IAC3G,qEAAqE;CACtE,CAAC;AAEF,MAAM,UAAU,qBAAqB,CAAC,OAAiB;IACrD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QAC5B,IAAI,CAAC;YACH,OAAO,IAAI,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,2BAA2B,CAAC,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAC/E,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,QAAkB,EAAE;IAC9D,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,uBAAuB,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,GAAG,CAAC;QACjE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type Finding, type ReviewResult, type Severity } from "../types.js";
2
+ export declare function formatReport(result: ReviewResult): string;
3
+ export declare function summaryCounts(findings: Finding[]): string;
4
+ /** True when any finding meets or exceeds the fail_on severity threshold. */
5
+ export declare function shouldFail(findings: Finding[], failOn: Severity): boolean;
@@ -0,0 +1,63 @@
1
+ import pc from "picocolors";
2
+ import { severityRank } from "../types.js";
3
+ const SEVERITY_LABEL = {
4
+ critical: (s) => pc.bgRed(pc.white(` ${s.toUpperCase()} `)),
5
+ high: (s) => pc.red(s.toUpperCase()),
6
+ medium: (s) => pc.yellow(s.toUpperCase()),
7
+ low: (s) => pc.cyan(s.toUpperCase()),
8
+ info: (s) => pc.dim(s.toUpperCase()),
9
+ };
10
+ export function formatReport(result) {
11
+ const lines = [];
12
+ lines.push(pc.bold("PR Review"));
13
+ lines.push("");
14
+ lines.push(result.summary.trim());
15
+ lines.push("");
16
+ if (result.findings.length === 0) {
17
+ lines.push(pc.green("✓ No findings."));
18
+ return lines.join("\n");
19
+ }
20
+ const byFile = new Map();
21
+ for (const f of result.findings) {
22
+ if (!byFile.has(f.file))
23
+ byFile.set(f.file, []);
24
+ byFile.get(f.file).push(f);
25
+ }
26
+ for (const [file, findings] of byFile) {
27
+ lines.push(pc.bold(pc.underline(file)));
28
+ for (const f of findings) {
29
+ const range = f.start_line === f.end_line ? `L${f.start_line}` : `L${f.start_line}-${f.end_line}`;
30
+ lines.push(` ${SEVERITY_LABEL[f.severity](f.severity)} ${pc.dim(`[${f.category}]`)} ${pc.dim(range)} ${f.title} ${pc.dim(`(${Math.round(f.confidence * 100)}%)`)}`);
31
+ for (const bodyLine of f.body.trim().split("\n")) {
32
+ lines.push(` ${bodyLine}`);
33
+ }
34
+ if (f.suggestion) {
35
+ lines.push(pc.dim(" suggestion:"));
36
+ for (const s of f.suggestion.split("\n"))
37
+ lines.push(pc.green(` + ${s}`));
38
+ }
39
+ lines.push("");
40
+ }
41
+ }
42
+ lines.push(summaryCounts(result.findings));
43
+ if (result.reconciliation) {
44
+ const r = result.reconciliation;
45
+ lines.push(pc.dim(`lifecycle: ${r.new.length} new · ${r.persistent.length} persistent · ${r.resolved.length} resolved · ${r.suppressed.length} suppressed`));
46
+ }
47
+ return lines.join("\n");
48
+ }
49
+ export function summaryCounts(findings) {
50
+ const counts = new Map();
51
+ for (const f of findings)
52
+ counts.set(f.severity, (counts.get(f.severity) ?? 0) + 1);
53
+ const parts = [...counts.entries()]
54
+ .sort((a, b) => severityRank(a[0]) - severityRank(b[0]))
55
+ .map(([sev, n]) => `${n} ${sev}`);
56
+ return pc.bold(`${findings.length} finding(s): ${parts.join(", ")}`);
57
+ }
58
+ /** True when any finding meets or exceeds the fail_on severity threshold. */
59
+ export function shouldFail(findings, failOn) {
60
+ const threshold = severityRank(failOn);
61
+ return findings.some((f) => severityRank(f.severity) <= threshold);
62
+ }
63
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/report/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { Finding, Reconciliation, ReviewResult, RunStats } from "../types.js";
2
+ export interface JsonReport {
3
+ meta: {
4
+ generatedAt: string;
5
+ model: string;
6
+ changeDescription: string;
7
+ usedContext: boolean;
8
+ };
9
+ summary: string;
10
+ findings: ReviewResult["findings"];
11
+ reconciliation?: Reconciliation;
12
+ stats?: RunStats;
13
+ }
14
+ export declare function buildJsonReport(result: ReviewResult & {
15
+ stats?: RunStats;
16
+ }, meta: {
17
+ model: string;
18
+ changeDescription: string;
19
+ usedContext: boolean;
20
+ }): JsonReport;
21
+ /** One structured log line for CI parsing: counts, tokens, model. */
22
+ export declare function formatRunSummaryLine(stats: RunStats): string;
23
+ export declare function writeJsonReport(path: string, report: JsonReport): Promise<void>;
24
+ /** Load finding ids from a previous pr-review-report.json if present. */
25
+ export declare function loadPreviousIds(reportPath: string): Promise<Set<string>>;
26
+ /** Load prior findings (for soft-match reconcile when titles were rephrased). */
27
+ export declare function loadPreviousFindings(reportPath: string): Promise<Finding[]>;
@@ -0,0 +1,53 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ export function buildJsonReport(result, meta) {
4
+ return {
5
+ meta: { generatedAt: new Date().toISOString(), ...meta },
6
+ summary: result.summary,
7
+ findings: result.findings,
8
+ reconciliation: result.reconciliation,
9
+ stats: result.stats,
10
+ };
11
+ }
12
+ /** One structured log line for CI parsing: counts, tokens, model. */
13
+ export function formatRunSummaryLine(stats) {
14
+ return `pr-review-summary ${JSON.stringify(stats)}`;
15
+ }
16
+ export async function writeJsonReport(path, report) {
17
+ await writeFile(path, JSON.stringify(report, null, 2) + "\n", "utf8");
18
+ }
19
+ /** Load finding ids from a previous pr-review-report.json if present. */
20
+ export async function loadPreviousIds(reportPath) {
21
+ const findings = await loadPreviousFindings(reportPath);
22
+ const ids = new Set();
23
+ for (const f of findings) {
24
+ if (f.id)
25
+ ids.add(f.id.toLowerCase());
26
+ }
27
+ if (!existsSync(reportPath))
28
+ return ids;
29
+ try {
30
+ const raw = JSON.parse(await readFile(reportPath, "utf8"));
31
+ for (const id of raw.reconciliation?.new ?? [])
32
+ ids.add(id.toLowerCase());
33
+ for (const id of raw.reconciliation?.persistent ?? [])
34
+ ids.add(id.toLowerCase());
35
+ }
36
+ catch {
37
+ /* ignore */
38
+ }
39
+ return ids;
40
+ }
41
+ /** Load prior findings (for soft-match reconcile when titles were rephrased). */
42
+ export async function loadPreviousFindings(reportPath) {
43
+ if (!existsSync(reportPath))
44
+ return [];
45
+ try {
46
+ const raw = JSON.parse(await readFile(reportPath, "utf8"));
47
+ return (raw.findings ?? []).filter((f) => f && f.id && f.file && f.category && f.title);
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ }
53
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/report/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAgBvD,MAAM,UAAU,eAAe,CAC7B,MAA2C,EAC3C,IAAwE;IAExE,OAAO;QACL,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,EAAE;QACxD,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,oBAAoB,CAAC,KAAe;IAClD,OAAO,qBAAqB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,MAAkB;IACpE,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AACxE,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAkB;IACtD,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,GAAG,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAExD,CAAC;QACF,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,cAAc,EAAE,GAAG,IAAI,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1E,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,cAAc,EAAE,UAAU,IAAI,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACnF,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,UAAkB;IAC3D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAExD,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { type FileDiff, type Finding, type ReviewConfig, type ReviewResult, type RunStats } from "./types.js";
2
+ export interface RunReviewOptions {
3
+ cwd: string;
4
+ diffText: string;
5
+ config: ReviewConfig;
6
+ changeDescription: string;
7
+ /** Disable codemap context injection. */
8
+ useContext?: boolean;
9
+ /** Fingerprints from a previous report or existing PR comments. */
10
+ previousIds?: Iterable<string>;
11
+ /** Prior findings for soft-match when titles were rephrased. */
12
+ previousFindings?: import("./reconcile.js").PreviousFinding[];
13
+ /** Ids listed in `.pr-review-suppressions.yml`. */
14
+ suppressedIds?: Iterable<string>;
15
+ log?: (msg: string) => void;
16
+ }
17
+ export interface RunReviewOutput extends ReviewResult {
18
+ files: FileDiff[];
19
+ usedContext: boolean;
20
+ stats: RunStats;
21
+ }
22
+ export declare function runReview(opts: RunReviewOptions): Promise<RunReviewOutput>;
23
+ /**
24
+ * Collapse findings that share a fingerprint id — the same underlying issue
25
+ * reported more than once in a single LLM response (or across chunked calls
26
+ * for a file split isn't possible, but the model itself repeating a finding
27
+ * for the same file/category/title is common). Keeps the higher-confidence
28
+ * copy; first-seen position is preserved so downstream ordering is stable.
29
+ */
30
+ export declare function dedupeById(findings: Finding[]): {
31
+ kept: Finding[];
32
+ duplicates: Finding[];
33
+ };
34
+ /**
35
+ * Enforce per-file and total finding caps, dropping lowest-confidence first
36
+ * (severity breaks ties). Kept findings stay in validation order.
37
+ */
38
+ export declare function capFindings(findings: Finding[], limits: ReviewConfig["limits"]): {
39
+ kept: Finding[];
40
+ capped: Finding[];
41
+ };