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
package/dist/review.js ADDED
@@ -0,0 +1,274 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+ import { estimateTokens, getModel, loadIndex, selectContext, structuredCall } from "codengram";
5
+ import { isIgnored } from "./config.js";
6
+ import { parseDiff } from "./diff/parse.js";
7
+ import { withFindingId } from "./finding-id.js";
8
+ import { CRITIQUE_SYSTEM_PROMPT, REVIEW_SYSTEM_PROMPT, buildCritiqueUserPrompt, buildReviewUserPrompt, } from "./llm/prompt.js";
9
+ import { reviewOutputSchema, validateFindings } from "./llm/schema.js";
10
+ import { reconcileFindings } from "./reconcile.js";
11
+ import { compileRedactPatterns, redactSecrets } from "./redact.js";
12
+ import { SEVERITIES, severityRank } from "./types.js";
13
+ /** Cap on the prompt tokens spent on diff text per LLM call; larger diffs are chunked by file. */
14
+ const DIFF_TOKENS_PER_CALL = 60_000;
15
+ /** Token cap per inlined source snippet (~4 chars/token). */
16
+ const SNIPPET_TOKENS_PER_FILE = 2000;
17
+ export async function runReview(opts) {
18
+ const log = opts.log ?? (() => { });
19
+ const stats = {
20
+ model: getModel(),
21
+ llmCalls: 0,
22
+ inputTokens: 0,
23
+ outputTokens: 0,
24
+ findingsBySeverity: Object.fromEntries(SEVERITIES.map((s) => [s, 0])),
25
+ droppedValidation: 0,
26
+ droppedCaps: 0,
27
+ droppedDuplicates: 0,
28
+ droppedCritique: 0,
29
+ suppressed: 0,
30
+ persistent: 0,
31
+ };
32
+ const onUsage = (u) => {
33
+ stats.inputTokens += u.inputTokens;
34
+ stats.outputTokens += u.outputTokens;
35
+ };
36
+ const temperature = opts.config.temperature ?? undefined;
37
+ const extraRedactPatterns = opts.config.redact_secrets && opts.config.redact_patterns.length > 0
38
+ ? compileRedactPatterns(opts.config.redact_patterns)
39
+ : [];
40
+ const maybeRedact = (text) => opts.config.redact_secrets ? redactSecrets(text, extraRedactPatterns) : text;
41
+ // Parse redacted text so raw secrets never enter prompts. PEM replacement may collapse lines.
42
+ const allFiles = parseDiff(maybeRedact(opts.diffText));
43
+ const files = allFiles.filter((f) => !isIgnored(f.path, opts.config) && f.status !== "deleted" && f.changedLines.length > 0);
44
+ if (files.length === 0) {
45
+ return {
46
+ findings: [],
47
+ summary: "No reviewable changes in the diff.",
48
+ dropped: [],
49
+ files: [],
50
+ usedContext: false,
51
+ reconciliation: emptyReconciliation(),
52
+ findingsToPost: [],
53
+ stats,
54
+ };
55
+ }
56
+ log(`Reviewing ${files.length} changed file(s).`);
57
+ let context = "";
58
+ let usedContext = false;
59
+ if (opts.useContext !== false) {
60
+ const index = await loadIndex(opts.cwd);
61
+ if (index) {
62
+ const selection = selectContext(index, files.map((f) => f.path), opts.config.context_token_budget);
63
+ const snippets = await renderSnippets(opts.cwd, selection.entries, opts.config.context_snippets);
64
+ context = [selection.rendered, snippets].filter(Boolean).join("\n\n");
65
+ usedContext = selection.entries.length > 0;
66
+ log(`Injecting context from ${selection.entries.length} codemap entries.`);
67
+ }
68
+ else {
69
+ log("No codengram index found (.codengram/index.json) — reviewing diff-only. Run `codengram init` (or `codengram index`) and commit the index for whole-repo context.");
70
+ }
71
+ }
72
+ context = maybeRedact(context);
73
+ const chunks = chunkFiles(files);
74
+ if (chunks.length > 1)
75
+ log(`Large diff — splitting into ${chunks.length} review calls.`);
76
+ const outputs = [];
77
+ for (const chunk of chunks) {
78
+ const user = buildReviewUserPrompt({
79
+ files: chunk,
80
+ context,
81
+ config: opts.config,
82
+ changeDescription: opts.changeDescription,
83
+ });
84
+ const output = await withRetry(() => structuredCall({
85
+ system: REVIEW_SYSTEM_PROMPT,
86
+ user,
87
+ schema: reviewOutputSchema,
88
+ schemaName: "review_output",
89
+ temperature,
90
+ onUsage,
91
+ }));
92
+ stats.llmCalls += 1;
93
+ outputs.push(output);
94
+ }
95
+ const merged = {
96
+ summary: outputs.map((o) => o.summary).join(" "),
97
+ findings: outputs.flatMap((o) => o.findings),
98
+ };
99
+ const validated = validateFindings(merged, files, opts.config);
100
+ stats.droppedValidation = validated.dropped.length;
101
+ if (validated.dropped.length > 0) {
102
+ log(`Dropped ${validated.dropped.length} finding(s) failing validation (outside diff, low confidence, or disabled category).`);
103
+ }
104
+ const { kept, capped } = capFindings(validated.findings, opts.config.limits);
105
+ stats.droppedCaps = capped.length;
106
+ if (capped.length > 0) {
107
+ log(`Capped ${capped.length} finding(s) (limits: ${opts.config.limits.max_findings_per_file}/file, ${opts.config.limits.max_total} total).`);
108
+ }
109
+ const { kept: deduped, duplicates } = dedupeById(kept.map((f) => withFindingId(f)));
110
+ stats.droppedDuplicates = duplicates.length;
111
+ if (duplicates.length > 0) {
112
+ log(`Dropped ${duplicates.length} duplicate finding(s) (same file/category/title reported more than once).`);
113
+ }
114
+ let identified = deduped;
115
+ let droppedCritique = [];
116
+ if (opts.config.self_critique && identified.length > 0) {
117
+ const keepSchema = z.object({
118
+ keep_ids: z.array(z.string()).describe("Ids of the findings to keep"),
119
+ });
120
+ const critique = await withRetry(() => structuredCall({
121
+ system: CRITIQUE_SYSTEM_PROMPT,
122
+ user: buildCritiqueUserPrompt({ findings: identified, changeDescription: opts.changeDescription }),
123
+ schema: keepSchema,
124
+ schemaName: "critique_output",
125
+ temperature,
126
+ onUsage,
127
+ }));
128
+ stats.llmCalls += 1;
129
+ const keep = new Set(critique.keep_ids);
130
+ const surviving = identified.filter((f) => keep.has(f.id));
131
+ // A keep-list that empties the review is a misfire, not a judgment — keep everything.
132
+ if (surviving.length > 0 || identified.length === 0) {
133
+ droppedCritique = identified.filter((f) => !keep.has(f.id));
134
+ identified = surviving;
135
+ }
136
+ stats.droppedCritique = droppedCritique.length;
137
+ if (droppedCritique.length > 0) {
138
+ log(`Self-critique dropped ${droppedCritique.length} finding(s) it would not defend.`);
139
+ }
140
+ }
141
+ const { active, toPost, reconciliation } = reconcileFindings(identified, {
142
+ previousIds: opts.previousIds,
143
+ previousFindings: opts.previousFindings,
144
+ suppressedIds: opts.suppressedIds,
145
+ });
146
+ log(`Reconciliation: ${reconciliation.new.length} new, ${reconciliation.persistent.length} persistent, ${reconciliation.resolved.length} resolved, ${reconciliation.suppressed.length} suppressed.`);
147
+ for (const f of active)
148
+ stats.findingsBySeverity[f.severity] += 1;
149
+ stats.suppressed = reconciliation.suppressed.length;
150
+ stats.persistent = reconciliation.persistent.length;
151
+ return {
152
+ findings: active,
153
+ summary: validated.summary,
154
+ dropped: [...validated.dropped, ...capped, ...duplicates, ...droppedCritique],
155
+ reconciliation,
156
+ findingsToPost: toPost,
157
+ files,
158
+ usedContext,
159
+ stats,
160
+ };
161
+ }
162
+ /**
163
+ * Collapse findings that share a fingerprint id — the same underlying issue
164
+ * reported more than once in a single LLM response (or across chunked calls
165
+ * for a file split isn't possible, but the model itself repeating a finding
166
+ * for the same file/category/title is common). Keeps the higher-confidence
167
+ * copy; first-seen position is preserved so downstream ordering is stable.
168
+ */
169
+ export function dedupeById(findings) {
170
+ const byId = new Map();
171
+ const duplicates = [];
172
+ for (const f of findings) {
173
+ const id = f.id;
174
+ const prior = byId.get(id);
175
+ if (!prior) {
176
+ byId.set(id, f);
177
+ }
178
+ else if (f.confidence > prior.confidence) {
179
+ byId.set(id, f);
180
+ duplicates.push(prior);
181
+ }
182
+ else {
183
+ duplicates.push(f);
184
+ }
185
+ }
186
+ return { kept: [...byId.values()], duplicates };
187
+ }
188
+ /**
189
+ * Enforce per-file and total finding caps, dropping lowest-confidence first
190
+ * (severity breaks ties). Kept findings stay in validation order.
191
+ */
192
+ export function capFindings(findings, limits) {
193
+ const value = (f) => f.confidence - severityRank(f.severity) / 100;
194
+ const capped = new Set();
195
+ const byFile = new Map();
196
+ for (const f of findings) {
197
+ if (!byFile.has(f.file))
198
+ byFile.set(f.file, []);
199
+ byFile.get(f.file).push(f);
200
+ }
201
+ for (const group of byFile.values()) {
202
+ if (group.length <= limits.max_findings_per_file)
203
+ continue;
204
+ [...group]
205
+ .sort((a, b) => value(a) - value(b))
206
+ .slice(0, group.length - limits.max_findings_per_file)
207
+ .forEach((f) => capped.add(f));
208
+ }
209
+ let kept = findings.filter((f) => !capped.has(f));
210
+ if (kept.length > limits.max_total) {
211
+ [...kept]
212
+ .sort((a, b) => value(a) - value(b))
213
+ .slice(0, kept.length - limits.max_total)
214
+ .forEach((f) => capped.add(f));
215
+ kept = findings.filter((f) => !capped.has(f));
216
+ }
217
+ return { kept, capped: findings.filter((f) => capped.has(f)) };
218
+ }
219
+ /** Inline exact source of the top-N related (non-changed) context files, truncated per file. */
220
+ async function renderSnippets(cwd, entries, count) {
221
+ if (count <= 0)
222
+ return "";
223
+ const related = entries.filter((e) => e.relation !== "changed").slice(0, count);
224
+ const parts = [];
225
+ for (const e of related) {
226
+ try {
227
+ const src = await readFile(path.join(cwd, e.path), "utf8");
228
+ const maxChars = SNIPPET_TOKENS_PER_FILE * 4;
229
+ const body = src.length > maxChars ? `${src.slice(0, maxChars)}\n… (truncated)` : src;
230
+ parts.push(`## ${e.path} (full source, ${e.relation} of the change)\n\`\`\`\n${body}\n\`\`\``);
231
+ }
232
+ catch {
233
+ // file missing from the working tree (e.g. sparse checkout) — skip
234
+ }
235
+ }
236
+ return parts.join("\n\n");
237
+ }
238
+ function emptyReconciliation() {
239
+ return { new: [], persistent: [], resolved: [], suppressed: [] };
240
+ }
241
+ function chunkFiles(files) {
242
+ const chunks = [];
243
+ let current = [];
244
+ let tokens = 0;
245
+ for (const f of files) {
246
+ const cost = estimateTokens(JSON.stringify(f.hunks));
247
+ if (current.length > 0 && tokens + cost > DIFF_TOKENS_PER_CALL) {
248
+ chunks.push(current);
249
+ current = [];
250
+ tokens = 0;
251
+ }
252
+ current.push(f);
253
+ tokens += cost;
254
+ }
255
+ if (current.length)
256
+ chunks.push(current);
257
+ return chunks;
258
+ }
259
+ async function withRetry(fn, attempts = 3) {
260
+ let last;
261
+ for (let i = 0; i < attempts; i++) {
262
+ try {
263
+ return await fn();
264
+ }
265
+ catch (err) {
266
+ last = err;
267
+ if (i < attempts - 1) {
268
+ await new Promise((r) => setTimeout(r, 500 * 2 ** i));
269
+ }
270
+ }
271
+ }
272
+ throw last;
273
+ }
274
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function isWontFixText(body: string): boolean;
2
+ export declare function collectWontFixIds(items: Array<{
3
+ body: string;
4
+ isAgent?: boolean;
5
+ parentAgentId?: string | null;
6
+ reactionWontFix?: boolean;
7
+ }>): Set<string>;
@@ -0,0 +1,19 @@
1
+ import { parseFindingId } from "./finding-id.js";
2
+ const WONT_FIX_RE = /\b(won't\s*fix|wont\s*fix|false\s*positive|not\s*a\s*bug)\b/i;
3
+ export function isWontFixText(body) {
4
+ return WONT_FIX_RE.test(body);
5
+ }
6
+ export function collectWontFixIds(items) {
7
+ const out = new Set();
8
+ for (const item of items) {
9
+ const hit = item.reactionWontFix || isWontFixText(item.body);
10
+ if (!hit)
11
+ continue;
12
+ const id = item.parentAgentId ??
13
+ (item.isAgent ? parseFindingId(item.body) ?? undefined : undefined);
14
+ if (id)
15
+ out.add(id.toLowerCase());
16
+ }
17
+ return out;
18
+ }
19
+ //# sourceMappingURL=suppress-signals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppress-signals.js","sourceRoot":"","sources":["../src/suppress-signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,GAAG,8DAA8D,CAAC;AAEnF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAKE;IAEF,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7D,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,MAAM,EAAE,GACN,IAAI,CAAC,aAAa;YAClB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACtE,IAAI,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare const SUPPRESSIONS_FILENAME = ".pr-review-suppressions.yml";
2
+ export interface Suppressions {
3
+ ids: Set<string>;
4
+ }
5
+ export declare function loadSuppressions(cwd: string, configPath?: string): Promise<Suppressions>;
6
+ export declare function appendSuppressionIds(cwd: string, ids: Iterable<string>, filePath?: string): Promise<number>;
@@ -0,0 +1,48 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import YAML from "yaml";
5
+ import { z } from "zod";
6
+ export const SUPPRESSIONS_FILENAME = ".pr-review-suppressions.yml";
7
+ const schema = z
8
+ .object({
9
+ ids: z.array(z.string()).optional(),
10
+ })
11
+ .strict();
12
+ export async function loadSuppressions(cwd, configPath) {
13
+ const file = configPath ?? path.join(cwd, SUPPRESSIONS_FILENAME);
14
+ if (!existsSync(file)) {
15
+ if (configPath)
16
+ throw new Error(`Suppressions file not found: ${configPath}`);
17
+ return { ids: new Set() };
18
+ }
19
+ const raw = YAML.parse(await readFile(file, "utf8")) ?? {};
20
+ const parsed = schema.safeParse(raw);
21
+ if (!parsed.success) {
22
+ const issues = parsed.error.issues
23
+ .map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`)
24
+ .join("\n");
25
+ throw new Error(`Invalid ${path.basename(file)}:\n${issues}`);
26
+ }
27
+ return { ids: new Set((parsed.data.ids ?? []).map((id) => id.toLowerCase())) };
28
+ }
29
+ export async function appendSuppressionIds(cwd, ids, filePath) {
30
+ const file = filePath ?? path.join(cwd, SUPPRESSIONS_FILENAME);
31
+ const existing = existsSync(file)
32
+ ? await loadSuppressions(cwd, filePath)
33
+ : { ids: new Set() };
34
+ let added = 0;
35
+ for (const raw of ids) {
36
+ const id = raw.toLowerCase();
37
+ if (!existing.ids.has(id)) {
38
+ existing.ids.add(id);
39
+ added++;
40
+ }
41
+ }
42
+ if (added === 0)
43
+ return 0;
44
+ const sorted = [...existing.ids].sort();
45
+ await writeFile(file, YAML.stringify({ ids: sorted }), "utf8");
46
+ return added;
47
+ }
48
+ //# sourceMappingURL=suppressions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppressions.js","sourceRoot":"","sources":["../src/suppressions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ export type Severity = "critical" | "high" | "medium" | "low" | "info";
2
+ export declare const SEVERITIES: Severity[];
3
+ /** Lower index = more severe. */
4
+ export declare function severityRank(s: Severity): number;
5
+ export type Category = "bug" | "security" | "missing-tests" | "naming" | "complexity" | "custom";
6
+ export declare const CATEGORIES: Category[];
7
+ export interface Finding {
8
+ file: string;
9
+ /** New-file line numbers (side RIGHT of the diff). */
10
+ start_line: number;
11
+ end_line: number;
12
+ severity: Severity;
13
+ category: Category;
14
+ /** Model self-rated confidence, 0..1. */
15
+ confidence: number;
16
+ title: string;
17
+ /** Markdown explanation. */
18
+ body: string;
19
+ /** Optional replacement code for exactly the start_line..end_line range. */
20
+ suggestion?: string | null;
21
+ /** Stable fingerprint (file|category|normalized title). Set after validation. */
22
+ id?: string;
23
+ }
24
+ export interface Reconciliation {
25
+ new: string[];
26
+ persistent: string[];
27
+ resolved: string[];
28
+ suppressed: string[];
29
+ }
30
+ export interface Hunk {
31
+ oldStart: number;
32
+ oldLines: number;
33
+ newStart: number;
34
+ newLines: number;
35
+ /** Raw hunk lines including the leading ' ', '+', '-' or '\' character. */
36
+ lines: string[];
37
+ }
38
+ export type FileStatus = "added" | "modified" | "deleted" | "renamed";
39
+ export interface FileDiff {
40
+ /** New path (or old path for deletions). */
41
+ path: string;
42
+ oldPath?: string;
43
+ status: FileStatus;
44
+ hunks: Hunk[];
45
+ /** New-file line numbers that were added or modified — the commentable lines. */
46
+ changedLines: number[];
47
+ }
48
+ export interface ReviewConfig {
49
+ fail_on: Severity;
50
+ min_confidence: number;
51
+ ignore: string[];
52
+ context_token_budget: number;
53
+ /** Number of related files whose exact source is inlined into the prompt (0 disables). */
54
+ context_snippets: number;
55
+ custom_instructions: string;
56
+ categories: Record<Category, boolean>;
57
+ /** Sampling temperature; null = provider default (openai-compat: 0; anthropic: adaptive thinking). */
58
+ temperature: number | null;
59
+ limits: {
60
+ max_findings_per_file: number;
61
+ max_total: number;
62
+ };
63
+ /** Second drop-only LLM pass that removes findings the model would walk back. */
64
+ self_critique: boolean;
65
+ redact_secrets: boolean;
66
+ redact_patterns: string[];
67
+ write_suppressions: boolean;
68
+ }
69
+ export interface RunStats {
70
+ model: string;
71
+ llmCalls: number;
72
+ inputTokens: number;
73
+ outputTokens: number;
74
+ findingsBySeverity: Record<Severity, number>;
75
+ droppedValidation: number;
76
+ droppedCaps: number;
77
+ droppedDuplicates: number;
78
+ droppedCritique: number;
79
+ suppressed: number;
80
+ persistent: number;
81
+ }
82
+ export interface ReviewResult {
83
+ findings: Finding[];
84
+ /** Model's overall summary of the change. */
85
+ summary: string;
86
+ /** Findings the model produced that were dropped by post-validation. */
87
+ dropped: Finding[];
88
+ /** Lifecycle vs previous run / PR comments / suppressions. */
89
+ reconciliation?: Reconciliation;
90
+ /** Subset of findings that should be newly posted to the PR. */
91
+ findingsToPost?: Finding[];
92
+ }
package/dist/types.js ADDED
@@ -0,0 +1,14 @@
1
+ export const SEVERITIES = ["critical", "high", "medium", "low", "info"];
2
+ /** Lower index = more severe. */
3
+ export function severityRank(s) {
4
+ return SEVERITIES.indexOf(s);
5
+ }
6
+ export const CATEGORIES = [
7
+ "bug",
8
+ "security",
9
+ "missing-tests",
10
+ "naming",
11
+ "complexity",
12
+ "custom",
13
+ ];
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAe,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAEpF,iCAAiC;AACjC,MAAM,UAAU,YAAY,CAAC,CAAW;IACtC,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC;AAUD,MAAM,CAAC,MAAM,UAAU,GAAe;IACpC,KAAK;IACL,UAAU;IACV,eAAe;IACf,QAAQ;IACR,YAAY;IACZ,QAAQ;CACT,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "secondpair",
3
+ "version": "0.1.0",
4
+ "description": "LLM-powered pull request reviewer with whole-repo context from a persistent codengram codemap",
5
+ "type": "module",
6
+ "bin": {
7
+ "secondpair": "./dist/cli.js",
8
+ "pr-review": "./dist/cli.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "license": "MIT",
13
+ "author": "rv-ranbir",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/rv-ranbir/ai-tools.git",
17
+ "directory": "packages/secondpair"
18
+ },
19
+ "homepage": "https://github.com/rv-ranbir/ai-tools/tree/main/packages/secondpair#readme",
20
+ "bugs": "https://github.com/rv-ranbir/ai-tools/issues",
21
+ "keywords": [
22
+ "ai",
23
+ "llm",
24
+ "pr-review",
25
+ "code-review",
26
+ "github-actions",
27
+ "gitlab-ci",
28
+ "bitbucket",
29
+ "claude"
30
+ ],
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc -b",
36
+ "dev": "tsx src/cli.ts"
37
+ },
38
+ "engines": {
39
+ "node": ">=20"
40
+ },
41
+ "dependencies": {
42
+ "@octokit/rest": "^21.0.0",
43
+ "commander": "^12.1.0",
44
+ "picocolors": "^1.1.0",
45
+ "codengram": "^0.1.0",
46
+ "yaml": "^2.5.0",
47
+ "zod": "^4.4.3"
48
+ }
49
+ }