@codraoss/core 0.9.5 → 0.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2TF4DSRX.js → chunk-4XEMXRNG.js} +12 -12
- package/dist/{chunk-754NO4DH.js → chunk-EQL5OOEF.js} +4 -4
- package/dist/{chunk-JC74XH7Q.js → chunk-GDX2ZEBA.js} +1 -1
- package/dist/chunk-GDX2ZEBA.js.map +1 -0
- package/dist/formatter-CDFpqbDP.d.ts +24 -0
- package/dist/formatter.d.ts +29 -0
- package/dist/formatter.js +111 -0
- package/dist/formatter.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +19 -21
- package/dist/index.js.map +1 -1
- package/dist/model-output/index.js +5 -5
- package/dist/ports/index.d.ts +6 -24
- package/dist/ports/index.js +1 -1
- package/dist/rules/detect.js +2 -2
- package/package.json +9 -74
- package/dist/chunk-JC74XH7Q.js.map +0 -1
- /package/dist/{chunk-2TF4DSRX.js.map → chunk-4XEMXRNG.js.map} +0 -0
- /package/dist/{chunk-754NO4DH.js.map → chunk-EQL5OOEF.js.map} +0 -0
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findPositionForLine,
|
|
3
|
+
getValidPositions
|
|
4
|
+
} from "./chunk-I5S3NBFL.js";
|
|
1
5
|
import {
|
|
2
6
|
generatorFindingCap
|
|
3
7
|
} from "./chunk-ONJZXUQT.js";
|
|
4
8
|
import {
|
|
5
9
|
renderDiffSnippet
|
|
6
10
|
} from "./chunk-KTDBDKL5.js";
|
|
11
|
+
import {
|
|
12
|
+
MAX_LOGGED_JSON_CHARS,
|
|
13
|
+
MIN_DISCRIMINATING_EVIDENCE_CHARS,
|
|
14
|
+
NON_ANSWER_MAX_RESPONSE_CHARS,
|
|
15
|
+
NON_ANSWER_MIN_DIFF_LINES,
|
|
16
|
+
SEVERITY_ORDER
|
|
17
|
+
} from "./chunk-W3RY755V.js";
|
|
7
18
|
import {
|
|
8
19
|
buildPresenceIndex,
|
|
9
20
|
checkAbsenceClaim,
|
|
@@ -21,17 +32,6 @@ import {
|
|
|
21
32
|
import {
|
|
22
33
|
logger
|
|
23
34
|
} from "./chunk-Z5B5X7QP.js";
|
|
24
|
-
import {
|
|
25
|
-
findPositionForLine,
|
|
26
|
-
getValidPositions
|
|
27
|
-
} from "./chunk-I5S3NBFL.js";
|
|
28
|
-
import {
|
|
29
|
-
MAX_LOGGED_JSON_CHARS,
|
|
30
|
-
MIN_DISCRIMINATING_EVIDENCE_CHARS,
|
|
31
|
-
NON_ANSWER_MAX_RESPONSE_CHARS,
|
|
32
|
-
NON_ANSWER_MIN_DIFF_LINES,
|
|
33
|
-
SEVERITY_ORDER
|
|
34
|
-
} from "./chunk-W3RY755V.js";
|
|
35
35
|
|
|
36
36
|
// src/model-output/index.ts
|
|
37
37
|
import {
|
|
@@ -913,4 +913,4 @@ export {
|
|
|
913
913
|
groundParsedFindings,
|
|
914
914
|
parseFileReviewResponse
|
|
915
915
|
};
|
|
916
|
-
//# sourceMappingURL=chunk-
|
|
916
|
+
//# sourceMappingURL=chunk-4XEMXRNG.js.map
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RULES
|
|
3
3
|
} from "./chunk-JAF7JIZQ.js";
|
|
4
|
+
import {
|
|
5
|
+
MAX_RULE_SCAN_ADDED_LINES
|
|
6
|
+
} from "./chunk-W3RY755V.js";
|
|
4
7
|
import {
|
|
5
8
|
commentSyntaxFor,
|
|
6
9
|
stripCommentsAndStrings
|
|
@@ -11,9 +14,6 @@ import {
|
|
|
11
14
|
buildFindingFingerprintV2,
|
|
12
15
|
normalizeDiffText
|
|
13
16
|
} from "./chunk-XPYVLHSX.js";
|
|
14
|
-
import {
|
|
15
|
-
MAX_RULE_SCAN_ADDED_LINES
|
|
16
|
-
} from "./chunk-W3RY755V.js";
|
|
17
17
|
|
|
18
18
|
// src/rules/detect.ts
|
|
19
19
|
import { CLAIM_TYPE_CATEGORY } from "@codraoss/schema";
|
|
@@ -114,4 +114,4 @@ export {
|
|
|
114
114
|
scanFileForRuleHits,
|
|
115
115
|
ruleHitsToComments
|
|
116
116
|
};
|
|
117
|
-
//# sourceMappingURL=chunk-
|
|
117
|
+
//# sourceMappingURL=chunk-EQL5OOEF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ports/in-memory.ts"],"sourcesContent":["import type { KeyValueStore } from './kv';\nimport type { QueueProducer } from './queue';\nimport type { JobOrchestrator } from './orchestrator';\nimport type { SessionStore, DashboardSessionUser } from './session-store';\nimport type { ReviewJobMessage } from '@codraoss/schema';\n\nexport class InMemoryKV implements KeyValueStore {\n private store = new Map<string, { value: string; expiresAt?: number }>();\n\n async put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void> {\n const expiresAt = options?.expirationTtl ? Date.now() + options.expirationTtl * 1000 : undefined;\n this.store.set(key, { value, expiresAt });\n }\n\n async get(key: string, type?: 'json' | 'text'): Promise<any> {\n const entry = this.store.get(key);\n if (!entry) return null;\n if (entry.expiresAt && Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return null;\n }\n if (type === 'json') {\n try { return JSON.parse(entry.value); } catch { return null; }\n }\n return entry.value;\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n}\n\nexport class InMemoryQueue<T> implements QueueProducer<T> {\n public messages: Array<{ message: T; delaySeconds?: number }> = [];\n\n async send(message: T, options?: { delaySeconds?: number }): Promise<void> {\n this.messages.push({ message, delaySeconds: options?.delaySeconds });\n }\n}\n\nexport class InMemoryOrchestrator implements JobOrchestrator {\n public jobs: Map<string, ReviewJobMessage> = new Map();\n\n async startReviewJob(id: string, params: ReviewJobMessage): Promise<void> {\n this.jobs.set(id, params);\n }\n}\n\nexport class InMemorySessionStore implements SessionStore {\n private kv = new InMemoryKV();\n\n async createSession(session: DashboardSessionUser): Promise<string> {\n const token = Math.random().toString(36).substring(2);\n await this.kv.put(`session:${token}`, JSON.stringify(session), { expirationTtl: 60 * 60 * 24 * 7 });\n return token;\n }\n\n async readSession(token: string): Promise<DashboardSessionUser | null> {\n return this.kv.get(`session:${token}`, 'json');\n }\n\n async destroySession(token: string): Promise<void> {\n await this.kv.delete(`session:${token}`);\n }\n\n async renewSession(token: string): Promise<void> {\n const session = await this.readSession(token);\n if (session) {\n await this.kv.put(`session:${token}`, JSON.stringify(session), { expirationTtl: 60 * 60 * 24 * 7 });\n }\n }\n}\n"],"mappings":";AAMO,IAAM,aAAN,MAA0C;AAAA,EACvC,QAAQ,oBAAI,IAAmD;AAAA,EAEvE,MAAM,IAAI,KAAa,OAAe,SAAqD;AACzF,UAAM,YAAY,SAAS,gBAAgB,KAAK,IAAI,IAAI,QAAQ,gBAAgB,MAAO;AACvF,SAAK,MAAM,IAAI,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,EAC1C;AAAA,EAEA,MAAM,IAAI,KAAa,MAAsC;AAC3D,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,MAAM,aAAa,KAAK,IAAI,IAAI,MAAM,WAAW;AACnD,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AACA,QAAI,SAAS,QAAQ;AACnB,UAAI;AAAE,eAAO,KAAK,MAAM,MAAM,KAAK;AAAA,MAAG,QAAQ;AAAE,eAAO;AAAA,MAAM;AAAA,IAC/D;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AACF;AAEO,IAAM,gBAAN,MAAmD;AAAA,EACjD,WAAyD,CAAC;AAAA,EAEjE,MAAM,KAAK,SAAY,SAAoD;AACzE,SAAK,SAAS,KAAK,EAAE,SAAS,cAAc,SAAS,aAAa,CAAC;AAAA,EACrE;AACF;AAEO,IAAM,uBAAN,MAAsD;AAAA,EACpD,OAAsC,oBAAI,IAAI;AAAA,EAErD,MAAM,eAAe,IAAY,QAAyC;AACxE,SAAK,KAAK,IAAI,IAAI,MAAM;AAAA,EAC1B;AACF;AAEO,IAAM,uBAAN,MAAmD;AAAA,EAChD,KAAK,IAAI,WAAW;AAAA,EAE5B,MAAM,cAAc,SAAgD;AAClE,UAAM,QAAQ,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,CAAC;AACpD,UAAM,KAAK,GAAG,IAAI,WAAW,KAAK,IAAI,KAAK,UAAU,OAAO,GAAG,EAAE,eAAe,KAAK,KAAK,KAAK,EAAE,CAAC;AAClG,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,OAAqD;AACrE,WAAO,KAAK,GAAG,IAAI,WAAW,KAAK,IAAI,MAAM;AAAA,EAC/C;AAAA,EAEA,MAAM,eAAe,OAA8B;AACjD,UAAM,KAAK,GAAG,OAAO,WAAW,KAAK,EAAE;AAAA,EACzC;AAAA,EAEA,MAAM,aAAa,OAA8B;AAC/C,UAAM,UAAU,MAAM,KAAK,YAAY,KAAK;AAC5C,QAAI,SAAS;AACX,YAAM,KAAK,GAAG,IAAI,WAAW,KAAK,IAAI,KAAK,UAAU,OAAO,GAAG,EAAE,eAAe,KAAK,KAAK,KAAK,EAAE,CAAC;AAAA,IACpG;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ParsedReviewComment } from '@codraoss/schema';
|
|
2
|
+
|
|
3
|
+
interface ReviewFormatter {
|
|
4
|
+
toReviewEvent(verdict: 'approve' | 'comment'): 'APPROVE' | 'COMMENT';
|
|
5
|
+
summarizeVerdict(comments: ParsedReviewComment[], hasFailures: boolean): {
|
|
6
|
+
verdict: 'approve' | 'comment';
|
|
7
|
+
errors: number;
|
|
8
|
+
warnings: number;
|
|
9
|
+
};
|
|
10
|
+
formatInlineComment(comment: ParsedReviewComment): string;
|
|
11
|
+
formatReviewOverview(input: ReviewOverviewInput): string;
|
|
12
|
+
}
|
|
13
|
+
type ReviewOverviewInput = {
|
|
14
|
+
commitSha: string;
|
|
15
|
+
/** Comments actually posted. Zero means the header must not promise suggestions. */
|
|
16
|
+
postedFindings: number;
|
|
17
|
+
filesReviewed: number;
|
|
18
|
+
linesReviewed: number;
|
|
19
|
+
/** Candidates the gates dropped; on a clean review this is what "nothing to report" cost. */
|
|
20
|
+
withheldFindings: number;
|
|
21
|
+
filesFailed: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type { ReviewOverviewInput as R, ReviewFormatter as a };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ParsedReviewComment } from '@codraoss/schema';
|
|
2
|
+
import { R as ReviewOverviewInput } from './formatter-CDFpqbDP.js';
|
|
3
|
+
|
|
4
|
+
declare function formatFindingMarker(comment: Pick<ParsedReviewComment, 'fingerprint' | 'anchorHash' | 'fingerprintV2'>): string;
|
|
5
|
+
declare function parseFindingMarker(body: string | null | undefined): {
|
|
6
|
+
fingerprint: string;
|
|
7
|
+
anchorHash: string | null;
|
|
8
|
+
fingerprintV2: string | null;
|
|
9
|
+
} | null;
|
|
10
|
+
declare class FormatterService {
|
|
11
|
+
private baseUrl;
|
|
12
|
+
constructor(baseUrl: string);
|
|
13
|
+
toReviewEvent(verdict: 'approve' | 'comment'): "APPROVE" | "COMMENT";
|
|
14
|
+
severityIcon(severity: ParsedReviewComment['severity']): string;
|
|
15
|
+
stripLeadingTags(text: string): string;
|
|
16
|
+
formatInlineComment(comment: ParsedReviewComment): string;
|
|
17
|
+
summarizeVerdict(comments: ParsedReviewComment[], hasFailures: boolean): {
|
|
18
|
+
verdict: "comment";
|
|
19
|
+
errors: number;
|
|
20
|
+
warnings: number;
|
|
21
|
+
} | {
|
|
22
|
+
verdict: "approve";
|
|
23
|
+
errors: number;
|
|
24
|
+
warnings: number;
|
|
25
|
+
};
|
|
26
|
+
formatReviewOverview(input: ReviewOverviewInput): string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { FormatterService, formatFindingMarker, parseFindingMarker };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/formatter.ts
|
|
2
|
+
var FINDING_MARKER_PATTERN = /<!--\s*codra-fp:([0-9a-f]+):([0-9a-f]*)(?::([0-9a-f]*))?\s*-->/;
|
|
3
|
+
function formatFindingMarker(comment) {
|
|
4
|
+
if (!comment.fingerprint) return "";
|
|
5
|
+
return `
|
|
6
|
+
|
|
7
|
+
<!-- codra-fp:${comment.fingerprint}:${comment.anchorHash ?? ""}:${comment.fingerprintV2 ?? ""} -->`;
|
|
8
|
+
}
|
|
9
|
+
function parseFindingMarker(body) {
|
|
10
|
+
if (!body) return null;
|
|
11
|
+
const match = FINDING_MARKER_PATTERN.exec(body);
|
|
12
|
+
if (!match) return null;
|
|
13
|
+
return { fingerprint: match[1], anchorHash: match[2] || null, fingerprintV2: match[3] || null };
|
|
14
|
+
}
|
|
15
|
+
var FormatterService = class {
|
|
16
|
+
constructor(baseUrl) {
|
|
17
|
+
this.baseUrl = baseUrl;
|
|
18
|
+
}
|
|
19
|
+
toReviewEvent(verdict) {
|
|
20
|
+
return verdict === "approve" ? "APPROVE" : "COMMENT";
|
|
21
|
+
}
|
|
22
|
+
severityIcon(severity) {
|
|
23
|
+
const iconBase = `${this.baseUrl}/icons`;
|
|
24
|
+
const img = (name, alt) => `<img src="${iconBase}/${name}-icon.svg" width="20" height="20" alt="${alt}" style="vertical-align:middle" />`;
|
|
25
|
+
switch (severity) {
|
|
26
|
+
case "P0":
|
|
27
|
+
return img("p0", "P0");
|
|
28
|
+
case "P1":
|
|
29
|
+
return img("p1", "P1");
|
|
30
|
+
case "P2":
|
|
31
|
+
return img("p2", "P2");
|
|
32
|
+
case "P3":
|
|
33
|
+
return img("p3", "P3");
|
|
34
|
+
case "nit":
|
|
35
|
+
return img("nit", "nit");
|
|
36
|
+
default:
|
|
37
|
+
return "\u26AA";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Mirrors model-output cleanText; keep both in sync.
|
|
41
|
+
stripLeadingTags(text) {
|
|
42
|
+
let current = text.trim();
|
|
43
|
+
let prev = "";
|
|
44
|
+
while (current !== prev) {
|
|
45
|
+
prev = current;
|
|
46
|
+
current = current.replace(/^([\u{1F300}-\u{1F9FF}\u{2600}-\u{27BF}\u{FE00}-\u{FEFF}]|\[QUALITY\]|\[SECURITY\]|\[BUG\]|\[P[0-3]\]|\[NIT\]|QUALITY|SECURITY|BUG|P[0-3]|NIT|[:\-\s\uFE0F]|[^\w\s])+/giu, "").trim();
|
|
47
|
+
}
|
|
48
|
+
return current;
|
|
49
|
+
}
|
|
50
|
+
formatInlineComment(comment) {
|
|
51
|
+
let body = this.stripLeadingTags(comment.body);
|
|
52
|
+
const firstLine = body.split("\n")[0].trim();
|
|
53
|
+
const cleanFirstLine = this.stripLeadingTags(firstLine);
|
|
54
|
+
if (cleanFirstLine.toLowerCase().startsWith(comment.title.toLowerCase()) || comment.title.toLowerCase().startsWith(cleanFirstLine.toLowerCase())) {
|
|
55
|
+
body = body.slice(firstLine.length).replace(/^[\n\r]+/, "");
|
|
56
|
+
}
|
|
57
|
+
return `${this.severityIcon(comment.severity)} <strong>${comment.title}</strong>
|
|
58
|
+
|
|
59
|
+
${body}${formatFindingMarker(comment)}`;
|
|
60
|
+
}
|
|
61
|
+
summarizeVerdict(comments, hasFailures) {
|
|
62
|
+
const p0 = comments.filter((c) => c.severity === "P0").length;
|
|
63
|
+
const p1 = comments.filter((c) => c.severity === "P1").length;
|
|
64
|
+
const p2 = comments.filter((c) => c.severity === "P2").length;
|
|
65
|
+
if (p0 > 0 || p1 > 0 || hasFailures || p2 > 0) {
|
|
66
|
+
return { verdict: "comment", errors: p0 + p1, warnings: p2 };
|
|
67
|
+
}
|
|
68
|
+
return { verdict: "approve", errors: 0, warnings: 0 };
|
|
69
|
+
}
|
|
70
|
+
formatReviewOverview(input) {
|
|
71
|
+
const { commitSha, postedFindings, filesReviewed, linesReviewed } = input;
|
|
72
|
+
const shortSha = commitSha.slice(0, 10);
|
|
73
|
+
const plural = (n, one, many = one + "s") => `${n} ${n === 1 ? one : many}`;
|
|
74
|
+
const headline = postedFindings === 0 ? `\u2705 **Nothing to flag.** Reviewed ${plural(filesReviewed, "file")} (${plural(linesReviewed, "changed line")}) and found no issues worth raising.` : "Here are some automated review suggestions for this pull request.";
|
|
75
|
+
const notes = [];
|
|
76
|
+
if (input.filesFailed > 0) {
|
|
77
|
+
notes.push(`${plural(input.filesFailed, "file")} could not be reviewed, so this pass is incomplete.`);
|
|
78
|
+
}
|
|
79
|
+
if (postedFindings === 0 && input.withheldFindings > 0) {
|
|
80
|
+
notes.push(`${plural(input.withheldFindings, "candidate")} did not survive the evidence and claim gates \u2014 see the [dashboard](${this.baseUrl}) for what was dropped and why.`);
|
|
81
|
+
}
|
|
82
|
+
const noteBlock = notes.length > 0 ? "\n" + notes.map((line) => `> [!NOTE]
|
|
83
|
+
> ${line}`).join("\n\n") + "\n" : "";
|
|
84
|
+
const aboutOutcome = postedFindings === 0 ? "Every review posts a summary here. A clean pass also gets a \u{1F44D} on the pull request itself." : "If Codra has suggestions, it will comment; otherwise it will react with \u{1F44D}.";
|
|
85
|
+
return `### Codra Review
|
|
86
|
+
|
|
87
|
+
${headline}
|
|
88
|
+
${noteBlock}
|
|
89
|
+
**Reviewed commit:** \`${shortSha}\`
|
|
90
|
+
|
|
91
|
+
<details>
|
|
92
|
+
<summary>\u2139\uFE0F About Codra in GitHub</summary>
|
|
93
|
+
|
|
94
|
+
<br/>
|
|
95
|
+
|
|
96
|
+
[Your team has set up Codra to review pull requests in this repo](${this.baseUrl}/repos). Reviews are triggered when you:
|
|
97
|
+
|
|
98
|
+
- **Open** a pull request for review
|
|
99
|
+
- **Mark** a draft as ready
|
|
100
|
+
|
|
101
|
+
${aboutOutcome}
|
|
102
|
+
|
|
103
|
+
</details>`;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
export {
|
|
107
|
+
FormatterService,
|
|
108
|
+
formatFindingMarker,
|
|
109
|
+
parseFindingMarker
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/formatter.ts"],"sourcesContent":["import type { ParsedReviewComment } from '@codraoss/schema';\nimport type { ReviewOverviewInput } from './ports';\n\n// The third field is OPTIONAL so every comment already on GitHub still parses; requiring it would silently stop recording deletions of historical comments.\nconst FINDING_MARKER_PATTERN = /<!--\\s*codra-fp:([0-9a-f]+):([0-9a-f]*)(?::([0-9a-f]*))?\\s*-->/;\n\n// Matching on (path, line) instead would fail exactly when it matters most: GitHub nulls `line` once a comment goes outdated.\nexport function formatFindingMarker(\n comment: Pick<ParsedReviewComment, 'fingerprint' | 'anchorHash' | 'fingerprintV2'>,\n) {\n if (!comment.fingerprint) return '';\n return `\\n\\n<!-- codra-fp:${comment.fingerprint}:${comment.anchorHash ?? ''}:${comment.fingerprintV2 ?? ''} -->`;\n}\n\nexport function parseFindingMarker(body: string | null | undefined) {\n if (!body) return null;\n const match = FINDING_MARKER_PATTERN.exec(body);\n if (!match) return null;\n return { fingerprint: match[1], anchorHash: match[2] || null, fingerprintV2: match[3] || null };\n}\n\nexport class FormatterService {\n constructor(private baseUrl: string) {}\n\n toReviewEvent(verdict: 'approve' | 'comment') {\n return verdict === 'approve' ? 'APPROVE' as const : 'COMMENT' as const;\n }\n\n severityIcon(severity: ParsedReviewComment['severity']) {\n const iconBase = `${this.baseUrl}/icons`;\n const img = (name: string, alt: string) =>\n `<img src=\"${iconBase}/${name}-icon.svg\" width=\"20\" height=\"20\" alt=\"${alt}\" style=\"vertical-align:middle\" />`;\n switch (severity) {\n case 'P0': return img('p0', 'P0');\n case 'P1': return img('p1', 'P1');\n case 'P2': return img('p2', 'P2');\n case 'P3': return img('p3', 'P3');\n case 'nit': return img('nit', 'nit');\n default: return '⚪';\n }\n }\n\n // Mirrors model-output cleanText; keep both in sync.\n stripLeadingTags(text: string): string {\n let current = text.trim();\n let prev = '';\n while (current !== prev) {\n prev = current;\n current = current\n .replace(/^([\\u{1F300}-\\u{1F9FF}\\u{2600}-\\u{27BF}\\u{FE00}-\\u{FEFF}]|\\[QUALITY\\]|\\[SECURITY\\]|\\[BUG\\]|\\[P[0-3]\\]|\\[NIT\\]|QUALITY|SECURITY|BUG|P[0-3]|NIT|[:\\-\\s\\uFE0F]|[^\\w\\s])+/giu, '')\n .trim();\n }\n return current;\n }\n\n formatInlineComment(comment: ParsedReviewComment) {\n // Removes a leading line duplicating the title, which can happen with stale DB records.\n let body = this.stripLeadingTags(comment.body);\n const firstLine = body.split('\\n')[0].trim();\n const cleanFirstLine = this.stripLeadingTags(firstLine);\n if (\n cleanFirstLine.toLowerCase().startsWith(comment.title.toLowerCase()) ||\n comment.title.toLowerCase().startsWith(cleanFirstLine.toLowerCase())\n ) {\n body = body.slice(firstLine.length).replace(/^[\\n\\r]+/, '');\n }\n\n return `${this.severityIcon(comment.severity)} <strong>${comment.title}</strong>\\n\\n${body}${formatFindingMarker(comment)}`;\n }\n\n summarizeVerdict(comments: ParsedReviewComment[], hasFailures: boolean) {\n const p0 = comments.filter((c) => c.severity === 'P0').length;\n const p1 = comments.filter((c) => c.severity === 'P1').length;\n const p2 = comments.filter((c) => c.severity === 'P2').length;\n\n if (p0 > 0 || p1 > 0 || hasFailures || p2 > 0) {\n return { verdict: 'comment' as const, errors: p0 + p1, warnings: p2 };\n }\n\n return { verdict: 'approve' as const, errors: 0, warnings: 0 };\n }\n\n formatReviewOverview(input: ReviewOverviewInput) {\n const { commitSha, postedFindings, filesReviewed, linesReviewed } = input;\n const shortSha = commitSha.slice(0, 10);\n const plural = (n: number, one: string, many = one + 's') => `${n} ${n === 1 ? one : many}`;\n\n // A clean review used to say \"here are some automated review suggestions\" and then list none,\n // which reads as a failure rather than a pass. Say what was checked and that nothing came of it.\n // With findings the original wording stays.\n const headline = postedFindings === 0\n ? `✅ **Nothing to flag.** Reviewed ${plural(filesReviewed, 'file')} (${plural(linesReviewed, 'changed line')}) and found no issues worth raising.`\n : 'Here are some automated review suggestions for this pull request.';\n\n const notes: string[] = [];\n if (input.filesFailed > 0) {\n notes.push(`${plural(input.filesFailed, 'file')} could not be reviewed, so this pass is incomplete.`);\n }\n if (postedFindings === 0 && input.withheldFindings > 0) {\n // \"No issues\" is a weaker claim when candidates were dropped for failing to ground themselves,\n // and every one of them is on the dashboard.\n notes.push(`${plural(input.withheldFindings, 'candidate')} did not survive the evidence and claim gates — see the [dashboard](${this.baseUrl}) for what was dropped and why.`);\n }\n \n const noteBlock = notes.length > 0\n ? '\\n' + notes.map((line) => `> [!NOTE]\\n> ${line}`).join('\\n\\n') + '\\n'\n : '';\n\n const aboutOutcome = postedFindings === 0\n ? 'Every review posts a summary here. A clean pass also gets a 👍 on the pull request itself.'\n : 'If Codra has suggestions, it will comment; otherwise it will react with 👍.';\n\n return `### Codra Review\\n\\n${headline}\\n${noteBlock}\\n**Reviewed commit:** \\`${shortSha}\\`\\n\\n<details>\\n<summary>ℹ️ About Codra in GitHub</summary>\\n\\n<br/>\\n\\n[Your team has set up Codra to review pull requests in this repo](${this.baseUrl}/repos). Reviews are triggered when you:\\n\\n- **Open** a pull request for review\\n- **Mark** a draft as ready\\n\\n${aboutOutcome}\\n\\n</details>`;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { RepoConfig, ParsedReviewComment, FindingDisposition, ReviewJobMessage } from '@codraoss/schema';
|
|
2
2
|
import { ReviewRuntime, ReviewGitProvider, PersistedReviewJob } from './ports/index.js';
|
|
3
|
-
export { AuthorizationResult, BulkFileReviewInput, Clock, DashboardSessionUser, FileReviewRow, FileReviewStore, GitProviderFactory, IdGenerator, IdentityProvider, InMemoryKV, InMemoryOrchestrator, InMemoryQueue, InMemorySessionStore, InstanceIdStore, JobLeaseClaim, JobOrchestrator, JobRow, JobStore, KeyValueStore, KvStore, LearningStore, ModelConfigReader, PullRequestRecord, QueueProducer, RepoConfigLoader, RepoConfigStore, ReviewComment,
|
|
3
|
+
export { AuthorizationResult, BulkFileReviewInput, Clock, DashboardSessionUser, FileReviewRow, FileReviewStore, GitProviderFactory, IdGenerator, IdentityProvider, InMemoryKV, InMemoryOrchestrator, InMemoryQueue, InMemorySessionStore, InstanceIdStore, JobLeaseClaim, JobOrchestrator, JobRow, JobStore, KeyValueStore, KvStore, LearningStore, ModelConfigReader, PullRequestRecord, QueueProducer, RepoConfigLoader, RepoConfigStore, ReviewComment, ReviewSettingsReader, ReviewTelemetryEvent, SecretStore, SessionStore, SuppressedFinding, TelemetrySink, WebhookDeliveryReader } from './ports/index.js';
|
|
4
4
|
import { R as ReviewModel } from './model-BkqfyVh9.js';
|
|
5
5
|
export { F as FileReviewOutcome, M as ModelErrorClassifier, a as ModelResponse, b as ModelResponseSchema } from './model-BkqfyVh9.js';
|
|
6
|
+
export { a as ReviewFormatter, R as ReviewOverviewInput } from './formatter-CDFpqbDP.js';
|
|
6
7
|
export { a as BIN_DIFF_CHAR_BUDGET, b as BIN_MAX_FILES, c as BIN_TARGET_DIFF_LINES, F as FRESH_INVOCATION_YIELD_SECONDS, P as PACKABLE_MAX_DIFF_LINES } from './index-CrtHz4vN.js';
|
|
7
8
|
import { F as FileDiff } from './position-zZl8KGVu.js';
|
|
8
9
|
export { Logger } from './logger.js';
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dedupeFindings
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4XEMXRNG.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
InMemorySessionStore
|
|
9
|
-
} from "./chunk-JC74XH7Q.js";
|
|
10
|
-
import {
|
|
11
|
-
ruleHitsToComments,
|
|
12
|
-
scanFileForRuleHits
|
|
13
|
-
} from "./chunk-754NO4DH.js";
|
|
14
|
-
import "./chunk-JAF7JIZQ.js";
|
|
5
|
+
filterReviewableFiles,
|
|
6
|
+
parseUnifiedDiff
|
|
7
|
+
} from "./chunk-I5S3NBFL.js";
|
|
15
8
|
import {
|
|
16
9
|
changelogExcerptFromDiff,
|
|
17
10
|
renderFileDiff,
|
|
@@ -25,16 +18,17 @@ import {
|
|
|
25
18
|
parseVerifyResponse,
|
|
26
19
|
renderDiffSnippet
|
|
27
20
|
} from "./chunk-KTDBDKL5.js";
|
|
28
|
-
import "./chunk-IKVUW4WC.js";
|
|
29
|
-
import "./chunk-XPYVLHSX.js";
|
|
30
|
-
import "./chunk-QPPLO2YV.js";
|
|
31
21
|
import {
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
InMemoryKV,
|
|
23
|
+
InMemoryOrchestrator,
|
|
24
|
+
InMemoryQueue,
|
|
25
|
+
InMemorySessionStore
|
|
26
|
+
} from "./chunk-GDX2ZEBA.js";
|
|
34
27
|
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from "./chunk-
|
|
28
|
+
ruleHitsToComments,
|
|
29
|
+
scanFileForRuleHits
|
|
30
|
+
} from "./chunk-EQL5OOEF.js";
|
|
31
|
+
import "./chunk-JAF7JIZQ.js";
|
|
38
32
|
import {
|
|
39
33
|
ASYNC_BATCH_POLL_DELAY_SECONDS,
|
|
40
34
|
BIN_DIFF_CHAR_BUDGET,
|
|
@@ -57,6 +51,12 @@ import {
|
|
|
57
51
|
REVIEW_CHUNK_WALL_CLOCK_MS,
|
|
58
52
|
VERIFY_MIN_ANSWER_RATIO
|
|
59
53
|
} from "./chunk-W3RY755V.js";
|
|
54
|
+
import "./chunk-IKVUW4WC.js";
|
|
55
|
+
import "./chunk-XPYVLHSX.js";
|
|
56
|
+
import "./chunk-QPPLO2YV.js";
|
|
57
|
+
import {
|
|
58
|
+
logger
|
|
59
|
+
} from "./chunk-Z5B5X7QP.js";
|
|
60
60
|
|
|
61
61
|
// src/review/index.ts
|
|
62
62
|
import "@codraoss/schema/webhook";
|
|
@@ -222,8 +222,6 @@ var NextPhaseError = class extends Error {
|
|
|
222
222
|
this.phase = phase;
|
|
223
223
|
this.delaySeconds = delaySeconds;
|
|
224
224
|
}
|
|
225
|
-
phase;
|
|
226
|
-
delaySeconds;
|
|
227
225
|
};
|
|
228
226
|
async function enqueueJobPhase(env, jobId, phase, delaySeconds = 0) {
|
|
229
227
|
await env.jobs.markJobContinuationQueued(jobId, delaySeconds);
|