#!/usr/bin/env node
/**
* GitHub PR 評論工具
* 使用 GitHub API 在 PR 中發送評論
*/
import * as github from '@actions/github';
import * as core from '@actions/core';
const MAX_COMMENT_LENGTH = 65500; // GitHub 評論限制約 65536 字元,留一些緩衝
/**
* 發送或更新 PR 評論
*/
export async function commentOnPR(token, report, commentOnPR = true) {
if (!commentOnPR) {
core.info('comment-on-pr 設為 false,跳過 PR 評論');
return null;
}
const octokit = github.getOctokit(token);
const context = github.context;
// 檢查是否在 PR 環境中
if (context.eventName !== 'pull_request') {
core.warning('此 action 只能在 pull_request 事件中使用');
return null;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = context.payload.pull_request?.number;
if (!prNumber) {
core.warning('無法取得 PR 編號');
return null;
}
try {
// 查找現有的評論(由 bot 發送)
const existingComment = await findExistingComment(octokit, owner, repo, prNumber);
// 如果報告太長,分段處理
const commentBody = formatCommentBody(report);
if (existingComment) {
// 更新現有評論
await octokit.rest.issues.updateComment({
owner,
repo,
comment_id: existingComment.id,
body: commentBody
});
core.info(`已更新 PR #${prNumber} 的評論`);
return existingComment.id;
} else {
// 創建新評論
const { data } = await octokit.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: commentBody
});
core.info(`已在 PR #${prNumber} 中創建評論`);
return data.id;
}
} catch (error) {
core.error(`發送 PR 評論失敗: ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
}
/**
* 查找現有的評論
*/
async function findExistingComment(octokit, owner, repo, prNumber) {
try {
const { data: comments } = await octokit.rest.issues.listComments({
owner,
repo,
issue_number: prNumber
});
// 查找包含特定標記的評論(優先找 Bot,但如果有標記就使用)
const botComment = comments.find(comment => {
const body = comment.body || '';
// 只要包含標記就視為我們的評論(不限制 Bot,因為可能以其他身份執行)
return body.includes('<!-- dev-advisor-mcp -->');
});
return botComment || null;
} catch (error) {
core.warning(`查找現有評論失敗: ${error instanceof Error ? error.message : String(error)}`);
return null;
}
}
/**
* 格式化評論內容
*/
function formatCommentBody(report) {
let body = '<!-- dev-advisor-mcp -->\n\n';
body += '# 🔍 開發決策顧問分析報告\n\n';
body += `> 此報告由 [@mukiwu/dev-advisor-mcp](https://github.com/mukiwu/dev-advisor-mcp) 自動生成\n\n`;
// 添加現代化分析報告
if (report.modernization) {
body += '## 📊 程式碼現代化分析\n\n';
const modernizationReport = report.modernization.report;
// 如果報告太長,截斷並添加提示
if (modernizationReport.length > MAX_COMMENT_LENGTH - body.length - 1000) {
const truncated = modernizationReport.substring(0, MAX_COMMENT_LENGTH - body.length - 1000);
body += truncated;
body += '\n\n---\n\n';
body += '> ⚠️ 報告內容過長,已截斷。請查看完整的分析結果。\n\n';
} else {
body += modernizationReport;
body += '\n\n---\n\n';
}
}
// 添加相容性分析報告
if (report.compatibility) {
body += '## 🌐 API 相容性分析\n\n';
const compatibilityReport = report.compatibility.report;
// 檢查長度
const remainingLength = MAX_COMMENT_LENGTH - body.length;
if (compatibilityReport.length > remainingLength - 1000) {
const truncated = compatibilityReport.substring(0, remainingLength - 1000);
body += truncated;
body += '\n\n---\n\n';
body += '> ⚠️ 報告內容過長,已截斷。請查看完整的分析結果。\n\n';
} else {
body += compatibilityReport;
body += '\n\n---\n\n';
}
}
// 添加錯誤資訊
if (report.errors && report.errors.length > 0) {
body += '## ⚠️ 分析錯誤\n\n';
for (const error of report.errors) {
body += `- **${error.type}**: ${error.error}\n`;
}
body += '\n';
}
// 添加摘要
if (report.modernization || report.compatibility) {
body += '## 📋 分析摘要\n\n';
if (report.modernization) {
const summary = report.modernization.summary;
body += '### 現代化分析\n';
body += `- 掃描檔案: ${summary.totalFiles}\n`;
body += `- 發現建議: ${summary.totalSuggestions}\n`;
body += `- 效能提升: ${summary.performanceGain}%\n`;
body += `- Bundle 減少: ${summary.bundleReduction}KB\n`;
body += `- 風險等級: ${summary.risk}\n\n`;
}
if (report.compatibility) {
const summary = report.compatibility.summary;
body += '### 相容性分析\n';
body += `- 分析的 API: ${summary.totalApis}\n`;
body += `- 完全相容: ${summary.compatibleApis}\n`;
body += `- 不相容: ${summary.incompatibleApis}\n`;
body += `- 整體相容性: ${summary.overallCompatibility}%\n`;
body += `- 需要 Polyfill: ${summary.polyfillsNeeded}\n\n`;
}
}
body += '\n---\n';
body += '*此評論由 [@mukiwu/dev-advisor-mcp](https://github.com/mukiwu/dev-advisor-mcp) 自動生成*\n';
// 確保不超過長度限制
if (body.length > MAX_COMMENT_LENGTH) {
body = body.substring(0, MAX_COMMENT_LENGTH - 200);
body += '\n\n---\n\n> ⚠️ 報告內容過長,已截斷。\n';
}
return body;
}
/**
* 發送 AI 分析報告到 PR
* @param {string} token - GitHub Token
* @param {string} aiReport - AI 生成的報告內容
* @param {string} provider - AI 提供者
* @param {string} model - AI 模型
* @param {Object} prInfo - PR 資訊
*/
export async function commentAIReport(token, aiReport, provider, model, prInfo) {
const octokit = github.getOctokit(token);
const context = github.context;
// 檢查是否在 PR 環境中
if (context.eventName !== 'pull_request') {
core.warning('此 action 只能在 pull_request 事件中使用');
return null;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = context.payload.pull_request?.number;
if (!prNumber) {
core.warning('無法取得 PR 編號');
return null;
}
try {
// 查找現有的評論
const existingComment = await findExistingComment(octokit, owner, repo, prNumber);
// 格式化 AI 報告
const commentBody = formatAICommentBody(aiReport, provider, model);
if (existingComment) {
// 更新現有評論
await octokit.rest.issues.updateComment({
owner,
repo,
comment_id: existingComment.id,
body: commentBody
});
core.info(`已更新 PR #${prNumber} 的 AI 分析評論`);
return existingComment.id;
} else {
// 創建新評論
const { data } = await octokit.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: commentBody
});
core.info(`已在 PR #${prNumber} 中創建 AI 分析評論`);
return data.id;
}
} catch (error) {
core.error(`發送 AI 評論失敗: ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
}
/**
* 格式化 AI 評論內容
*/
function formatAICommentBody(aiReport, provider, model) {
const providerNames = {
openai: 'OpenAI',
anthropic: 'Anthropic Claude',
gemini: 'Google Gemini'
};
const providerName = providerNames[provider] || provider;
const modelDisplay = model || '預設模型';
let body = '<!-- dev-advisor-mcp -->\n\n';
body += '# 🤖 AI 程式碼審查報告\n\n';
body += `> 此報告由 [Dev Advisor MCP](https://github.com/mukiwu/dev-advisor-mcp) 使用 ${providerName} (${modelDisplay}) 自動生成\n\n`;
body += '---\n\n';
// 添加 AI 報告內容
if (aiReport.length > MAX_COMMENT_LENGTH - body.length - 200) {
const truncated = aiReport.substring(0, MAX_COMMENT_LENGTH - body.length - 200);
body += truncated;
body += '\n\n---\n\n';
body += '> ⚠️ 報告內容過長,已截斷。\n';
} else {
body += aiReport;
}
body += '\n\n---\n';
body += `*此評論由 [Dev Advisor MCP](https://github.com/mukiwu/dev-advisor-mcp) 自動生成*\n`;
return body;
}