@codraoss/core 0.9.4
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 +625 -0
- package/README.md +15 -0
- package/dist/chunk-FGXA7JNR.js +57 -0
- package/dist/chunk-FGXA7JNR.js.map +1 -0
- package/dist/chunk-ILZUCIZH.js +117 -0
- package/dist/chunk-ILZUCIZH.js.map +1 -0
- package/dist/chunk-JC74XH7Q.js +67 -0
- package/dist/chunk-JC74XH7Q.js.map +1 -0
- package/dist/chunk-MMVQPGK3.js +341 -0
- package/dist/chunk-MMVQPGK3.js.map +1 -0
- package/dist/chunk-NSTV4TRP.js +39 -0
- package/dist/chunk-NSTV4TRP.js.map +1 -0
- package/dist/chunk-OIQRTDYR.js +337 -0
- package/dist/chunk-OIQRTDYR.js.map +1 -0
- package/dist/chunk-PVXFURDR.js +90 -0
- package/dist/chunk-PVXFURDR.js.map +1 -0
- package/dist/chunk-R2HRWFRH.js +108 -0
- package/dist/chunk-R2HRWFRH.js.map +1 -0
- package/dist/chunk-RQ5S67XB.js +82 -0
- package/dist/chunk-RQ5S67XB.js.map +1 -0
- package/dist/chunk-U3VKVDZ7.js +266 -0
- package/dist/chunk-U3VKVDZ7.js.map +1 -0
- package/dist/chunk-V3RF6GQW.js +137 -0
- package/dist/chunk-V3RF6GQW.js.map +1 -0
- package/dist/chunk-WSK45HW4.js +79 -0
- package/dist/chunk-WSK45HW4.js.map +1 -0
- package/dist/chunk-XD6CGYHO.js +893 -0
- package/dist/chunk-XD6CGYHO.js.map +1 -0
- package/dist/claim-checks.d.ts +54 -0
- package/dist/claim-checks.js +20 -0
- package/dist/claim-checks.js.map +1 -0
- package/dist/diff/index.d.ts +22 -0
- package/dist/diff/index.js +25 -0
- package/dist/diff/index.js.map +1 -0
- package/dist/fingerprint.d.ts +9 -0
- package/dist/fingerprint.js +19 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index-BTDWXDck.d.ts +93 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +1755 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.js +17 -0
- package/dist/logger.js.map +1 -0
- package/dist/model-gRDTk8yk.d.ts +117 -0
- package/dist/model-output/index.d.ts +4 -0
- package/dist/model-output/index.js +32 -0
- package/dist/model-output/index.js.map +1 -0
- package/dist/ports/index.d.ts +512 -0
- package/dist/ports/index.js +13 -0
- package/dist/ports/index.js.map +1 -0
- package/dist/position-h_jdn6ZH.d.ts +30 -0
- package/dist/prompts/file-review.d.ts +6 -0
- package/dist/prompts/file-review.js +25 -0
- package/dist/prompts/file-review.js.map +1 -0
- package/dist/prompts/languages.d.ts +9 -0
- package/dist/prompts/languages.js +7 -0
- package/dist/prompts/languages.js.map +1 -0
- package/dist/prompts/summary.d.ts +12 -0
- package/dist/prompts/summary.js +50 -0
- package/dist/prompts/summary.js.map +1 -0
- package/dist/prompts/verify.d.ts +71 -0
- package/dist/prompts/verify.js +15 -0
- package/dist/prompts/verify.js.map +1 -0
- package/dist/rules/detect.d.ts +32 -0
- package/dist/rules/detect.js +13 -0
- package/dist/rules/detect.js.map +1 -0
- package/dist/rules/table.d.ts +18 -0
- package/dist/rules/table.js +7 -0
- package/dist/rules/table.js.map +1 -0
- package/dist/timeout.d.ts +6 -0
- package/dist/timeout.js +26 -0
- package/dist/timeout.js.map +1 -0
- package/dist/token-tracker.d.ts +38 -0
- package/dist/token-tracker.js +8 -0
- package/dist/token-tracker.js.map +1 -0
- package/dist/verify.d.ts +3 -0
- package/dist/verify.js +21 -0
- package/dist/verify.js.map +1 -0
- package/package.json +181 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var PACKABLE_MAX_DIFF_LINES = 150;
|
|
3
|
+
var BIN_TARGET_DIFF_LINES = 300;
|
|
4
|
+
var BIN_MAX_FILES = 4;
|
|
5
|
+
var BIN_DIFF_CHAR_BUDGET = 24e3;
|
|
6
|
+
var DIFF_CACHE_TTL_SECONDS = 6 * 60 * 60;
|
|
7
|
+
var REVIEW_CHUNK_WALL_CLOCK_MS = 12 * 60 * 1e3;
|
|
8
|
+
var JOB_LEASE_SECONDS = 15 * 60;
|
|
9
|
+
var BUSY_RETRY_SECONDS = 60;
|
|
10
|
+
var RETRYABLE_MODEL_FAILURE_RETRY_DELAYS_SECONDS = [30, 2 * 60, 5 * 60];
|
|
11
|
+
var FRESH_INVOCATION_YIELD_SECONDS = 8;
|
|
12
|
+
var ASYNC_BATCH_POLL_DELAY_SECONDS = 20;
|
|
13
|
+
var MAX_RETRYABLE_FILE_REVIEW_FAILURES = 6;
|
|
14
|
+
var MAX_JOB_CONTINUATIONS = 20;
|
|
15
|
+
var MAX_FINALIZE_CONTINUATIONS = 3;
|
|
16
|
+
var FILE_FIXED_SUBREQUESTS = 2;
|
|
17
|
+
var MAX_MODEL_ATTEMPTS_ESTIMATE = 4;
|
|
18
|
+
var MISSING_FILE_ERROR = "Model omitted this file from a batched review; retrying later.";
|
|
19
|
+
var MIN_DISCRIMINATING_EVIDENCE_CHARS = 8;
|
|
20
|
+
var NON_ANSWER_MAX_RESPONSE_CHARS = 600;
|
|
21
|
+
var NON_ANSWER_MIN_DIFF_LINES = 200;
|
|
22
|
+
var MAX_LOGGED_JSON_CHARS = 2e3;
|
|
23
|
+
var SEVERITY_ORDER = ["P0", "P1", "P2", "P3", "nit"];
|
|
24
|
+
var VERIFY_MIN_ANSWER_RATIO = 0.6;
|
|
25
|
+
var MAX_RULE_SCAN_ADDED_LINES = 600;
|
|
26
|
+
var EXEMPLAR_BLOCK_CHARS = 700;
|
|
27
|
+
var PR_DESCRIPTION_CHARS = 2e3;
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
PACKABLE_MAX_DIFF_LINES,
|
|
31
|
+
BIN_TARGET_DIFF_LINES,
|
|
32
|
+
BIN_MAX_FILES,
|
|
33
|
+
BIN_DIFF_CHAR_BUDGET,
|
|
34
|
+
DIFF_CACHE_TTL_SECONDS,
|
|
35
|
+
REVIEW_CHUNK_WALL_CLOCK_MS,
|
|
36
|
+
JOB_LEASE_SECONDS,
|
|
37
|
+
BUSY_RETRY_SECONDS,
|
|
38
|
+
RETRYABLE_MODEL_FAILURE_RETRY_DELAYS_SECONDS,
|
|
39
|
+
FRESH_INVOCATION_YIELD_SECONDS,
|
|
40
|
+
ASYNC_BATCH_POLL_DELAY_SECONDS,
|
|
41
|
+
MAX_RETRYABLE_FILE_REVIEW_FAILURES,
|
|
42
|
+
MAX_JOB_CONTINUATIONS,
|
|
43
|
+
MAX_FINALIZE_CONTINUATIONS,
|
|
44
|
+
FILE_FIXED_SUBREQUESTS,
|
|
45
|
+
MAX_MODEL_ATTEMPTS_ESTIMATE,
|
|
46
|
+
MISSING_FILE_ERROR,
|
|
47
|
+
MIN_DISCRIMINATING_EVIDENCE_CHARS,
|
|
48
|
+
NON_ANSWER_MAX_RESPONSE_CHARS,
|
|
49
|
+
NON_ANSWER_MIN_DIFF_LINES,
|
|
50
|
+
MAX_LOGGED_JSON_CHARS,
|
|
51
|
+
SEVERITY_ORDER,
|
|
52
|
+
VERIFY_MIN_ANSWER_RATIO,
|
|
53
|
+
MAX_RULE_SCAN_ADDED_LINES,
|
|
54
|
+
EXEMPLAR_BLOCK_CHARS,
|
|
55
|
+
PR_DESCRIPTION_CHARS
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=chunk-FGXA7JNR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts"],"sourcesContent":["// Packing & Diff Limits\nexport const PACKABLE_MAX_DIFF_LINES = 150;\nexport const BIN_TARGET_DIFF_LINES = 300;\nexport const BIN_MAX_FILES = 4;\nexport const BIN_DIFF_CHAR_BUDGET = 24_000;\nexport const DIFF_CACHE_TTL_SECONDS = 6 * 60 * 60;\n\n// Phase Control & Timers\nexport const REVIEW_CHUNK_WALL_CLOCK_MS = 12 * 60 * 1000;\nexport const JOB_LEASE_SECONDS = 15 * 60;\nexport const BUSY_RETRY_SECONDS = 60;\nexport const RETRYABLE_MODEL_FAILURE_RETRY_DELAYS_SECONDS = [30, 2 * 60, 5 * 60];\nexport const FRESH_INVOCATION_YIELD_SECONDS = 8;\nexport const ASYNC_BATCH_POLL_DELAY_SECONDS = 20;\nexport const MAX_RETRYABLE_FILE_REVIEW_FAILURES = 6;\nexport const MAX_JOB_CONTINUATIONS = 20;\nexport const MAX_FINALIZE_CONTINUATIONS = 3;\n\n// Budget & Attempts\nexport const FILE_FIXED_SUBREQUESTS = 2;\nexport const MAX_MODEL_ATTEMPTS_ESTIMATE = 4;\nexport const MISSING_FILE_ERROR = 'Model omitted this file from a batched review; retrying later.';\n\n// Model Output Parsing\nexport const MIN_DISCRIMINATING_EVIDENCE_CHARS = 8;\nexport const NON_ANSWER_MAX_RESPONSE_CHARS = 600;\nexport const NON_ANSWER_MIN_DIFF_LINES = 200;\nexport const MAX_LOGGED_JSON_CHARS = 2_000;\nexport const SEVERITY_ORDER = ['P0', 'P1', 'P2', 'P3', 'nit'] as const;\n\n// Finding Gates\nexport const VERIFY_MIN_ANSWER_RATIO = 0.6;\n\n// Rules\nexport const MAX_RULE_SCAN_ADDED_LINES = 600;\n\n// Prompts\nexport const EXEMPLAR_BLOCK_CHARS = 700;\nexport const PR_DESCRIPTION_CHARS = 2_000;\n"],"mappings":";AACO,IAAM,0BAA0B;AAChC,IAAM,wBAAwB;AAC9B,IAAM,gBAAgB;AACtB,IAAM,uBAAuB;AAC7B,IAAM,yBAAyB,IAAI,KAAK;AAGxC,IAAM,6BAA6B,KAAK,KAAK;AAC7C,IAAM,oBAAoB,KAAK;AAC/B,IAAM,qBAAqB;AAC3B,IAAM,+CAA+C,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE;AACxE,IAAM,iCAAiC;AACvC,IAAM,iCAAiC;AACvC,IAAM,qCAAqC;AAC3C,IAAM,wBAAwB;AAC9B,IAAM,6BAA6B;AAGnC,IAAM,yBAAyB;AAC/B,IAAM,8BAA8B;AACpC,IAAM,qBAAqB;AAG3B,IAAM,oCAAoC;AAC1C,IAAM,gCAAgC;AACtC,IAAM,4BAA4B;AAClC,IAAM,wBAAwB;AAC9B,IAAM,iBAAiB,CAAC,MAAM,MAAM,MAAM,MAAM,KAAK;AAGrD,IAAM,0BAA0B;AAGhC,IAAM,4BAA4B;AAGlC,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;","names":[]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_RULE_SCAN_ADDED_LINES
|
|
3
|
+
} from "./chunk-FGXA7JNR.js";
|
|
4
|
+
import {
|
|
5
|
+
RULES
|
|
6
|
+
} from "./chunk-R2HRWFRH.js";
|
|
7
|
+
import {
|
|
8
|
+
commentSyntaxFor,
|
|
9
|
+
stripCommentsAndStrings
|
|
10
|
+
} from "./chunk-U3VKVDZ7.js";
|
|
11
|
+
import {
|
|
12
|
+
buildAnchorHash,
|
|
13
|
+
buildFindingFingerprint,
|
|
14
|
+
buildFindingFingerprintV2,
|
|
15
|
+
normalizeDiffText
|
|
16
|
+
} from "./chunk-NSTV4TRP.js";
|
|
17
|
+
|
|
18
|
+
// src/rules/detect.ts
|
|
19
|
+
import { CLAIM_TYPE_CATEGORY } from "@codraoss/schema";
|
|
20
|
+
function extensionOf(path) {
|
|
21
|
+
return path.toLowerCase().split(".").pop() ?? "";
|
|
22
|
+
}
|
|
23
|
+
function ruleApplies(rule, ext) {
|
|
24
|
+
return !rule.extensions || rule.extensions.includes(ext);
|
|
25
|
+
}
|
|
26
|
+
function scanFileForRuleHits(file, options = {}) {
|
|
27
|
+
const stats = {
|
|
28
|
+
addedLinesScanned: 0,
|
|
29
|
+
sievePassed: 0,
|
|
30
|
+
hits: 0,
|
|
31
|
+
shadowHits: 0,
|
|
32
|
+
suppressedAsMoved: 0,
|
|
33
|
+
unstrippable: 0,
|
|
34
|
+
truncated: false,
|
|
35
|
+
byRule: {}
|
|
36
|
+
};
|
|
37
|
+
const hits = [];
|
|
38
|
+
if (file.isDeleted || file.isBinary || !file.path) return { hits, stats };
|
|
39
|
+
const ext = extensionOf(file.path);
|
|
40
|
+
const denied = new Set(options.deniedClaimTypes ?? []);
|
|
41
|
+
const disabled = new Set(options.disabledRuleIds ?? []);
|
|
42
|
+
const shadowIds = new Set(options.shadowRuleIds ?? []);
|
|
43
|
+
const active = RULES.filter((rule) => rule.enabled && !disabled.has(rule.id) && !denied.has(rule.claimType) && ruleApplies(rule, ext));
|
|
44
|
+
if (active.length === 0) return { hits, stats };
|
|
45
|
+
const triggers = [...new Set(active.flatMap((rule) => rule.triggers))];
|
|
46
|
+
const syntax = commentSyntaxFor(file.path);
|
|
47
|
+
for (const hunk of file.hunks) {
|
|
48
|
+
const removed = /* @__PURE__ */ new Set();
|
|
49
|
+
for (const l of hunk.lines) {
|
|
50
|
+
if (l.kind === "del") removed.add(normalizeDiffText(l.content));
|
|
51
|
+
}
|
|
52
|
+
for (const line of hunk.lines) {
|
|
53
|
+
if (line.kind !== "add") continue;
|
|
54
|
+
if (stats.addedLinesScanned >= MAX_RULE_SCAN_ADDED_LINES) {
|
|
55
|
+
stats.truncated = true;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
stats.addedLinesScanned += 1;
|
|
59
|
+
const raw = line.content;
|
|
60
|
+
if (!triggers.some((trigger) => raw.includes(trigger))) continue;
|
|
61
|
+
stats.sievePassed += 1;
|
|
62
|
+
const stripped = stripCommentsAndStrings(raw, syntax);
|
|
63
|
+
if (stripped === null) {
|
|
64
|
+
stats.unstrippable += 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const rule of active) {
|
|
68
|
+
if (!rule.triggers.some((trigger) => raw.includes(trigger))) continue;
|
|
69
|
+
if (!rule.pattern.test(stripped)) continue;
|
|
70
|
+
if (rule.rejectRaw?.test(raw)) continue;
|
|
71
|
+
if (removed.has(normalizeDiffText(raw))) {
|
|
72
|
+
stats.suppressedAsMoved += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const shadow = shadowIds.has(rule.id);
|
|
76
|
+
hits.push({ rule, line, shadow });
|
|
77
|
+
stats.byRule[rule.id] = (stats.byRule[rule.id] ?? 0) + 1;
|
|
78
|
+
if (shadow) stats.shadowHits += 1;
|
|
79
|
+
else stats.hits += 1;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (stats.truncated) break;
|
|
84
|
+
}
|
|
85
|
+
return { hits, stats };
|
|
86
|
+
}
|
|
87
|
+
function ruleHitsToComments(file, result) {
|
|
88
|
+
const comments = [];
|
|
89
|
+
for (const hit of result.hits) {
|
|
90
|
+
if (hit.shadow) continue;
|
|
91
|
+
const { rule, line } = hit;
|
|
92
|
+
const anchorHash = buildAnchorHash(line.content);
|
|
93
|
+
comments.push({
|
|
94
|
+
path: file.path,
|
|
95
|
+
line: line.newLineNumber ?? null,
|
|
96
|
+
position: line.position ?? null,
|
|
97
|
+
severity: rule.severity,
|
|
98
|
+
category: CLAIM_TYPE_CATEGORY[rule.claimType] ?? "quality",
|
|
99
|
+
title: rule.title,
|
|
100
|
+
body: rule.body,
|
|
101
|
+
evidence: line.content,
|
|
102
|
+
anchorHash,
|
|
103
|
+
claimType: rule.claimType,
|
|
104
|
+
fingerprint: buildFindingFingerprint(file.path, `${rule.title} @${anchorHash}`),
|
|
105
|
+
fingerprintV2: buildFindingFingerprintV2(file.path, rule.claimType, anchorHash),
|
|
106
|
+
source: "rule",
|
|
107
|
+
ruleId: rule.id
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return comments;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
scanFileForRuleHits,
|
|
115
|
+
ruleHitsToComments
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=chunk-ILZUCIZH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rules/detect.ts"],"sourcesContent":["import type { ClaimType, ParsedReviewComment } from '@codraoss/schema';\r\nimport type { DiffLine, FileDiff } from '../diff';\r\nimport { commentSyntaxFor, stripCommentsAndStrings } from '../claim-checks';\r\nimport { buildAnchorHash, buildFindingFingerprint, buildFindingFingerprintV2, normalizeDiffText } from '../fingerprint';\r\nimport { CLAIM_TYPE_CATEGORY } from '@codraoss/schema';\r\nimport { RULES, type Rule } from './table';\r\n\r\nimport { MAX_RULE_SCAN_ADDED_LINES } from '../constants';\r\n\r\nexport type RuleHit = {\r\n rule: Rule;\r\n line: DiffLine;\r\n shadow: boolean;\r\n};\r\n\r\nexport type RuleScanStats = {\r\n addedLinesScanned: number;\r\n sievePassed: number;\r\n hits: number;\r\n shadowHits: number;\r\n suppressedAsMoved: number;\r\n unstrippable: number;\r\n truncated: boolean;\r\n byRule: Record<string, number>;\r\n};\r\n\r\nexport type RuleScanResult = { hits: RuleHit[]; stats: RuleScanStats };\r\n\r\nexport type RuleScanOptions = {\r\n disabledRuleIds?: readonly string[];\r\n shadowRuleIds?: readonly string[];\r\n deniedClaimTypes?: readonly ClaimType[];\r\n};\r\n\r\nfunction extensionOf(path: string) {\r\n return path.toLowerCase().split('.').pop() ?? '';\r\n}\r\n\r\nfunction ruleApplies(rule: Rule, ext: string) {\r\n return !rule.extensions || rule.extensions.includes(ext);\r\n}\r\n\r\nexport function scanFileForRuleHits(file: FileDiff, options: RuleScanOptions = {}): RuleScanResult {\r\n const stats: RuleScanStats = {\r\n addedLinesScanned: 0,\r\n sievePassed: 0,\r\n hits: 0,\r\n shadowHits: 0,\r\n suppressedAsMoved: 0,\r\n unstrippable: 0,\r\n truncated: false,\r\n byRule: {},\r\n };\r\n const hits: RuleHit[] = [];\r\n\r\n if (file.isDeleted || file.isBinary || !file.path) return { hits, stats };\r\n\r\n const ext = extensionOf(file.path);\r\n const denied = new Set(options.deniedClaimTypes ?? []);\r\n const disabled = new Set(options.disabledRuleIds ?? []);\r\n const shadowIds = new Set(options.shadowRuleIds ?? []);\r\n\r\n const active = RULES.filter((rule) =>\r\n rule.enabled\r\n && !disabled.has(rule.id)\r\n && !denied.has(rule.claimType)\r\n && ruleApplies(rule, ext));\r\n if (active.length === 0) return { hits, stats };\r\n\r\n const triggers = [...new Set(active.flatMap((rule) => rule.triggers))];\r\n const syntax = commentSyntaxFor(file.path);\r\n\r\n for (const hunk of file.hunks) {\r\n const removed = new Set<string>();\r\n for (const l of hunk.lines) {\r\n if (l.kind === 'del') removed.add(normalizeDiffText(l.content));\r\n }\r\n\r\n for (const line of hunk.lines) {\r\n if (line.kind !== 'add') continue;\r\n if (stats.addedLinesScanned >= MAX_RULE_SCAN_ADDED_LINES) {\r\n stats.truncated = true;\r\n break;\r\n }\r\n stats.addedLinesScanned += 1;\r\n\r\n const raw = line.content;\r\n if (!triggers.some((trigger) => raw.includes(trigger))) continue;\r\n stats.sievePassed += 1;\r\n\r\n const stripped = stripCommentsAndStrings(raw, syntax);\r\n if (stripped === null) {\r\n stats.unstrippable += 1;\r\n continue;\r\n }\r\n\r\n for (const rule of active) {\r\n if (!rule.triggers.some((trigger) => raw.includes(trigger))) continue;\r\n if (!rule.pattern.test(stripped)) continue;\r\n if (rule.rejectRaw?.test(raw)) continue;\r\n\r\n if (removed.has(normalizeDiffText(raw))) {\r\n stats.suppressedAsMoved += 1;\r\n continue;\r\n }\r\n\r\n const shadow = shadowIds.has(rule.id);\r\n hits.push({ rule, line, shadow });\r\n stats.byRule[rule.id] = (stats.byRule[rule.id] ?? 0) + 1;\r\n if (shadow) stats.shadowHits += 1;\r\n else stats.hits += 1;\r\n break;\r\n }\r\n }\r\n if (stats.truncated) break;\r\n }\r\n\r\n return { hits, stats };\r\n}\r\n\r\nexport function ruleHitsToComments(file: FileDiff, result: RuleScanResult): ParsedReviewComment[] {\r\n const comments: ParsedReviewComment[] = [];\r\n for (const hit of result.hits) {\r\n if (hit.shadow) continue;\r\n\r\n const { rule, line } = hit;\r\n const anchorHash = buildAnchorHash(line.content);\r\n comments.push({\r\n path: file.path,\r\n line: line.newLineNumber ?? null,\r\n position: line.position ?? null,\r\n severity: rule.severity,\r\n category: CLAIM_TYPE_CATEGORY[rule.claimType] ?? 'quality',\r\n title: rule.title,\r\n body: rule.body,\r\n evidence: line.content,\r\n anchorHash,\r\n claimType: rule.claimType,\r\n fingerprint: buildFindingFingerprint(file.path, `${rule.title} @${anchorHash}`),\r\n fingerprintV2: buildFindingFingerprintV2(file.path, rule.claimType, anchorHash),\r\n source: 'rule' as const,\r\n ruleId: rule.id,\r\n } satisfies ParsedReviewComment);\r\n }\r\n\r\n return comments;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;AAIA,SAAS,2BAA2B;AA8BpC,SAAS,YAAY,MAAc;AACjC,SAAO,KAAK,YAAY,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK;AAChD;AAEA,SAAS,YAAY,MAAY,KAAa;AAC5C,SAAO,CAAC,KAAK,cAAc,KAAK,WAAW,SAAS,GAAG;AACzD;AAEO,SAAS,oBAAoB,MAAgB,UAA2B,CAAC,GAAmB;AACjG,QAAM,QAAuB;AAAA,IAC3B,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,cAAc;AAAA,IACd,WAAW;AAAA,IACX,QAAQ,CAAC;AAAA,EACX;AACA,QAAM,OAAkB,CAAC;AAEzB,MAAI,KAAK,aAAa,KAAK,YAAY,CAAC,KAAK,KAAM,QAAO,EAAE,MAAM,MAAM;AAExE,QAAM,MAAM,YAAY,KAAK,IAAI;AACjC,QAAM,SAAS,IAAI,IAAI,QAAQ,oBAAoB,CAAC,CAAC;AACrD,QAAM,WAAW,IAAI,IAAI,QAAQ,mBAAmB,CAAC,CAAC;AACtD,QAAM,YAAY,IAAI,IAAI,QAAQ,iBAAiB,CAAC,CAAC;AAErD,QAAM,SAAS,MAAM,OAAO,CAAC,SAC3B,KAAK,WACF,CAAC,SAAS,IAAI,KAAK,EAAE,KACrB,CAAC,OAAO,IAAI,KAAK,SAAS,KAC1B,YAAY,MAAM,GAAG,CAAC;AAC3B,MAAI,OAAO,WAAW,EAAG,QAAO,EAAE,MAAM,MAAM;AAE9C,QAAM,WAAW,CAAC,GAAG,IAAI,IAAI,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC;AACrE,QAAM,SAAS,iBAAiB,KAAK,IAAI;AAEzC,aAAW,QAAQ,KAAK,OAAO;AAC7B,UAAM,UAAU,oBAAI,IAAY;AAChC,eAAW,KAAK,KAAK,OAAO;AAC1B,UAAI,EAAE,SAAS,MAAO,SAAQ,IAAI,kBAAkB,EAAE,OAAO,CAAC;AAAA,IAChE;AAEA,eAAW,QAAQ,KAAK,OAAO;AAC7B,UAAI,KAAK,SAAS,MAAO;AACzB,UAAI,MAAM,qBAAqB,2BAA2B;AACxD,cAAM,YAAY;AAClB;AAAA,MACF;AACA,YAAM,qBAAqB;AAE3B,YAAM,MAAM,KAAK;AACjB,UAAI,CAAC,SAAS,KAAK,CAAC,YAAY,IAAI,SAAS,OAAO,CAAC,EAAG;AACxD,YAAM,eAAe;AAErB,YAAM,WAAW,wBAAwB,KAAK,MAAM;AACpD,UAAI,aAAa,MAAM;AACrB,cAAM,gBAAgB;AACtB;AAAA,MACF;AAEA,iBAAW,QAAQ,QAAQ;AACzB,YAAI,CAAC,KAAK,SAAS,KAAK,CAAC,YAAY,IAAI,SAAS,OAAO,CAAC,EAAG;AAC7D,YAAI,CAAC,KAAK,QAAQ,KAAK,QAAQ,EAAG;AAClC,YAAI,KAAK,WAAW,KAAK,GAAG,EAAG;AAE/B,YAAI,QAAQ,IAAI,kBAAkB,GAAG,CAAC,GAAG;AACvC,gBAAM,qBAAqB;AAC3B;AAAA,QACF;AAEA,cAAM,SAAS,UAAU,IAAI,KAAK,EAAE;AACpC,aAAK,KAAK,EAAE,MAAM,MAAM,OAAO,CAAC;AAChC,cAAM,OAAO,KAAK,EAAE,KAAK,MAAM,OAAO,KAAK,EAAE,KAAK,KAAK;AACvD,YAAI,OAAQ,OAAM,cAAc;AAAA,YAC3B,OAAM,QAAQ;AACnB;AAAA,MACF;AAAA,IACF;AACA,QAAI,MAAM,UAAW;AAAA,EACvB;AAEA,SAAO,EAAE,MAAM,MAAM;AACvB;AAEO,SAAS,mBAAmB,MAAgB,QAA+C;AAChG,QAAM,WAAkC,CAAC;AACzC,aAAW,OAAO,OAAO,MAAM;AAC7B,QAAI,IAAI,OAAQ;AAEhB,UAAM,EAAE,MAAM,KAAK,IAAI;AACvB,UAAM,aAAa,gBAAgB,KAAK,OAAO;AAC/C,aAAS,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,MAAM,KAAK,iBAAiB;AAAA,MAC5B,UAAU,KAAK,YAAY;AAAA,MAC3B,UAAU,KAAK;AAAA,MACf,UAAU,oBAAoB,KAAK,SAAS,KAAK;AAAA,MACjD,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf;AAAA,MACA,WAAW,KAAK;AAAA,MAChB,aAAa,wBAAwB,KAAK,MAAM,GAAG,KAAK,KAAK,KAAK,UAAU,EAAE;AAAA,MAC9E,eAAe,0BAA0B,KAAK,MAAM,KAAK,WAAW,UAAU;AAAA,MAC9E,QAAQ;AAAA,MACR,QAAQ,KAAK;AAAA,IACf,CAA+B;AAAA,EACjC;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/ports/in-memory.ts
|
|
2
|
+
var InMemoryKV = class {
|
|
3
|
+
store = /* @__PURE__ */ new Map();
|
|
4
|
+
async put(key, value, options) {
|
|
5
|
+
const expiresAt = options?.expirationTtl ? Date.now() + options.expirationTtl * 1e3 : void 0;
|
|
6
|
+
this.store.set(key, { value, expiresAt });
|
|
7
|
+
}
|
|
8
|
+
async get(key, type) {
|
|
9
|
+
const entry = this.store.get(key);
|
|
10
|
+
if (!entry) return null;
|
|
11
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
12
|
+
this.store.delete(key);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (type === "json") {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(entry.value);
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return entry.value;
|
|
23
|
+
}
|
|
24
|
+
async delete(key) {
|
|
25
|
+
this.store.delete(key);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var InMemoryQueue = class {
|
|
29
|
+
messages = [];
|
|
30
|
+
async send(message, options) {
|
|
31
|
+
this.messages.push({ message, delaySeconds: options?.delaySeconds });
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var InMemoryOrchestrator = class {
|
|
35
|
+
jobs = /* @__PURE__ */ new Map();
|
|
36
|
+
async startReviewJob(id, params) {
|
|
37
|
+
this.jobs.set(id, params);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var InMemorySessionStore = class {
|
|
41
|
+
kv = new InMemoryKV();
|
|
42
|
+
async createSession(session) {
|
|
43
|
+
const token = Math.random().toString(36).substring(2);
|
|
44
|
+
await this.kv.put(`session:${token}`, JSON.stringify(session), { expirationTtl: 60 * 60 * 24 * 7 });
|
|
45
|
+
return token;
|
|
46
|
+
}
|
|
47
|
+
async readSession(token) {
|
|
48
|
+
return this.kv.get(`session:${token}`, "json");
|
|
49
|
+
}
|
|
50
|
+
async destroySession(token) {
|
|
51
|
+
await this.kv.delete(`session:${token}`);
|
|
52
|
+
}
|
|
53
|
+
async renewSession(token) {
|
|
54
|
+
const session = await this.readSession(token);
|
|
55
|
+
if (session) {
|
|
56
|
+
await this.kv.put(`session:${token}`, JSON.stringify(session), { expirationTtl: 60 * 60 * 24 * 7 });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
InMemoryKV,
|
|
63
|
+
InMemoryQueue,
|
|
64
|
+
InMemoryOrchestrator,
|
|
65
|
+
InMemorySessionStore
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=chunk-JC74XH7Q.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,MAAqC;AAC1D,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,341 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLanguageForFile
|
|
3
|
+
} from "./chunk-RQ5S67XB.js";
|
|
4
|
+
import {
|
|
5
|
+
EXEMPLAR_BLOCK_CHARS,
|
|
6
|
+
PR_DESCRIPTION_CHARS
|
|
7
|
+
} from "./chunk-FGXA7JNR.js";
|
|
8
|
+
|
|
9
|
+
// src/prompts/file-review.ts
|
|
10
|
+
import { claimTypes } from "@codraoss/schema";
|
|
11
|
+
function generatorFindingCap(maxComments) {
|
|
12
|
+
return Math.max(1, maxComments * 2);
|
|
13
|
+
}
|
|
14
|
+
function findingItemSchema() {
|
|
15
|
+
return {
|
|
16
|
+
type: "object",
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
required: ["evidence", "code_location", "claim_type", "title", "body", "priority"],
|
|
19
|
+
properties: {
|
|
20
|
+
evidence: { type: "string" },
|
|
21
|
+
code_location: {
|
|
22
|
+
type: "object",
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
properties: {
|
|
25
|
+
absolute_file_path: { type: "string" },
|
|
26
|
+
line: { type: "integer", minimum: 1 },
|
|
27
|
+
line_range: {
|
|
28
|
+
type: "object",
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
required: ["start", "end"],
|
|
31
|
+
properties: {
|
|
32
|
+
start: { type: "integer", minimum: 1 },
|
|
33
|
+
end: { type: "integer", minimum: 1 }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
anyOf: [
|
|
38
|
+
{ required: ["line"] },
|
|
39
|
+
{ required: ["line_range"] }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
claim_type: { type: "string", enum: [...claimTypes] },
|
|
43
|
+
title: { type: "string", maxLength: 100 },
|
|
44
|
+
body: { type: "string" },
|
|
45
|
+
priority: { type: "integer", minimum: 0, maximum: 4 },
|
|
46
|
+
code_suggestion: { type: "string" }
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function buildReviewResponseSchema(maxComments) {
|
|
51
|
+
return {
|
|
52
|
+
name: "codra_file_review",
|
|
53
|
+
schema: {
|
|
54
|
+
type: "object",
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
required: ["findings", "overall_explanation", "overall_correctness", "overall_confidence_score"],
|
|
57
|
+
properties: {
|
|
58
|
+
findings: {
|
|
59
|
+
type: "array",
|
|
60
|
+
maxItems: generatorFindingCap(maxComments),
|
|
61
|
+
items: findingItemSchema()
|
|
62
|
+
},
|
|
63
|
+
overall_explanation: { type: "string" },
|
|
64
|
+
overall_correctness: { type: "string", enum: ["patch is correct", "patch is incorrect"] },
|
|
65
|
+
overall_confidence_score: { type: "number", minimum: 0, maximum: 1 }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function buildBatchReviewResponseSchema(maxComments, fileCount) {
|
|
71
|
+
return {
|
|
72
|
+
name: "codra_batch_review",
|
|
73
|
+
schema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
required: ["files", "overall_confidence_score"],
|
|
77
|
+
properties: {
|
|
78
|
+
files: {
|
|
79
|
+
type: "array",
|
|
80
|
+
maxItems: fileCount,
|
|
81
|
+
items: {
|
|
82
|
+
type: "object",
|
|
83
|
+
additionalProperties: false,
|
|
84
|
+
required: ["absolute_file_path", "findings", "overall_explanation", "overall_correctness"],
|
|
85
|
+
properties: {
|
|
86
|
+
absolute_file_path: { type: "string" },
|
|
87
|
+
findings: { type: "array", items: findingItemSchema() },
|
|
88
|
+
overall_explanation: { type: "string" },
|
|
89
|
+
overall_correctness: { type: "string", enum: ["patch is correct", "patch is incorrect"] }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
overall_confidence_score: { type: "number", minimum: 0, maximum: 1 }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
var SINGLE_FILE_SCHEMA_FORMAT = `{
|
|
99
|
+
"findings": [
|
|
100
|
+
{
|
|
101
|
+
"evidence": "<the exact line of code from the diff this finding is about>",
|
|
102
|
+
"code_location": {
|
|
103
|
+
"line": number,
|
|
104
|
+
"line_range": { "start": number, "end": number }
|
|
105
|
+
},
|
|
106
|
+
"claim_type": "<one of the claim types listed above>",
|
|
107
|
+
"title": "<Plain title, NO tags/emoji>",
|
|
108
|
+
"body": "<Explanation of the concrete defect and its impact>",
|
|
109
|
+
"priority": 0 | 1 | 2 | 3 | 4,
|
|
110
|
+
"code_suggestion": "Optional replacement code"
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
"overall_explanation": "Summary",
|
|
114
|
+
"overall_correctness": "patch is correct" | "patch is incorrect",
|
|
115
|
+
"overall_confidence_score": number (0 to 1)
|
|
116
|
+
}`;
|
|
117
|
+
var MULTI_FILE_SCHEMA_FORMAT = `{
|
|
118
|
+
"files": [
|
|
119
|
+
{
|
|
120
|
+
"absolute_file_path": "<exact path of one of the files listed below, copied character-for-character>",
|
|
121
|
+
"findings": [
|
|
122
|
+
{
|
|
123
|
+
"evidence": "<the exact line of code from THIS file's diff this finding is about>",
|
|
124
|
+
"code_location": {
|
|
125
|
+
"absolute_file_path": "<the same path as above>",
|
|
126
|
+
"line": number,
|
|
127
|
+
"line_range": { "start": number, "end": number }
|
|
128
|
+
},
|
|
129
|
+
"claim_type": "<one of the claim types listed above>",
|
|
130
|
+
"title": "<Plain title, NO tags/emoji>",
|
|
131
|
+
"body": "<Explanation of the concrete defect and its impact>",
|
|
132
|
+
"priority": 0 | 1 | 2 | 3 | 4,
|
|
133
|
+
"code_suggestion": "Optional replacement code"
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
"overall_explanation": "Summary for THIS file",
|
|
137
|
+
"overall_correctness": "patch is correct" | "patch is incorrect"
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"overall_confidence_score": number (0 to 1)
|
|
141
|
+
}`;
|
|
142
|
+
function buildFileReviewSystemPromptBase(opts) {
|
|
143
|
+
const multi = opts?.multiFile === true;
|
|
144
|
+
const contextScope = multi ? `- You can see ONLY the diffs below, not the whole files or the rest of the repository.
|
|
145
|
+
- Each file below is INDEPENDENT. A finding about one file must be grounded in a line from THAT file's diff, and must be reported inside that file's entry. Never carry a claim from one file to another, and never assume two files interact unless both diffs show it.` : "- You can see ONLY the diff below, not the whole file or the rest of the repository.";
|
|
146
|
+
const evidenceSource = multi ? `the single line of code the finding is about, copied VERBATIM from that file's diff below.` : "the single line of code the finding is about, copied VERBATIM from the diff below.";
|
|
147
|
+
const capRule = multi ? "4. Return at most {{MAX_COMMENTS}} findings PER FILE, most severe first. Keep each body under 160 words." : "4. Return at most {{MAX_COMMENTS}} findings, most severe first. Keep each body under 160 words.";
|
|
148
|
+
const emptyRule = multi ? `5. Return exactly one entry per file listed below, in the same order, and never omit a file. Review each file's diff with the same care you would give it if it were the only file in front of you. An empty findings array is a positive claim that this diff introduces no defect, so return one only when that is true. Do not pad, and do not withhold.` : "5. If the diff genuinely introduces no defect, return an empty findings array and a short explanation. Do not pad, and do not withhold.";
|
|
149
|
+
return `You are a world-class software engineer performing a precise, high-signal code review.
|
|
150
|
+
Your goal is to find REAL defects (bugs, security vulnerabilities, and performance problems) introduced by the diff. Every finding must be grounded in a line you can quote from the diff.
|
|
151
|
+
|
|
152
|
+
### CONTEXT EXTENDS (read carefully, this prevents false positives):
|
|
153
|
+
${contextScope}
|
|
154
|
+
- You cannot see which files import this one. Never predict that a change breaks callers, importers, "other modules" or "external files" -- a removed \`export\`, a renamed symbol or a changed signature may have no consumers at all, and you have no way to check. The same applies in reverse to a function whose body is not shown: do not assume what it does with its errors or its return value.
|
|
155
|
+
- Assume every third-party package is at the version this project pins, and that its API is whatever that version provides. Never claim a library "does not expose", "does not provide" or "does not support" something; your training data predates the installed version.
|
|
156
|
+
- Assume the language, runtime and build target are whatever the project already uses successfully. A syntax or standard-library method appearing in the diff is available in this project by construction -- the code around it already compiles and ships. Do not raise compatibility, polyfill, transpilation, engine-version or server-side-rendering concerns unless the diff itself shows the incompatibility.
|
|
157
|
+
- Two async facts that are frequently misread. \`return somePromise()\` inside an \`async\` function IS awaited by whoever awaits that function; it is equivalent to \`return await\` except inside \`try\`/\`finally\`, so it is not a missing await and not a floating promise. And \`void someAsyncCall()\` is deliberate fire-and-forget: if the called function handles its own errors, there is no unhandled rejection to report.
|
|
158
|
+
|
|
159
|
+
### WHAT TO REPORT:
|
|
160
|
+
- Report anything a senior engineer reviewing this diff would want to investigate: a bug, a security hole, a performance problem, a resource leak, an unhandled failure, a broken invariant.
|
|
161
|
+
- You do not need to be certain. A finding you can ground in a quoted line is worth raising; every finding is independently checked against the diff afterwards, and a wrong one is discarded at no cost to you. A defect you decline to mention is simply lost.
|
|
162
|
+
|
|
163
|
+
### EVIDENCE (mandatory, a finding without it cannot be posted):
|
|
164
|
+
- Every finding MUST include "evidence": ${evidenceSource}
|
|
165
|
+
- Copy the code exactly as it appears. Do NOT include the two line-number columns or the +/- marker, do NOT paraphrase, reformat, shorten, or invent code.
|
|
166
|
+
- If you cannot quote a specific line from the diff that exhibits the problem, you do not have a finding. Omit it.
|
|
167
|
+
|
|
168
|
+
### CLAIM TYPE (required, pick the one that fits, or "other"):
|
|
169
|
+
${claimTypes.join(", ")}
|
|
170
|
+
- This is a label for the KIND of defect. It does not license the claim: only report a type if the
|
|
171
|
+
diff actually shows it. Picking a type the code cannot exhibit makes the finding easy to discard.
|
|
172
|
+
- If nothing fits, use "other". Do not stretch a label to fit.
|
|
173
|
+
- NEVER claim that a package, action, tag or version "does not exist", or that a config key is invalid. You cannot know what was released after your training data, and a step pinned to a commit SHA resolves by that SHA regardless of the version written beside it. Such claims are discarded.
|
|
174
|
+
- Label honestly. The type you choose does not affect whether a finding is accepted; an inaccurate label only makes a real defect harder to act on.
|
|
175
|
+
|
|
176
|
+
### OUTPUT RULES:
|
|
177
|
+
1. Output MUST be valid JSON, EXACTLY ONE object matching the schema below.
|
|
178
|
+
2. DO NOT output any conversational text, source code, or diff hunks before or after the JSON.
|
|
179
|
+
3. Prioritize by severity: 0 = P0 critical, 1 = P1 high, 2 = P2 medium, 3 = P3 low, 4 = nit (cosmetic/trivial). Set priority honestly; do not inflate. Use 4 for anything a reviewer would prefix with "nit:".
|
|
180
|
+
A finding that rests on a condition you cannot check from the diff -- "if this runs on an older engine", "if another module imports this", "depending on the caller" -- is at most priority 3, never 0 or 1, however serious the consequence would be if the condition held. Certainty about the consequence is not certainty about the premise.
|
|
181
|
+
${capRule}
|
|
182
|
+
${emptyRule}
|
|
183
|
+
|
|
184
|
+
### SCHEMA FORMAT:
|
|
185
|
+
${multi ? MULTI_FILE_SCHEMA_FORMAT : SINGLE_FILE_SCHEMA_FORMAT}
|
|
186
|
+
|
|
187
|
+
Identify security risks such as XSS, SQLi, CSRF, insecure randomness, and data leaks that the diff actually introduces.`;
|
|
188
|
+
}
|
|
189
|
+
var fileReviewSystemPromptBase = buildFileReviewSystemPromptBase();
|
|
190
|
+
function buildFileReviewSystemPrompt(config, languagePersona, opts) {
|
|
191
|
+
const persona = languagePersona ? ` as ${languagePersona}` : "";
|
|
192
|
+
const prompt = buildFileReviewSystemPromptBase(opts).replace("{{MAX_COMMENTS}}", generatorFindingCap(config.max_comments).toString());
|
|
193
|
+
return `You are a world-class professional senior code reviewer${persona}. ${prompt}`;
|
|
194
|
+
}
|
|
195
|
+
function renderExemplars(exemplars) {
|
|
196
|
+
if (!exemplars?.length) return null;
|
|
197
|
+
const lines = [];
|
|
198
|
+
let used = 0;
|
|
199
|
+
for (const exemplar of exemplars) {
|
|
200
|
+
const line = `- ${exemplar.title}${exemplar.claimType ? ` (${exemplar.claimType})` : ""}`;
|
|
201
|
+
if (used + line.length > EXEMPLAR_BLOCK_CHARS) break;
|
|
202
|
+
lines.push(line);
|
|
203
|
+
used += line.length;
|
|
204
|
+
}
|
|
205
|
+
if (lines.length === 0) return null;
|
|
206
|
+
const heading = "Findings a reviewer on THIS repository has already rejected. Do not report things like these:";
|
|
207
|
+
return [heading, ...lines].join("\n");
|
|
208
|
+
}
|
|
209
|
+
function renderPrContext(prDescription) {
|
|
210
|
+
const trimmed = prDescription?.trim();
|
|
211
|
+
if (!trimmed) return null;
|
|
212
|
+
return `PR description (author intent - use to judge whether a change is deliberate):
|
|
213
|
+
${trimmed.slice(0, PR_DESCRIPTION_CHARS)}${trimmed.length > PR_DESCRIPTION_CHARS ? "\u2026" : ""}`;
|
|
214
|
+
}
|
|
215
|
+
function renderCustomRules(config) {
|
|
216
|
+
const rules = config.custom_rules.length > 0 ? config.custom_rules.map((rule) => `- ${rule}`).join("\n") : "- None";
|
|
217
|
+
return `Custom rules:
|
|
218
|
+
${rules}`;
|
|
219
|
+
}
|
|
220
|
+
function renderLanguageGuidelines(path) {
|
|
221
|
+
const languageInfo = getLanguageForFile(path);
|
|
222
|
+
const guidelineHeader = "Specific Guidelines (check the diff against each of these)";
|
|
223
|
+
return languageInfo ? `Language: ${languageInfo.language}
|
|
224
|
+
${guidelineHeader}:
|
|
225
|
+
${languageInfo.guidelines.map((g) => `- ${g}`).join("\n")}` : "Language: Generic\nSpecific Guidelines: Follow general best practices.";
|
|
226
|
+
}
|
|
227
|
+
function buildFileReviewPrompts(input) {
|
|
228
|
+
const languageInfo = getLanguageForFile(input.file.path);
|
|
229
|
+
const rules = input.config.custom_rules.length > 0 ? input.config.custom_rules.map((rule) => `- ${rule}`).join("\n") : "- None";
|
|
230
|
+
const systemPrompt = buildFileReviewSystemPrompt(input.config, languageInfo?.persona);
|
|
231
|
+
const languageGuidelines = renderLanguageGuidelines(input.file.path);
|
|
232
|
+
const prContext = renderPrContext(input.prDescription);
|
|
233
|
+
const exemplars = renderExemplars(input.rejectedExemplars);
|
|
234
|
+
const userPrompt = [
|
|
235
|
+
`PR title: ${input.prTitle ?? "Untitled PR"}`,
|
|
236
|
+
...prContext ? [prContext] : [],
|
|
237
|
+
...exemplars ? [exemplars] : [],
|
|
238
|
+
`File path: ${input.file.path}`,
|
|
239
|
+
languageGuidelines,
|
|
240
|
+
`Custom rules:
|
|
241
|
+
${rules}`,
|
|
242
|
+
"Review ONLY the diff shown below. You cannot see the rest of the file or repository - do not report something as undefined, unimported, unused, or missing just because it is not in the diff. If the diff note says it was truncated, do not infer issues from omitted lines.",
|
|
243
|
+
"Line numbers: every diff line below is prefixed with two columns - the OLD file line number, then the NEW file line number. Always report `line` (and `line_range`) using the NEW (second, right-hand) number, and only ever cite a line that appears in the diff. For a removed line, cite the nearest NEW line number shown next to it.",
|
|
244
|
+
"Evidence: every finding must carry an `evidence` string containing the exact code of the line it is about, copied character-for-character from the diff below. Strip the two leading line-number columns and the +/-/space marker - quote only the code itself. A finding whose evidence does not appear in the diff will be discarded.",
|
|
245
|
+
"Prioritize correctness, security, and production-impacting bugs. Raise anything you can ground in a quoted line; avoid subjective style feedback.",
|
|
246
|
+
"",
|
|
247
|
+
`## Output JSON Schema (STRICTLY REQUIRED)`,
|
|
248
|
+
`{
|
|
249
|
+
"findings": [
|
|
250
|
+
{
|
|
251
|
+
"evidence": "<exact line of code copied verbatim from the diff below, without the line-number columns or +/- marker>",
|
|
252
|
+
"code_location": {
|
|
253
|
+
"absolute_file_path": "${input.file.path}",
|
|
254
|
+
"line": <int>,
|
|
255
|
+
"line_range": {"start": <int>, "end": <int>}
|
|
256
|
+
},
|
|
257
|
+
"claim_type": "<${claimTypes.join(" | ")}>",
|
|
258
|
+
"title": "<Plain title>",
|
|
259
|
+
"body": "<Technical explanation>",
|
|
260
|
+
"priority": <0|1|2|3|4>,
|
|
261
|
+
"code_suggestion": "string"
|
|
262
|
+
}
|
|
263
|
+
],
|
|
264
|
+
"overall_correctness": "patch is correct" | "patch is incorrect",
|
|
265
|
+
"overall_explanation": "Summary",
|
|
266
|
+
"overall_confidence_score": <float 0.0-1.0>
|
|
267
|
+
}`,
|
|
268
|
+
"",
|
|
269
|
+
"Unified diff:",
|
|
270
|
+
renderFileDiff(input.file)
|
|
271
|
+
].join("\n");
|
|
272
|
+
return { systemPrompt, userPrompt };
|
|
273
|
+
}
|
|
274
|
+
function packFileHeader(file, index, total) {
|
|
275
|
+
return `===== FILE ${index + 1} of ${total}: ${file.path} =====`;
|
|
276
|
+
}
|
|
277
|
+
function buildBatchReviewPrompts(input) {
|
|
278
|
+
const files = input.files;
|
|
279
|
+
const languages = new Set(files.map((file) => getLanguageForFile(file.path)));
|
|
280
|
+
const uniformLanguage = languages.size === 1 ? [...languages][0] : void 0;
|
|
281
|
+
const systemPrompt = buildFileReviewSystemPrompt(input.config, uniformLanguage?.persona, { multiFile: true });
|
|
282
|
+
const prContext = renderPrContext(input.prDescription);
|
|
283
|
+
const exemplars = renderExemplars(input.rejectedExemplars);
|
|
284
|
+
const pathList = files.map((file) => `- ${file.path}`).join("\n");
|
|
285
|
+
const fileBlocks = files.flatMap((file, index) => [
|
|
286
|
+
"",
|
|
287
|
+
packFileHeader(file, index, files.length),
|
|
288
|
+
...uniformLanguage ? [] : [renderLanguageGuidelines(file.path)],
|
|
289
|
+
"Unified diff:",
|
|
290
|
+
renderFileDiff(file)
|
|
291
|
+
]);
|
|
292
|
+
const userPrompt = [
|
|
293
|
+
`PR title: ${input.prTitle ?? "Untitled PR"}`,
|
|
294
|
+
...prContext ? [prContext] : [],
|
|
295
|
+
...exemplars ? [exemplars] : [],
|
|
296
|
+
`You are reviewing ${files.length} files in ONE response. Return exactly ${files.length} entries in "files", one per path, in this order:
|
|
297
|
+
${pathList}`,
|
|
298
|
+
...uniformLanguage ? [renderLanguageGuidelines(files[0].path)] : [],
|
|
299
|
+
renderCustomRules(input.config),
|
|
300
|
+
"Review ONLY the diffs shown below. You cannot see the rest of any file or the repository - do not report something as undefined, unimported, unused, or missing just because it is not in a diff. If a diff note says it was truncated, do not infer issues from omitted lines.",
|
|
301
|
+
"File scoping: each finding belongs to exactly ONE file. Put it inside that file's entry, set that file's path in `absolute_file_path`, and quote evidence from that file's diff only. Never report a finding about one file inside another file's entry, and never quote a line from a different file.",
|
|
302
|
+
"Line numbers: every diff line below is prefixed with two columns - the OLD file line number, then the NEW file line number. Always report `line` (and `line_range`) using the NEW (second, right-hand) number, and only ever cite a line that appears in that file's diff. For a removed line, cite the nearest NEW line number shown next to it.",
|
|
303
|
+
"Evidence: every finding must carry an `evidence` string containing the exact code of the line it is about, copied character-for-character from its own file's diff below. Strip the two leading line-number columns and the +/-/space marker - quote only the code itself. A finding whose evidence does not appear in that file's diff will be discarded.",
|
|
304
|
+
"Prioritize correctness, security, and production-impacting bugs. Raise anything you can ground in a quoted line; avoid subjective style feedback.",
|
|
305
|
+
"",
|
|
306
|
+
`## Output JSON Schema (STRICTLY REQUIRED)`,
|
|
307
|
+
MULTI_FILE_SCHEMA_FORMAT,
|
|
308
|
+
...fileBlocks
|
|
309
|
+
].join("\n");
|
|
310
|
+
return { systemPrompt, userPrompt };
|
|
311
|
+
}
|
|
312
|
+
function renderFileDiff(file) {
|
|
313
|
+
const lines = [`diff --git a/${file.previousPath ?? file.path} b/${file.path}`];
|
|
314
|
+
for (const hunk of file.hunks) {
|
|
315
|
+
lines.push(hunk.header);
|
|
316
|
+
for (const line of hunk.lines) {
|
|
317
|
+
const prefix = line.kind === "add" ? "+" : line.kind === "del" ? "-" : " ";
|
|
318
|
+
const left = line.oldLineNumber ?? "";
|
|
319
|
+
const right = line.newLineNumber ?? "";
|
|
320
|
+
lines.push(`${String(left).padStart(4, " ")} ${String(right).padStart(4, " ")} ${prefix}${line.content}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (file.isTruncated) {
|
|
324
|
+
lines.push("");
|
|
325
|
+
lines.push(`[NOTE: This diff has been truncated from ${file.originalLineCount} lines to ${file.lineCount} lines for brevity.]`);
|
|
326
|
+
}
|
|
327
|
+
return lines.join("\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export {
|
|
331
|
+
generatorFindingCap,
|
|
332
|
+
buildReviewResponseSchema,
|
|
333
|
+
buildBatchReviewResponseSchema,
|
|
334
|
+
buildFileReviewSystemPromptBase,
|
|
335
|
+
fileReviewSystemPromptBase,
|
|
336
|
+
buildFileReviewSystemPrompt,
|
|
337
|
+
buildFileReviewPrompts,
|
|
338
|
+
buildBatchReviewPrompts,
|
|
339
|
+
renderFileDiff
|
|
340
|
+
};
|
|
341
|
+
//# sourceMappingURL=chunk-MMVQPGK3.js.map
|