返回 CodeWhale
review-adapter.ts
根目录 / crates / tui / extension-host / src / builtin / shared / review-adapter.ts
1 /** Presentation of immutable Core review facts. No provider, I/O, pass or publication authority. */
2 import { isJson } from '../../json.ts'
3
4 type Row = Record<string, unknown>
5 interface Result { ok: true; result: { content: string; success: true; metadata: null } }
6 export const REVIEW_LIMIT = 16 * 1024 * 1024
7 const OPERATIONS = ['review_source_prompt', 'review_pass_prompt', 'review_interactive_pr', 'review_report']
8 export function isReviewOperation(value: unknown): value is string { return typeof value === 'string' && OPERATIONS.includes(value) }
9 export function reviewEnvelopeBytes(value: unknown): number { return 8 + Buffer.byteLength(`{"jsonrpc":"2.0","id":18446744073709551615,"result":${JSON.stringify(value)}}`) }
10 function row(value: unknown): Row { if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid Core review snapshot'); return value as Row }
11 function text(value: unknown): string { if (typeof value !== 'string') throw new Error('invalid Core review text'); return value }
12 function bool(value: unknown): boolean { if (typeof value !== 'boolean') throw new Error('invalid Core review flag'); return value }
13 function integer(value: unknown): number { if (!Number.isSafeInteger(value) || (value as number) < 0) throw new Error('invalid Core review count'); return value as number }
14 function list(value: unknown): unknown[] { if (!Array.isArray(value)) throw new Error('invalid Core review list'); return value }
15 function optional(value: unknown): string | undefined { return value === null || value === undefined ? undefined : text(value) }
16 function trim(value: string): string { return value.replace(/^[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g, '') }
17 function lines(value: string): string[] {
18 if (!value.length) return []
19 const out = value.split('\n')
20 if (value.endsWith('\n')) out.pop()
21 return out.map((line, i) => (i < out.length - 1 || value.endsWith('\n')) && line.endsWith('\r') ? line.slice(0, -1) : line)
22 }
23 function skipped(value: unknown): string { return list(value).map(value => { const skip = row(value); return `${text(skip.file)} (${integer(skip.chars)} chars; ${text(skip.reason)})` }).join(', ') }
24 function ordered(value: unknown): unknown {
25 if (Array.isArray(value)) return value.map(ordered)
26 if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, value]) => [key, ordered(value)]))
27 return value
28 }
29 function source(input: Row): string {
30 const kind = text(input.kind)
31 if (kind === 'file') {
32 const content = lines(text(input.content)).map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join('\n')
33 return `Review the following file and provide feedback.\nPath: ${text(input.display)}\n\n${content}\n\nEnd of file.`
34 }
35 if (kind === 'diff') return `Review the following ${text(input.label)} and provide feedback.\n\n${text(input.diff)}\n\nEnd of diff.`
36 if (kind === 'cli_diff') return `Review the following diff and provide feedback:\n\n${text(input.diff)}\n\nEnd of diff.`
37 if (kind === 'pr') return `Review the complete pull request diff (${text(input.label)}) at head ${text(input.head_sha)} and base ${text(input.base_sha)}. Binary changes are represented by metadata; their contents are not semantically inspected. Exact binary object IDs remain in the review evidence.\n\n${text(input.diff)}\n\nEnd of diff.`
38 throw new Error('unadmitted review source')
39 }
40 function pass(input: Row): string {
41 const manifest = row(input.manifest), part = row(input.pass), view = row(input.view)
42 const task = list(manifest.skipped_files).length === 0
43 ? 'Review only defects introduced in this pass. Use supplementary source to check surrounding guards and declarations; it does not expand the commentable diff. Binary contents and omitted callers are not inspected. No build or tests have been run.'
44 : `Review only defects introduced in this pass. This is a partial review (pass ${integer(part.number)} of ${list(manifest.passes).length}): the gate did not read ${skipped(manifest.skipped_files)}. Do not claim full coverage. Use supplementary source to check surrounding guards and declarations; it does not expand the commentable diff. Binary contents and omitted callers are not inspected. No build or tests have been run.`
45 if (!isJson(input.context)) throw new Error('invalid Core review context')
46 const prompt = {task, untrusted_repository_data: true, pull_request: {number: integer(input.number), title: text(view.title), description: text(view.body)}, manifest, pass: part, diff: text(input.diff), repository_context: input.context,
47 context_limit: 'Context is bounded supplementary excerpts from the exact head. Null means no source context could fit. Missing files or omitted lines are not evidence of a defect.'}
48 return JSON.stringify(bool(input.sort_keys) ? ordered(prompt) : prompt)
49 }
50 function interactive(input: Row): string {
51 const view = row(input.view), number = integer(input.number)
52 const base = text(view.base), head = text(view.head)
53 const branches = base && head ? `${base} ← ${head}` : base || head || '(unknown)'
54 return `Review PR #${number} — ${trim(text(view.title)) || `(PR #${number})`}\n\nURL: ${text(view.url) || '(unavailable)'}\nBranches: ${branches}\nRevision: ${text(view.head_sha)} (base ${text(view.base_sha)}); ${integer(view.changed_files)} file patches.\nBinary changes are represented by metadata; their contents are not semantically inspected. Exact binary object IDs remain in the review evidence.\n\n## Description\n\n${trim(text(view.body)) || '(no description)'}\n\n## Diff\n\n\`\`\`diff\n${text(input.diff)}\n\`\`\`\n`
55 }
56 function fence(value: string): string { let longest = 0, run = 0; for (const char of value) { run = char === '`' ? run + 1 : 0; longest = Math.max(longest, run) } return '`'.repeat(Math.max(3, longest + 1)) }
57 function location(value: Row): string {
58 const path = optional(value.path)
59 return path === undefined ? '' : value.line === null || value.line === undefined ? `\`${trim(path)}\`` : `\`${trim(path)}:${integer(value.line)}\``
60 }
61 function report(input: Row): string {
62 if (input.review === null) return text(input.output)
63 const review = row(input.review), posted = bool(input.posted)
64 let out = '## Codewhale review\n\n'
65 const summary = text(review.summary), assessment = text(review.overall_assessment)
66 if (summary.length) out += trim(summary) + '\n\n'
67 const issues = list(review.issues), suggestions = list(review.suggestions)
68 if (issues.length) {
69 out += '### Findings\n\n'
70 for (const item of issues) {
71 const issue = row(item), at = location(issue)
72 out += `- **[${text(issue.severity).toUpperCase()}] ${text(issue.title)}**${at ? ` (${at})` : ''}\n`
73 const description = text(issue.description)
74 if (description.length) out += ` ${description}\n`
75 }
76 out += '\n'
77 }
78 if (suggestions.length) {
79 out += '### Suggestions\n\n'
80 for (const item of suggestions) {
81 const suggestion = row(item), at = location(suggestion)
82 out += `- ${at ? `${at} — ` : ''}${text(suggestion.suggestion)}\n`
83 const replacement = optional(suggestion.replacement)
84 if (replacement !== undefined && trim(replacement).length) {
85 const codeFence = fence(replacement)
86 out += `\n ${codeFence}${posted ? 'text' : 'suggestion'}\n`
87 for (const line of replacement.split('\n')) out += ` ${line}\n`
88 out += ` ${codeFence}\n`
89 }
90 }
91 out += '\n'
92 }
93 if (assessment.length) out += '### Assessment\n\n' + trim(assessment) + '\n\n'
94 return out
95 }
96 export function transformReviewSnapshot(operation: string, value: unknown): Result {
97 if (!isReviewOperation(operation)) throw new Error('unadmitted review operation')
98 const input = row(value)
99 const content = operation === 'review_source_prompt' ? source(input) : operation === 'review_pass_prompt' ? pass(input) : operation === 'review_interactive_pr' ? interactive(input) : report(input)
100 const result: Result = {ok: true, result: {content, success: true, metadata: null}}
101 // Same upper-bound JSON-RPC envelope as Core; limits include escaping and headers.
102 if (reviewEnvelopeBytes(result) > REVIEW_LIMIT) throw new Error('serialized review result exceeds 16 MiB')
103 return result
104 }
105
105 lines TYPESCRIPT