secondpair 0.1.3 → 0.1.5
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 +26 -3
- package/dist/bitbucket/auth.d.ts +13 -0
- package/dist/bitbucket/auth.js +37 -0
- package/dist/bitbucket/auth.js.map +1 -0
- package/dist/bitbucket/comments.d.ts +20 -14
- package/dist/bitbucket/comments.js +59 -45
- package/dist/bitbucket/comments.js.map +1 -1
- package/dist/cli.js +104 -43
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +68 -4
- package/dist/config.js.map +1 -1
- package/dist/diff/github.d.ts +2 -0
- package/dist/diff/github.js +10 -0
- package/dist/diff/github.js.map +1 -1
- package/dist/finding-id.d.ts +5 -3
- package/dist/finding-id.js +18 -11
- package/dist/finding-id.js.map +1 -1
- package/dist/github/auth.d.ts +4 -0
- package/dist/github/auth.js +16 -0
- package/dist/github/auth.js.map +1 -0
- package/dist/github/comments.d.ts +11 -2
- package/dist/github/comments.js +30 -4
- package/dist/github/comments.js.map +1 -1
- package/dist/gitlab/auth.d.ts +11 -0
- package/dist/gitlab/auth.js +22 -0
- package/dist/gitlab/auth.js.map +1 -0
- package/dist/gitlab/comments.d.ts +10 -9
- package/dist/gitlab/comments.js +34 -27
- package/dist/gitlab/comments.js.map +1 -1
- package/dist/host.d.ts +3 -0
- package/dist/host.js +15 -0
- package/dist/host.js.map +1 -0
- package/dist/llm/prompt.d.ts +21 -0
- package/dist/llm/prompt.js +57 -1
- package/dist/llm/prompt.js.map +1 -1
- package/dist/report/cli.d.ts +8 -3
- package/dist/report/cli.js +11 -1
- package/dist/report/cli.js.map +1 -1
- package/dist/report/json.d.ts +5 -0
- package/dist/report/json.js +2 -0
- package/dist/report/json.js.map +1 -1
- package/dist/retry.d.ts +1 -0
- package/dist/retry.js +16 -0
- package/dist/retry.js.map +1 -0
- package/dist/review-state.d.ts +16 -0
- package/dist/review-state.js +24 -0
- package/dist/review-state.js.map +1 -0
- package/dist/review.d.ts +29 -0
- package/dist/review.js +165 -29
- package/dist/review.js.map +1 -1
- package/dist/specialized-review.d.ts +29 -0
- package/dist/specialized-review.js +50 -0
- package/dist/specialized-review.js.map +1 -0
- package/dist/types.d.ts +18 -1
- package/package.json +3 -3
package/dist/config.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
12
12
|
context_token_budget: z.ZodOptional<z.ZodNumber>;
|
|
13
13
|
context_snippets: z.ZodOptional<z.ZodNumber>;
|
|
14
14
|
custom_instructions: z.ZodOptional<z.ZodString>;
|
|
15
|
+
custom_instructions_file: z.ZodOptional<z.ZodString>;
|
|
15
16
|
categories: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
16
17
|
[x: string]: string;
|
|
17
18
|
}> & z.core.$partial, z.ZodBoolean>>;
|
|
@@ -24,10 +25,20 @@ declare const configSchema: z.ZodObject<{
|
|
|
24
25
|
redact_secrets: z.ZodOptional<z.ZodBoolean>;
|
|
25
26
|
redact_patterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
27
|
write_suppressions: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
+
huge_pr_token_threshold: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
29
|
+
signal_detector: z.ZodOptional<z.ZodBoolean>;
|
|
30
|
+
parallel_agents: z.ZodOptional<z.ZodBoolean>;
|
|
31
|
+
semantic_dedup: z.ZodOptional<z.ZodBoolean>;
|
|
27
32
|
}, z.core.$strict>;
|
|
28
33
|
export declare const CONFIG_FILENAME = ".pr-review.yml";
|
|
34
|
+
export declare const SECONDPAIR_DIR = ".secondpair";
|
|
29
35
|
/** Load .pr-review.yml from the repo root, merged over defaults. */
|
|
30
36
|
export declare function loadConfig(cwd: string, configPath?: string): Promise<ReviewConfig>;
|
|
31
37
|
export declare function mergeConfig(partial: z.infer<typeof configSchema>): ReviewConfig;
|
|
38
|
+
/** Apply CLI-flag overrides (e.g. --fail-on, --write-suppressions) onto a loaded config. */
|
|
39
|
+
export declare function applyCliOverrides(config: ReviewConfig, overrides: {
|
|
40
|
+
failOn?: string;
|
|
41
|
+
writeSuppressions?: boolean;
|
|
42
|
+
}): ReviewConfig;
|
|
32
43
|
/** True when the path matches repocairn's built-in ignores or this config's patterns. */
|
|
33
44
|
export declare function isIgnored(filePath: string, config: ReviewConfig): boolean;
|
package/dist/config.js
CHANGED
|
@@ -9,12 +9,13 @@ import { CATEGORIES, SEVERITIES } from "./types.js";
|
|
|
9
9
|
export { matchesGlob };
|
|
10
10
|
const defaultCategories = Object.fromEntries(CATEGORIES.map((c) => [c, true]));
|
|
11
11
|
export const DEFAULT_CONFIG = {
|
|
12
|
-
fail_on: "
|
|
12
|
+
fail_on: "off",
|
|
13
13
|
min_confidence: 0.5,
|
|
14
14
|
ignore: [],
|
|
15
15
|
context_token_budget: 8000,
|
|
16
16
|
context_snippets: 3,
|
|
17
17
|
custom_instructions: "",
|
|
18
|
+
custom_instructions_file: ".pr-review-instructions.md",
|
|
18
19
|
categories: defaultCategories,
|
|
19
20
|
temperature: null,
|
|
20
21
|
limits: { max_findings_per_file: 5, max_total: 30 },
|
|
@@ -22,15 +23,20 @@ export const DEFAULT_CONFIG = {
|
|
|
22
23
|
redact_secrets: true,
|
|
23
24
|
redact_patterns: [],
|
|
24
25
|
write_suppressions: false,
|
|
26
|
+
huge_pr_token_threshold: 120_000,
|
|
27
|
+
signal_detector: true,
|
|
28
|
+
parallel_agents: true,
|
|
29
|
+
semantic_dedup: true,
|
|
25
30
|
};
|
|
26
31
|
const configSchema = z
|
|
27
32
|
.object({
|
|
28
|
-
fail_on: z.enum(SEVERITIES).optional(),
|
|
33
|
+
fail_on: z.enum([...SEVERITIES, "off"]).optional(),
|
|
29
34
|
min_confidence: z.number().min(0).max(1).optional(),
|
|
30
35
|
ignore: z.array(z.string()).optional(),
|
|
31
36
|
context_token_budget: z.number().int().positive().optional(),
|
|
32
37
|
context_snippets: z.number().int().min(0).optional(),
|
|
33
38
|
custom_instructions: z.string().optional(),
|
|
39
|
+
custom_instructions_file: z.string().optional(),
|
|
34
40
|
categories: z.partialRecord(z.enum(CATEGORIES), z.boolean()).optional(),
|
|
35
41
|
temperature: z.number().min(0).max(2).optional(),
|
|
36
42
|
limits: z
|
|
@@ -44,16 +50,61 @@ const configSchema = z
|
|
|
44
50
|
redact_secrets: z.boolean().optional(),
|
|
45
51
|
redact_patterns: z.array(z.string()).optional(),
|
|
46
52
|
write_suppressions: z.boolean().optional(),
|
|
53
|
+
huge_pr_token_threshold: z.number().int().positive().nullable().optional(),
|
|
54
|
+
signal_detector: z.boolean().optional(),
|
|
55
|
+
parallel_agents: z.boolean().optional(),
|
|
56
|
+
semantic_dedup: z.boolean().optional(),
|
|
47
57
|
})
|
|
48
58
|
.strict();
|
|
49
59
|
export const CONFIG_FILENAME = ".pr-review.yml";
|
|
60
|
+
export const SECONDPAIR_DIR = ".secondpair";
|
|
61
|
+
const SECONDPAIR_DIR_CANDIDATES = ["instructions.mdc", "instructions.md"];
|
|
62
|
+
/**
|
|
63
|
+
* `.secondpair/instructions.mdc` or `.secondpair/instructions.md` — same
|
|
64
|
+
* convention as `.claude`/`.cursor`/`.repocairn`. Checked before
|
|
65
|
+
* custom_instructions_file; wins over it when both exist.
|
|
66
|
+
*/
|
|
67
|
+
async function findDirInstructions(cwd) {
|
|
68
|
+
for (const name of SECONDPAIR_DIR_CANDIDATES) {
|
|
69
|
+
const file = path.join(cwd, SECONDPAIR_DIR, name);
|
|
70
|
+
if (existsSync(file))
|
|
71
|
+
return file;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolves the effective custom_instructions content, none of it mandatory:
|
|
77
|
+
* 1. `.secondpair/instructions.mdc|.md` (repo-standard location, highest precedence)
|
|
78
|
+
* 2. `custom_instructions_file` (repo-relative path, default `.pr-review-instructions.md`)
|
|
79
|
+
* 3. inline `custom_instructions` in .pr-review.yml (fallback, used as-is if neither file exists)
|
|
80
|
+
* This only ever feeds the CUSTOM REVIEW INSTRUCTIONS prompt section — it
|
|
81
|
+
* never touches REVIEW_SYSTEM_PROMPT, so it can't override review behavior,
|
|
82
|
+
* only add project-specific guidance on top of it.
|
|
83
|
+
*/
|
|
84
|
+
async function applyInstructionsFile(cwd, config) {
|
|
85
|
+
const dirFile = await findDirInstructions(cwd);
|
|
86
|
+
if (dirFile) {
|
|
87
|
+
const content = (await readFile(dirFile, "utf8")).trim();
|
|
88
|
+
if (content)
|
|
89
|
+
return { ...config, custom_instructions: content };
|
|
90
|
+
}
|
|
91
|
+
if (!config.custom_instructions_file)
|
|
92
|
+
return config;
|
|
93
|
+
const file = path.isAbsolute(config.custom_instructions_file)
|
|
94
|
+
? config.custom_instructions_file
|
|
95
|
+
: path.join(cwd, config.custom_instructions_file);
|
|
96
|
+
if (!existsSync(file))
|
|
97
|
+
return config;
|
|
98
|
+
const content = (await readFile(file, "utf8")).trim();
|
|
99
|
+
return content ? { ...config, custom_instructions: content } : config;
|
|
100
|
+
}
|
|
50
101
|
/** Load .pr-review.yml from the repo root, merged over defaults. */
|
|
51
102
|
export async function loadConfig(cwd, configPath) {
|
|
52
103
|
const file = configPath ?? path.join(cwd, CONFIG_FILENAME);
|
|
53
104
|
if (!existsSync(file)) {
|
|
54
105
|
if (configPath)
|
|
55
106
|
throw new Error(`Config file not found: ${configPath}`);
|
|
56
|
-
return { ...DEFAULT_CONFIG };
|
|
107
|
+
return applyInstructionsFile(cwd, { ...DEFAULT_CONFIG });
|
|
57
108
|
}
|
|
58
109
|
const raw = YAML.parse(await readFile(file, "utf8")) ?? {};
|
|
59
110
|
const parsed = configSchema.safeParse(raw);
|
|
@@ -64,7 +115,7 @@ export async function loadConfig(cwd, configPath) {
|
|
|
64
115
|
throw new Error(`Invalid ${path.basename(file)}:\n${issues}`);
|
|
65
116
|
}
|
|
66
117
|
compileRedactPatterns(parsed.data.redact_patterns ?? []);
|
|
67
|
-
return mergeConfig(parsed.data);
|
|
118
|
+
return applyInstructionsFile(cwd, mergeConfig(parsed.data));
|
|
68
119
|
}
|
|
69
120
|
export function mergeConfig(partial) {
|
|
70
121
|
return {
|
|
@@ -80,6 +131,19 @@ export function mergeConfig(partial) {
|
|
|
80
131
|
write_suppressions: partial.write_suppressions ?? DEFAULT_CONFIG.write_suppressions,
|
|
81
132
|
};
|
|
82
133
|
}
|
|
134
|
+
/** Apply CLI-flag overrides (e.g. --fail-on, --write-suppressions) onto a loaded config. */
|
|
135
|
+
export function applyCliOverrides(config, overrides) {
|
|
136
|
+
const next = { ...config };
|
|
137
|
+
if (overrides.writeSuppressions)
|
|
138
|
+
next.write_suppressions = true;
|
|
139
|
+
if (overrides.failOn) {
|
|
140
|
+
if (overrides.failOn !== "off" && !SEVERITIES.includes(overrides.failOn)) {
|
|
141
|
+
throw new Error(`--fail-on must be one of: ${SEVERITIES.join(", ")}, off`);
|
|
142
|
+
}
|
|
143
|
+
next.fail_on = overrides.failOn;
|
|
144
|
+
}
|
|
145
|
+
return next;
|
|
146
|
+
}
|
|
83
147
|
/** True when the path matches repocairn's built-in ignores or this config's patterns. */
|
|
84
148
|
export function isIgnored(filePath, config) {
|
|
85
149
|
return matchesIgnorePatterns(filePath, config.ignore);
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,IAAI,qBAAqB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/diff/github.d.ts
CHANGED
|
@@ -12,3 +12,5 @@ export declare function makeOctokit(token: string | undefined): Octokit;
|
|
|
12
12
|
/** Fetch the unified diff of a pull request. */
|
|
13
13
|
export declare function getPrDiff(octokit: Octokit, pr: PrRef): Promise<string>;
|
|
14
14
|
export declare function getPrHeadSha(octokit: Octokit, pr: PrRef): Promise<string>;
|
|
15
|
+
/** Paths changed between two commits on the PR's repo. */
|
|
16
|
+
export declare function listChangedFiles(octokit: Octokit, pr: PrRef, base: string, head: string): Promise<Set<string>>;
|
package/dist/diff/github.js
CHANGED
|
@@ -24,4 +24,14 @@ export async function getPrHeadSha(octokit, pr) {
|
|
|
24
24
|
const res = await octokit.pulls.get({ ...pr });
|
|
25
25
|
return res.data.head.sha;
|
|
26
26
|
}
|
|
27
|
+
/** Paths changed between two commits on the PR's repo. */
|
|
28
|
+
export async function listChangedFiles(octokit, pr, base, head) {
|
|
29
|
+
const res = await octokit.repos.compareCommits({
|
|
30
|
+
owner: pr.owner,
|
|
31
|
+
repo: pr.repo,
|
|
32
|
+
base,
|
|
33
|
+
head,
|
|
34
|
+
});
|
|
35
|
+
return new Set((res.data.files ?? []).map((f) => f.filename));
|
|
36
|
+
}
|
|
27
37
|
//# sourceMappingURL=github.js.map
|
package/dist/diff/github.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/diff/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAQxC,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,0BAA0B,CAAC,CAAC;IAC3F,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAyB;IACnD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAgB,EAAE,EAAS;IACzD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;QAClC,GAAG,EAAE;QACL,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;KAC9B,CAAC,CAAC;IACH,+DAA+D;IAC/D,OAAO,GAAG,CAAC,IAAyB,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB,EAAE,EAAS;IAC5D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC/C,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAC3B,CAAC"}
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/diff/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAQxC,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,0BAA0B,CAAC,CAAC;IAC3F,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAyB;IACnD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAgB,EAAE,EAAS;IACzD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;QAClC,GAAG,EAAE;QACL,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;KAC9B,CAAC,CAAC;IACH,+DAA+D;IAC/D,OAAO,GAAG,CAAC,IAAyB,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB,EAAE,EAAS;IAC5D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC/C,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAC3B,CAAC;AAED,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgB,EAChB,EAAS,EACT,IAAY,EACZ,IAAY;IAEZ,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC;QAC7C,KAAK,EAAE,EAAE,CAAC,KAAK;QACf,IAAI,EAAE,EAAE,CAAC,IAAI;QACb,IAAI;QACJ,IAAI;KACL,CAAC,CAAC;IACH,OAAO,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChE,CAAC"}
|
package/dist/finding-id.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Finding } from "./types.js";
|
|
2
|
-
export declare const FINDING_ID_MARKER_PREFIX = "
|
|
2
|
+
export declare const FINDING_ID_MARKER_PREFIX = "[secondpair-id]: # (pr-review-id:";
|
|
3
3
|
export declare const FINDING_ID_RE: RegExp;
|
|
4
4
|
/** Normalize title so minor wording churn still often matches. */
|
|
5
5
|
export declare function normalizeTitle(title: string): string;
|
|
@@ -22,7 +22,9 @@ export declare function titleSimilarity(a: string, b: string): number;
|
|
|
22
22
|
/** True when line ranges overlap or sit within `slack` lines of each other. */
|
|
23
23
|
export declare function linesNear(a: Pick<Finding, "start_line" | "end_line">, b: Pick<Finding, "start_line" | "end_line">, slack?: number): boolean;
|
|
24
24
|
/**
|
|
25
|
-
* Soft match for re-runs where the model rephrases the title
|
|
26
|
-
*
|
|
25
|
+
* Soft match for re-runs where the model rephrases the title (or flips its
|
|
26
|
+
* own category call, or cites a different anchor line for the same issue).
|
|
27
|
+
* Same file is the only hard requirement; category and line proximity are
|
|
28
|
+
* bonus signals traded off against title similarity.
|
|
27
29
|
*/
|
|
28
30
|
export declare function findingsSoftMatch(a: Pick<Finding, "file" | "category" | "title" | "start_line" | "end_line">, b: Pick<Finding, "file" | "category" | "title" | "start_line" | "end_line">, minTitleSimilarity?: number): boolean;
|
package/dist/finding-id.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
export const FINDING_ID_MARKER_PREFIX = "
|
|
3
|
-
export const FINDING_ID_RE =
|
|
2
|
+
export const FINDING_ID_MARKER_PREFIX = "[secondpair-id]: # (pr-review-id:";
|
|
3
|
+
export const FINDING_ID_RE = /\[secondpair-id\]:\s*#\s*\(pr-review-id:\s*([a-f0-9]+)\)/i;
|
|
4
4
|
/** Words that add churn without distinguishing the finding. */
|
|
5
5
|
const STOPWORDS = new Set([
|
|
6
6
|
"a", "an", "the", "to", "of", "in", "on", "for", "and", "or", "is", "are", "be",
|
|
@@ -38,9 +38,9 @@ export function withFindingId(f) {
|
|
|
38
38
|
}
|
|
39
39
|
export function embedFindingId(body, id) {
|
|
40
40
|
if (FINDING_ID_RE.test(body)) {
|
|
41
|
-
return body.replace(FINDING_ID_RE, `${FINDING_ID_MARKER_PREFIX} ${id}
|
|
41
|
+
return body.replace(FINDING_ID_RE, `${FINDING_ID_MARKER_PREFIX} ${id})`);
|
|
42
42
|
}
|
|
43
|
-
return `${body.trimEnd()}\n\n${FINDING_ID_MARKER_PREFIX} ${id}
|
|
43
|
+
return `${body.trimEnd()}\n\n${FINDING_ID_MARKER_PREFIX} ${id})\n`;
|
|
44
44
|
}
|
|
45
45
|
export function parseFindingId(body) {
|
|
46
46
|
return FINDING_ID_RE.exec(body)?.[1] ?? null;
|
|
@@ -75,13 +75,13 @@ export function linesNear(a, b, slack = 3) {
|
|
|
75
75
|
return a0 <= b.end_line && b.start_line <= a1;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Soft match for re-runs where the model rephrases the title
|
|
79
|
-
*
|
|
78
|
+
* Soft match for re-runs where the model rephrases the title (or flips its
|
|
79
|
+
* own category call, or cites a different anchor line for the same issue).
|
|
80
|
+
* Same file is the only hard requirement; category and line proximity are
|
|
81
|
+
* bonus signals traded off against title similarity.
|
|
80
82
|
*/
|
|
81
83
|
export function findingsSoftMatch(a, b, minTitleSimilarity = 0.3) {
|
|
82
|
-
if (a.file !== b.file
|
|
83
|
-
return false;
|
|
84
|
-
if (!linesNear(a, b))
|
|
84
|
+
if (a.file !== b.file)
|
|
85
85
|
return false;
|
|
86
86
|
const ta = new Set(titleTokens(a.title));
|
|
87
87
|
const tb = new Set(titleTokens(b.title));
|
|
@@ -89,8 +89,15 @@ export function findingsSoftMatch(a, b, minTitleSimilarity = 0.3) {
|
|
|
89
89
|
for (const t of ta)
|
|
90
90
|
if (tb.has(t))
|
|
91
91
|
inter++;
|
|
92
|
-
|
|
92
|
+
const sim = titleSimilarity(a.title, b.title);
|
|
93
|
+
// Category mismatch needs stronger title evidence — a bare overlap could
|
|
94
|
+
// just be two different issues in the same file/category pair.
|
|
95
|
+
const titleMatches = a.category === b.category ? inter >= 2 || sim >= minTitleSimilarity : inter >= 3 || sim >= 0.5;
|
|
96
|
+
if (!titleMatches)
|
|
97
|
+
return false;
|
|
98
|
+
if (linesNear(a, b))
|
|
93
99
|
return true;
|
|
94
|
-
|
|
100
|
+
// Large line drift only trusted when the wording is near-identical.
|
|
101
|
+
return sim >= 0.6;
|
|
95
102
|
}
|
|
96
103
|
//# sourceMappingURL=finding-id.js.map
|
package/dist/finding-id.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finding-id.js","sourceRoot":"","sources":["../src/finding-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,CAAC,MAAM,wBAAwB,GAAG,
|
|
1
|
+
{"version":3,"file":"finding-id.js","sourceRoot":"","sources":["../src/finding-id.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Resolve a GitHub token from the environment. */
|
|
2
|
+
export declare function resolveGhToken(): string;
|
|
3
|
+
/** Resolve owner/name from an explicit --repo flag or the GITHUB_REPOSITORY env var. */
|
|
4
|
+
export declare function resolveGhRepoSlug(explicit: string | undefined): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Resolve a GitHub token from the environment. */
|
|
2
|
+
export function resolveGhToken() {
|
|
3
|
+
const token = process.env.GITHUB_TOKEN;
|
|
4
|
+
if (!token) {
|
|
5
|
+
throw new Error("A GitHub token is required (GITHUB_TOKEN env var).");
|
|
6
|
+
}
|
|
7
|
+
return token;
|
|
8
|
+
}
|
|
9
|
+
/** Resolve owner/name from an explicit --repo flag or the GITHUB_REPOSITORY env var. */
|
|
10
|
+
export function resolveGhRepoSlug(explicit) {
|
|
11
|
+
const slug = explicit ?? process.env.GITHUB_REPOSITORY;
|
|
12
|
+
if (!slug)
|
|
13
|
+
throw new Error("Pass --repo owner/name or set GITHUB_REPOSITORY when using --pr.");
|
|
14
|
+
return slug;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/github/auth.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACvC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,iBAAiB,CAAC,QAA4B;IAC5D,MAAM,IAAI,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACvD,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;IAC/F,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { Octokit } from "@octokit/rest";
|
|
2
2
|
import type { PrRef } from "../diff/github.js";
|
|
3
|
+
import { type ReviewState } from "../review-state.js";
|
|
3
4
|
import type { Finding, ReviewResult, Severity } from "../types.js";
|
|
4
|
-
export declare const AGENT_MARKER = "
|
|
5
|
+
export declare const AGENT_MARKER = "[secondpair]: # (agent marker)";
|
|
5
6
|
export declare const SEVERITY_EMOJI: Record<Severity, string>;
|
|
6
7
|
export declare function formatCommentBody(f: Finding): string;
|
|
7
|
-
export declare function formatReviewBody(result: ReviewResult
|
|
8
|
+
export declare function formatReviewBody(result: ReviewResult & {
|
|
9
|
+
highLevelReview?: boolean;
|
|
10
|
+
}, failed: boolean, headSha?: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Recover the full review state (head sha + every active finding) embedded
|
|
13
|
+
* in the agent's most recent PR review body — the source of truth for
|
|
14
|
+
* soft-match reconciliation and incremental (changed-files-only) re-review.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getReviewState(octokit: Octokit, pr: PrRef): Promise<ReviewState | null>;
|
|
8
17
|
export interface PostReviewOptions {
|
|
9
18
|
octokit: Octokit;
|
|
10
19
|
pr: PrRef;
|
package/dist/github/comments.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { collectIdsFromBodies, embedFindingId, parseFindingId } from "../finding-id.js";
|
|
2
|
+
import { embedReviewState, parseReviewState } from "../review-state.js";
|
|
2
3
|
import { collectWontFixIds } from "../suppress-signals.js";
|
|
3
|
-
|
|
4
|
+
// Reference-link definition, not an HTML comment: some renderers (Bitbucket)
|
|
5
|
+
// HTML-escape raw comments instead of hiding them, leaving `<!-- -->` visible
|
|
6
|
+
// in the rendered comment. `[label]: # (...)` is invisible everywhere.
|
|
7
|
+
export const AGENT_MARKER = "[secondpair]: # (agent marker)";
|
|
4
8
|
export const SEVERITY_EMOJI = {
|
|
5
9
|
critical: "🟥",
|
|
6
10
|
high: "🔴",
|
|
@@ -25,27 +29,49 @@ export function formatCommentBody(f) {
|
|
|
25
29
|
body = embedFindingId(body, f.id);
|
|
26
30
|
return body;
|
|
27
31
|
}
|
|
28
|
-
export function formatReviewBody(result, failed) {
|
|
32
|
+
export function formatReviewBody(result, failed, headSha) {
|
|
29
33
|
const counts = countBySeverity(result.findings);
|
|
30
34
|
const countLine = result.findings.length === 0
|
|
31
35
|
? "No findings."
|
|
32
36
|
: Object.entries(counts)
|
|
33
37
|
.map(([sev, n]) => `${SEVERITY_EMOJI[sev]} ${n} ${sev}`)
|
|
34
38
|
.join(" · ");
|
|
39
|
+
const highLevelLine = result.highLevelReview
|
|
40
|
+
? "\n⚠️ **Large diff** — high-level review only (critical/high severity). Consider splitting this PR."
|
|
41
|
+
: "";
|
|
35
42
|
const recon = result.reconciliation;
|
|
36
43
|
const reconLine = recon
|
|
37
44
|
? `\n_Lifecycle:_ ${recon.new.length} new · ${recon.persistent.length} persistent · ${recon.resolved.length} resolved · ${recon.suppressed.length} suppressed`
|
|
38
45
|
: "";
|
|
39
|
-
|
|
46
|
+
let body = [
|
|
40
47
|
`## PR Review Agent`,
|
|
41
48
|
"",
|
|
42
49
|
result.summary.trim(),
|
|
43
50
|
"",
|
|
44
51
|
countLine,
|
|
52
|
+
highLevelLine,
|
|
45
53
|
reconLine,
|
|
46
54
|
failed ? "\n❌ **Check failed**: findings at or above the configured severity threshold." : "",
|
|
47
55
|
AGENT_MARKER,
|
|
48
56
|
].join("\n");
|
|
57
|
+
if (headSha)
|
|
58
|
+
body = embedReviewState(body, { headSha, findings: result.findings });
|
|
59
|
+
return body;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Recover the full review state (head sha + every active finding) embedded
|
|
63
|
+
* in the agent's most recent PR review body — the source of truth for
|
|
64
|
+
* soft-match reconciliation and incremental (changed-files-only) re-review.
|
|
65
|
+
*/
|
|
66
|
+
export async function getReviewState(octokit, pr) {
|
|
67
|
+
const reviews = await octokit.paginate(octokit.pulls.listReviews, { ...pr, per_page: 100 });
|
|
68
|
+
const mine = reviews.filter((r) => r.body?.includes(AGENT_MARKER));
|
|
69
|
+
for (let i = mine.length - 1; i >= 0; i--) {
|
|
70
|
+
const state = parseReviewState(mine[i].body ?? "");
|
|
71
|
+
if (state)
|
|
72
|
+
return state;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
49
75
|
}
|
|
50
76
|
function countBySeverity(findings) {
|
|
51
77
|
const counts = {};
|
|
@@ -157,7 +183,7 @@ export async function postReview(opts) {
|
|
|
157
183
|
}
|
|
158
184
|
if (skipped)
|
|
159
185
|
log(`Skipping ${skipped} comment(s) already posted (same finding id).`);
|
|
160
|
-
const body = formatReviewBody(result, opts.failed);
|
|
186
|
+
const body = formatReviewBody(result, opts.failed, opts.headSha);
|
|
161
187
|
try {
|
|
162
188
|
await octokit.pulls.createReview({
|
|
163
189
|
...pr,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../../src/github/comments.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,MAAM,CAAC,MAAM,YAAY,GAAG,
|
|
1
|
+
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../../src/github/comments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface GlRef {
|
|
2
|
+
/** GitLab server base URL, e.g. https://gitlab.com */
|
|
3
|
+
serverUrl: string;
|
|
4
|
+
/** URL-encoded project path ("group%2Fproject") or numeric project id. */
|
|
5
|
+
projectId: string;
|
|
6
|
+
mrIid: number;
|
|
7
|
+
}
|
|
8
|
+
/** Resolve a GitLab token from the environment. CI_JOB_TOKEN cannot post MR notes. */
|
|
9
|
+
export declare function resolveGlToken(): string;
|
|
10
|
+
/** Resolve project/MR from flags or GitLab CI env vars. */
|
|
11
|
+
export declare function resolveGlRef(repoSlug: string | undefined, pr: number | undefined): GlRef;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Resolve a GitLab token from the environment. CI_JOB_TOKEN cannot post MR notes. */
|
|
2
|
+
export function resolveGlToken() {
|
|
3
|
+
const token = process.env.GITLAB_TOKEN || process.env.GL_TOKEN;
|
|
4
|
+
if (!token) {
|
|
5
|
+
throw new Error("GitLab auth required: set GITLAB_TOKEN (personal or project access token with api scope). " +
|
|
6
|
+
"CI_JOB_TOKEN cannot post merge request notes.");
|
|
7
|
+
}
|
|
8
|
+
return token;
|
|
9
|
+
}
|
|
10
|
+
/** Resolve project/MR from flags or GitLab CI env vars. */
|
|
11
|
+
export function resolveGlRef(repoSlug, pr) {
|
|
12
|
+
const env = process.env;
|
|
13
|
+
const serverUrl = (env.CI_SERVER_URL || "https://gitlab.com").replace(/\/$/, "");
|
|
14
|
+
const projectId = repoSlug ? encodeURIComponent(repoSlug) : env.CI_PROJECT_ID;
|
|
15
|
+
const mrIid = pr ?? (env.CI_MERGE_REQUEST_IID ? parseInt(env.CI_MERGE_REQUEST_IID, 10) : undefined);
|
|
16
|
+
if (!projectId || mrIid == null || Number.isNaN(mrIid)) {
|
|
17
|
+
throw new Error("GitLab MR not identified. Pass --repo group/project and --pr <iid>, " +
|
|
18
|
+
"or run in a merge_request_event pipeline (CI_PROJECT_ID / CI_MERGE_REQUEST_IID).");
|
|
19
|
+
}
|
|
20
|
+
return { serverUrl, projectId, mrIid };
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/gitlab/auth.ts"],"names":[],"mappings":"AAQA,sFAAsF;AACtF,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC/D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,4FAA4F;YAC1F,+CAA+C,CAClD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,YAAY,CAAC,QAA4B,EAAE,EAAsB;IAC/E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,oBAAoB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACjF,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC;IAC9E,MAAM,KAAK,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACpG,IAAI,CAAC,SAAS,IAAI,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CACb,sEAAsE;YACpE,kFAAkF,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
+
import { type ReviewState } from "../review-state.js";
|
|
1
2
|
import type { ReviewResult } from "../types.js";
|
|
2
|
-
|
|
3
|
-
/** GitLab server base URL, e.g. https://gitlab.com */
|
|
4
|
-
serverUrl: string;
|
|
5
|
-
/** URL-encoded project path ("group%2Fproject") or numeric project id. */
|
|
6
|
-
projectId: string;
|
|
7
|
-
mrIid: number;
|
|
8
|
-
}
|
|
3
|
+
import { type GlRef } from "./auth.js";
|
|
9
4
|
export interface GlDiffRefs {
|
|
10
5
|
base_sha: string;
|
|
11
6
|
head_sha: string;
|
|
12
7
|
start_sha: string;
|
|
13
8
|
}
|
|
14
|
-
/** Resolve project/MR from flags or GitLab CI env vars. */
|
|
15
|
-
export declare function resolveGlRef(repoSlug: string | undefined, pr: number | undefined): GlRef;
|
|
16
9
|
/** Fetch MR changes and reconstruct a unified diff the parser understands. */
|
|
17
10
|
export declare function getGlMrDiff(ref: GlRef): Promise<{
|
|
18
11
|
diffText: string;
|
|
@@ -28,6 +21,14 @@ interface GlNote {
|
|
|
28
21
|
}
|
|
29
22
|
export declare function listGlNotes(ref: GlRef): Promise<GlNote[]>;
|
|
30
23
|
export declare function listGlFindingIds(ref: GlRef): Promise<Set<string>>;
|
|
24
|
+
/**
|
|
25
|
+
* Recover the full review state (head sha + every active finding) embedded
|
|
26
|
+
* in the most recent summary note — the source of truth for soft-match
|
|
27
|
+
* reconciliation and incremental (changed-files-only) re-review.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getGlReviewState(ref: GlRef): Promise<ReviewState | null>;
|
|
30
|
+
/** Paths changed between two commits in a GitLab project. */
|
|
31
|
+
export declare function listGlChangedFiles(ref: GlRef, from: string, to: string): Promise<Set<string>>;
|
|
31
32
|
export declare function listGlWontFixFindingIds(ref: GlRef): Promise<Set<string>>;
|
|
32
33
|
/** Resolve unresolved MR discussions whose notes contain one of the finding ids. */
|
|
33
34
|
export declare function resolveGlDiscussionsForIds(ref: GlRef, ids: Iterable<string>, log?: (msg: string) => void): Promise<number>;
|
package/dist/gitlab/comments.js
CHANGED
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
import { formatBbCommentBody, formatBbSummaryBody } from "../bitbucket/comments.js";
|
|
2
2
|
import { collectIdsFromBodies, parseFindingId } from "../finding-id.js";
|
|
3
3
|
import { AGENT_MARKER, SEVERITY_EMOJI } from "../github/comments.js";
|
|
4
|
+
import { parseReviewState } from "../review-state.js";
|
|
4
5
|
import { collectWontFixIds } from "../suppress-signals.js";
|
|
5
|
-
|
|
6
|
-
const token = process.env.GITLAB_TOKEN || process.env.GL_TOKEN;
|
|
7
|
-
if (!token) {
|
|
8
|
-
throw new Error("GitLab auth required: set GITLAB_TOKEN (personal or project access token with api scope). " +
|
|
9
|
-
"CI_JOB_TOKEN cannot post merge request notes.");
|
|
10
|
-
}
|
|
11
|
-
return token;
|
|
12
|
-
}
|
|
13
|
-
/** Resolve project/MR from flags or GitLab CI env vars. */
|
|
14
|
-
export function resolveGlRef(repoSlug, pr) {
|
|
15
|
-
const env = process.env;
|
|
16
|
-
const serverUrl = (env.CI_SERVER_URL || "https://gitlab.com").replace(/\/$/, "");
|
|
17
|
-
const projectId = repoSlug ? encodeURIComponent(repoSlug) : env.CI_PROJECT_ID;
|
|
18
|
-
const mrIid = pr ?? (env.CI_MERGE_REQUEST_IID ? parseInt(env.CI_MERGE_REQUEST_IID, 10) : undefined);
|
|
19
|
-
if (!projectId || mrIid == null || Number.isNaN(mrIid)) {
|
|
20
|
-
throw new Error("GitLab MR not identified. Pass --repo group/project and --pr <iid>, " +
|
|
21
|
-
"or run in a merge_request_event pipeline (CI_PROJECT_ID / CI_MERGE_REQUEST_IID).");
|
|
22
|
-
}
|
|
23
|
-
return { serverUrl, projectId, mrIid };
|
|
24
|
-
}
|
|
6
|
+
import { resolveGlToken } from "./auth.js";
|
|
25
7
|
function glApi(ref) {
|
|
26
8
|
return `${ref.serverUrl}/api/v4/projects/${ref.projectId}/merge_requests/${ref.mrIid}`;
|
|
27
9
|
}
|
|
@@ -72,6 +54,34 @@ export async function listGlFindingIds(ref) {
|
|
|
72
54
|
const notes = await listGlNotes(ref);
|
|
73
55
|
return collectIdsFromBodies(notes.filter((n) => n.body?.includes(AGENT_MARKER)).map((n) => n.body ?? ""));
|
|
74
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Recover the full review state (head sha + every active finding) embedded
|
|
59
|
+
* in the most recent summary note — the source of truth for soft-match
|
|
60
|
+
* reconciliation and incremental (changed-files-only) re-review.
|
|
61
|
+
*/
|
|
62
|
+
export async function getGlReviewState(ref) {
|
|
63
|
+
const notes = await listGlNotes(ref);
|
|
64
|
+
const summaries = notes.filter((n) => n.body?.includes(AGENT_MARKER) && n.type == null);
|
|
65
|
+
for (let i = summaries.length - 1; i >= 0; i--) {
|
|
66
|
+
const state = parseReviewState(summaries[i].body ?? "");
|
|
67
|
+
if (state)
|
|
68
|
+
return state;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/** Paths changed between two commits in a GitLab project. */
|
|
73
|
+
export async function listGlChangedFiles(ref, from, to) {
|
|
74
|
+
const res = await glFetch(`${ref.serverUrl}/api/v4/projects/${ref.projectId}/repository/compare?from=${from}&to=${to}`);
|
|
75
|
+
const data = (await res.json());
|
|
76
|
+
const paths = new Set();
|
|
77
|
+
for (const d of data.diffs ?? []) {
|
|
78
|
+
if (d.new_path)
|
|
79
|
+
paths.add(d.new_path);
|
|
80
|
+
if (d.old_path)
|
|
81
|
+
paths.add(d.old_path);
|
|
82
|
+
}
|
|
83
|
+
return paths;
|
|
84
|
+
}
|
|
75
85
|
export async function listGlWontFixFindingIds(ref) {
|
|
76
86
|
const notes = await listGlNotes(ref);
|
|
77
87
|
const byId = new Map(notes.flatMap((note) => (note.id == null ? [] : [[note.id, note]])));
|
|
@@ -153,13 +163,10 @@ export async function postGlReview(opts) {
|
|
|
153
163
|
const notes = await listGlNotes(ref);
|
|
154
164
|
const mine = notes.filter((n) => n.body?.includes(AGENT_MARKER));
|
|
155
165
|
const existingIds = collectIdsFromBodies(mine.map((n) => n.body ?? ""));
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
body: JSON.stringify({ body: formatBbSummaryBody(result, opts.failed) }),
|
|
161
|
-
});
|
|
162
|
-
}
|
|
166
|
+
await glFetch(`${glApi(ref)}/notes`, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
body: JSON.stringify({ body: formatBbSummaryBody(result, opts.failed, diffRefs.head_sha) }),
|
|
169
|
+
});
|
|
163
170
|
const toPost = result.findingsToPost ?? result.findings;
|
|
164
171
|
let posted = 0;
|
|
165
172
|
let skipped = 0;
|