feat(okr-ai): AI 分析失败/空跑也写入 sync_logs
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m30s

之前 AI 分析只在「有 commits 且全部成功」时写 success 日志,
出错走 .catch 只打 stdout、空跑直接 return 不写。导致 UI 同步日志页
看不出 AI 是否真的跑过、为什么没产出。

改为:
- 空跑写 success(带 "No unanalyzed commits")
- 单仓库失败累计后写入 message 末尾
- 全仓库失败或外层异常写 status=error 并附 stack
- message 截断到 1000 字符避免超长

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zyc 2026-04-29 13:40:04 +08:00
parent 8561f6190d
commit 33b3a2208e

View File

@ -504,17 +504,28 @@ export async function analyzeCommitsForOKR(dryRun = false): Promise<AnalyzeResul
const startTime = Date.now(); const startTime = Date.now();
const batchId = uuid(); const batchId = uuid();
let totalCommits = 0;
let actionsExecuted = 0;
const summaries: { repo: string; summary: string }[] = [];
const repoErrors: { repo: string; error: string }[] = [];
try {
const groups = await gatherUnanalyzedCommits(); const groups = await gatherUnanalyzedCommits();
if (groups.length === 0) { if (groups.length === 0) {
console.info('[AI-OKR] No unanalyzed commits found, skipping'); console.info('[AI-OKR] No unanalyzed commits found, skipping');
const elapsed = Date.now() - startTime;
await db.insert(syncLogs).values({
id: uuid(),
source: 'ai_okr',
status: 'success',
message: `${dryRun ? '[DRY RUN] ' : ''}No unanalyzed commits, skipped in ${elapsed}ms`,
recordsProcessed: 0,
syncedAt: new Date(),
});
return { totalCommits: 0, reposProcessed: 0, actionsExecuted: 0, summaries: [] }; return { totalCommits: 0, reposProcessed: 0, actionsExecuted: 0, summaries: [] };
} }
let totalCommits = 0;
let actionsExecuted = 0;
const summaries: { repo: string; summary: string }[] = [];
const MAX_COMMITS_PER_BATCH = 30; const MAX_COMMITS_PER_BATCH = 30;
for (const group of groups) { for (const group of groups) {
@ -559,20 +570,39 @@ export async function analyzeCommitsForOKR(dryRun = false): Promise<AnalyzeResul
} catch (err) { } catch (err) {
const msg = err instanceof Error ? err.message : String(err); const msg = err instanceof Error ? err.message : String(err);
console.error(`[AI-OKR] Failed to analyze repo ${group.repoName}: ${msg}`); console.error(`[AI-OKR] Failed to analyze repo ${group.repoName}: ${msg}`);
repoErrors.push({ repo: group.repoName, error: msg });
} }
} }
// 记录 sync log // 记录 sync log(成功或部分失败)
const elapsed = Date.now() - startTime; const elapsed = Date.now() - startTime;
const status = repoErrors.length === groups.length ? 'error' : 'success';
const errorSuffix = repoErrors.length > 0
? `; failed repos: ${repoErrors.map(e => `${e.repo}(${e.error})`).join(' | ')}`
: '';
await db.insert(syncLogs).values({ await db.insert(syncLogs).values({
id: uuid(), id: uuid(),
source: 'ai_okr', source: 'ai_okr',
status: 'success', status,
message: `${dryRun ? '[DRY RUN] ' : ''}Analyzed ${totalCommits} commits from ${groups.length} repos, executed ${actionsExecuted} actions in ${elapsed}ms`, message: `${dryRun ? '[DRY RUN] ' : ''}Analyzed ${totalCommits} commits from ${groups.length} repos, executed ${actionsExecuted} actions in ${elapsed}ms${errorSuffix}`.slice(0, 1000),
recordsProcessed: actionsExecuted, recordsProcessed: actionsExecuted,
syncedAt: new Date(), syncedAt: new Date(),
}); });
console.info(`[AI-OKR] Completed: ${totalCommits} commits, ${groups.length} repos, ${actionsExecuted} actions in ${elapsed}ms`); console.info(`[AI-OKR] Completed: ${totalCommits} commits, ${groups.length} repos, ${actionsExecuted} actions in ${elapsed}ms`);
return { totalCommits, reposProcessed: groups.length, actionsExecuted, summaries }; return { totalCommits, reposProcessed: groups.length, actionsExecuted, summaries };
} catch (err) {
const msg = err instanceof Error ? `${err.message}\n${err.stack || ''}` : String(err);
const elapsed = Date.now() - startTime;
console.error(`[AI-OKR] Fatal error: ${msg}`);
await db.insert(syncLogs).values({
id: uuid(),
source: 'ai_okr',
status: 'error',
message: `${dryRun ? '[DRY RUN] ' : ''}Fatal error after ${elapsed}ms: ${msg}`.slice(0, 1000),
recordsProcessed: actionsExecuted,
syncedAt: new Date(),
});
throw err;
}
} }