返回 CodeWhale
github-adapter.ts
根目录 / crates / tui / extension-host / src / builtin / shared / github-adapter.ts
1 /** Pure presentation of admitted Core GitHub outcomes. No network, process, store or policy API. */
2 import type { Json } from '../../protocol.ts'
3 interface Result { ok: true; result: { content: string; success: boolean; metadata: Json } }
4 type Row = Record<string, unknown>
5 const REPOSITORY = 'Hmbown/CodeWhale'
6 function row(value: unknown): Row { if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid GitHub snapshot'); return value as Row }
7 function text(value: unknown): string { if (typeof value !== 'string') throw new Error('invalid GitHub text'); return value }
8 function strings(value: unknown): string[] { if (!Array.isArray(value)) throw new Error('invalid GitHub list'); return value.map(text) }
9 // Rust char indices count Unicode scalars before discarding control characters.
10 export function githubSummary(value: string, limit: number): string {
11 let out = '', index = 0
12 for (const char of value) {
13 if (index++ >= Math.max(0, limit - 3)) return `${out}...`
14 const code = char.codePointAt(0)!
15 if ((code <= 31 || (code >= 127 && code <= 159)) && char !== '\n' && char !== '\t') continue
16 out += char
17 }
18 return out
19 }
20 export function renderIssueReview(value: unknown): string {
21 const report = row(value), fields = row(report.fields), id = text(report.id)
22 let out = `# ${text(fields.title)}\n\nDraft: ${id}\nStatus: ready for review\nPublication: unavailable\nDuplicate search: not performed\nDestination: ${REPOSITORY}\n\nReview the contents before sharing. Redaction does not guarantee privacy.\n`
23 if (report.revises !== null) out += `Revises: ${text(report.revises)}\n`
24 for (const [heading, key] of [['Expected behavior','expected'],['Actual behavior','actual'],['Impact','impact']]) out += `\n## ${heading}\n\n${text(fields[key])}\n`
25 for (const [heading, key] of [['Steps to reproduce (agent reported)','steps'],['Observed by the agent','observed'],['Inferences (not verified)','inferred']]) {
26 const items = strings(fields[key]); out += `\n## ${heading}\n\n`
27 out += items.length ? items.map(item => `- ${item}\n`).join('') : 'None recorded.\n'
28 }
29 out += `\n## Runtime context\n\n- Codewhale: ${text(report.version)}\n- Platform: ${text(report.platform)}\n- Active model: ${text(report.model)}\n`
30 for (const [label,key] of [['Provider','reported_provider'],['Tool','reported_tool'],['Terminal','reported_terminal']]) out += `- ${label} (agent reported): ${fields[key] === null ? 'unknown' : text(fields[key])}\n`
31 if (!Array.isArray(fields.related_issues) || fields.related_issues.some(value => !Number.isInteger(value) || Number(value) < 1 || Number(value) > 4294967295)) throw new Error('invalid related issue')
32 if (fields.related_issues.length) out += '\n## Related issues (agent supplied; not verified or searched)\n\n' + fields.related_issues.map(number => `- [#${number}](https://github.com/${REPOSITORY}/issues/${number})\n`).join('')
33 const redactions = strings(report.redactions)
34 if (redactions.length) out += `\nRedacted categories: ${redactions.join(', ')}\n`
35 return out + `\nReview: \`/feedback review ${id}\`\nRevise: \`/feedback edit ${id} <change>\`\n`
36 }
37 export function transformGithubSnapshot(value: unknown): Result {
38 const snapshot = row(value), action = text(snapshot.action)
39 if (action === 'report_draft' || action === 'report_read' || action === 'report_review') {
40 const report = row(snapshot.report), id = text(report.id)
41 const review = renderIssueReview(report)
42 const content = action === 'report_review' ? review : JSON.stringify({ report_id:id, revises:report.revises, state:'ready_for_review', publication:'unavailable', duplicate_search:'not_performed', review, artifact:`artifacts/issue-reports/${id}.json` })
43 return { ok:true, result:{success:true,content,metadata:null} }
44 }
45 const number = text(snapshot.number)
46 if (!/^[0-9]{1,20}$/.test(number)) throw new Error('invalid captured issue number')
47 if (action === 'issue_context' || action === 'pr_context') {
48 const raw = structuredClone(row(snapshot.raw)), kind = action === 'issue_context' ? 'issue' : 'pr'
49 if (snapshot.large_body === true) {
50 const body = text(raw.body)
51 raw.body_summary = githubSummary(body,900); raw.body_artifact = snapshot.body_artifact as Json; raw.body = githubSummary(body,1200)
52 }
53 if (action === 'pr_context' && snapshot.diff !== null) { raw.diff_summary=githubSummary(text(snapshot.diff),900); raw.diff_artifact=snapshot.diff_artifact as Json }
54 const subject = kind === 'issue' ? 'Issue' : 'PR'
55 return { ok:true, result:{success:true,metadata:null,content:JSON.stringify({summary:`${subject} #${number}: ${typeof raw.title === 'string' ? raw.title : ''}`,[kind]:raw})} }
56 }
57 const target = text(snapshot.target)
58 if (!['issue','pr'].includes(target)) throw new Error('invalid captured GitHub target')
59 const subject = target === 'issue' ? 'issue' : 'PR'
60 let content: string
61 if (snapshot.dirty === true) content = `Refusing to close ${subject}: worktree is dirty and allow_dirty was false.`
62 else if (action === 'comment') content = snapshot.dry_run === true ? `Dry run: would comment on ${target} #${number}.` : `Commented on ${target} #${number}.`
63 else if (action === 'close_issue' || action === 'close_pr') content = snapshot.dry_run === true ? `Dry run: would close ${subject} #${number}.` : `Closed ${subject} #${number}.`
64 else throw new Error('unknown captured GitHub operation')
65 return {ok:true,result:{success:snapshot.dirty !== true,content,metadata:null}}
66 }
67
67 lines TYPESCRIPT