diffcop 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +141 -2
- package/package.json +66 -4
- package/src/agent/claude-code/claude-code-agent.ts +174 -0
- package/src/agent/explored.ts +93 -0
- package/src/agent/reviewer-agent.ts +51 -0
- package/src/cli.ts +21 -0
- package/src/commands/review.ts +220 -0
- package/src/context/git/git-repository.ts +74 -0
- package/src/context/github/pull-request-context.ts +81 -0
- package/src/context/github/pull-request-data.ts +80 -0
- package/src/context/github/pull-request-fetcher.ts +149 -0
- package/src/context/repository.ts +16 -0
- package/src/context/review-context-collector.ts +106 -0
- package/src/github/github-client.ts +23 -0
- package/src/github/unwrap-response.ts +15 -0
- package/src/prompt/github/pull-request-formatter.ts +96 -0
- package/src/prompt/github/sanitize-content.ts +108 -0
- package/src/prompt/review-prompt-builder.ts +206 -0
- package/src/reporter/console/console-reporter.ts +72 -0
- package/src/reporter/console/json-reporter.ts +37 -0
- package/src/reporter/github/github-reporter.ts +93 -0
- package/src/reporter/github/review-comment-formatter.ts +144 -0
- package/src/reporter/github/sticky-comment.ts +181 -0
- package/src/reporter/progress.ts +12 -0
- package/src/reporter/review-layout.ts +359 -0
- package/src/reporter/review-reporter.ts +19 -0
- package/src/reporter/stats.ts +41 -0
- package/src/review/change-size-limit.ts +92 -0
- package/src/review/code-reviewer.ts +120 -0
- package/src/review/findings.ts +125 -0
- package/src/util/errors.ts +7 -0
- package/src/util/json.ts +7 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Explored } from "@/agent/explored";
|
|
3
|
+
import type { RunStats } from "@/agent/reviewer-agent";
|
|
4
|
+
|
|
5
|
+
export const SEVERITIES = ["high", "medium", "low", "nit"] as const;
|
|
6
|
+
|
|
7
|
+
export type Severity = (typeof SEVERITIES)[number];
|
|
8
|
+
|
|
9
|
+
export const SCOPES = ["introduced", "preexisting", "description"] as const;
|
|
10
|
+
|
|
11
|
+
export type Scope = (typeof SCOPES)[number];
|
|
12
|
+
|
|
13
|
+
// Descriptions end up in the JSON schema the model answers to.
|
|
14
|
+
export const findingSchema = z.object({
|
|
15
|
+
file: z.string().describe("Path relative to the repository root"),
|
|
16
|
+
startLine: z.int().positive(),
|
|
17
|
+
endLine: z.int().positive(),
|
|
18
|
+
severity: z
|
|
19
|
+
.enum(SEVERITIES)
|
|
20
|
+
.describe(
|
|
21
|
+
"high, medium or low: a bug, by impact. nit: not a bug, but readability, clarity, naming or a small cleanup on a changed line; never anything a formatter or linter enforces",
|
|
22
|
+
),
|
|
23
|
+
scope: z
|
|
24
|
+
.enum(SCOPES)
|
|
25
|
+
.describe(
|
|
26
|
+
"introduced: this diff caused it. preexisting: it was already wrong before this diff. description: the PR description claims something the diff doesn't do",
|
|
27
|
+
),
|
|
28
|
+
title: z.string().describe("One-line summary of the bug or nit"),
|
|
29
|
+
explanation: z
|
|
30
|
+
.string()
|
|
31
|
+
.describe(
|
|
32
|
+
"At most two sentences: which input or state fails and why, citing the code. For a nit, what to change and why",
|
|
33
|
+
),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export type Finding = z.infer<typeof findingSchema>;
|
|
37
|
+
|
|
38
|
+
export const findingsSchema = z.object({
|
|
39
|
+
summary: z
|
|
40
|
+
.string()
|
|
41
|
+
.describe(
|
|
42
|
+
"One sentence on what you checked, e.g. the call paths you traced",
|
|
43
|
+
),
|
|
44
|
+
findings: z.array(findingSchema),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const verdictSchema = z.object({
|
|
48
|
+
id: z.int().describe("The finding's id"),
|
|
49
|
+
verdict: z.enum(["confirmed", "refuted"]),
|
|
50
|
+
reason: z.string(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type Verdict = z.infer<typeof verdictSchema>;
|
|
54
|
+
|
|
55
|
+
export const verdictsSchema = z.object({
|
|
56
|
+
verdicts: z.array(verdictSchema),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export interface DroppedFinding {
|
|
60
|
+
finding: Finding;
|
|
61
|
+
// Why the verifier didn't confirm it.
|
|
62
|
+
reason: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ReviewResult {
|
|
66
|
+
summary: string;
|
|
67
|
+
findings: Finding[];
|
|
68
|
+
dropped: DroppedFinding[];
|
|
69
|
+
explored: Explored;
|
|
70
|
+
stats: RunStats;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Ids are 1-based positions, the numbering the verifier is shown.
|
|
74
|
+
export function numberFindings(findings: Finding[]) {
|
|
75
|
+
return findings.map((finding, index) => {
|
|
76
|
+
return {
|
|
77
|
+
id: index + 1,
|
|
78
|
+
...finding,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isConfirmed(verdict: Verdict) {
|
|
84
|
+
return verdict.verdict === "confirmed";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function dropReason(verdicts: Verdict[]) {
|
|
88
|
+
if (verdicts.length === 0) {
|
|
89
|
+
return "No verdict returned";
|
|
90
|
+
}
|
|
91
|
+
const reasons = verdicts
|
|
92
|
+
.map((v) => {
|
|
93
|
+
return v.reason.trim();
|
|
94
|
+
})
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.join(" / ");
|
|
97
|
+
if (verdicts.some(isConfirmed)) {
|
|
98
|
+
return `Contradictory verdicts: ${reasons}`;
|
|
99
|
+
}
|
|
100
|
+
return reasons || "Refuted without a reason";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// A finding needs a confirmation and no refutation, so a skipped or
|
|
104
|
+
// contradictory verdict drops it.
|
|
105
|
+
export function splitByVerdict(findings: Finding[], verdicts: Verdict[]) {
|
|
106
|
+
const confirmed: Finding[] = [];
|
|
107
|
+
const dropped: DroppedFinding[] = [];
|
|
108
|
+
findings.forEach((finding, index) => {
|
|
109
|
+
const own = verdicts.filter((v) => {
|
|
110
|
+
return v.id === index + 1;
|
|
111
|
+
});
|
|
112
|
+
if (own.length > 0 && own.every(isConfirmed)) {
|
|
113
|
+
confirmed.push(finding);
|
|
114
|
+
} else {
|
|
115
|
+
dropped.push({
|
|
116
|
+
finding: finding,
|
|
117
|
+
reason: dropReason(own),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
confirmed: confirmed,
|
|
123
|
+
dropped: dropped,
|
|
124
|
+
};
|
|
125
|
+
}
|
package/src/util/json.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"types": ["bun"],
|
|
11
|
+
|
|
12
|
+
// Bundler mode
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./src/*"]
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// Best practices
|
|
22
|
+
"strict": true,
|
|
23
|
+
"skipLibCheck": true,
|
|
24
|
+
"noFallthroughCasesInSwitch": true,
|
|
25
|
+
"noUncheckedIndexedAccess": true,
|
|
26
|
+
"noImplicitOverride": true,
|
|
27
|
+
|
|
28
|
+
// Some stricter flags (disabled by default)
|
|
29
|
+
"noUnusedLocals": false,
|
|
30
|
+
"noUnusedParameters": false,
|
|
31
|
+
"noPropertyAccessFromIndexSignature": false
|
|
32
|
+
},
|
|
33
|
+
"include": ["src", "*.config.ts"]
|
|
34
|
+
}
|