feat(okr-ai): AI 分析失败/空跑也写入 sync_logs
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m30s
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:
parent
8561f6190d
commit
33b3a2208e
@ -504,75 +504,105 @@ export async function analyzeCommitsForOKR(dryRun = false): Promise<AnalyzeResul
|
|||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const batchId = uuid();
|
const batchId = uuid();
|
||||||
|
|
||||||
const groups = await gatherUnanalyzedCommits();
|
|
||||||
|
|
||||||
if (groups.length === 0) {
|
|
||||||
console.info('[AI-OKR] No unanalyzed commits found, skipping');
|
|
||||||
return { totalCommits: 0, reposProcessed: 0, actionsExecuted: 0, summaries: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalCommits = 0;
|
let totalCommits = 0;
|
||||||
let actionsExecuted = 0;
|
let actionsExecuted = 0;
|
||||||
const summaries: { repo: string; summary: string }[] = [];
|
const summaries: { repo: string; summary: string }[] = [];
|
||||||
|
const repoErrors: { repo: string; error: string }[] = [];
|
||||||
|
|
||||||
const MAX_COMMITS_PER_BATCH = 30;
|
try {
|
||||||
|
const groups = await gatherUnanalyzedCommits();
|
||||||
|
|
||||||
for (const group of groups) {
|
if (groups.length === 0) {
|
||||||
try {
|
console.info('[AI-OKR] No unanalyzed commits found, skipping');
|
||||||
totalCommits += group.commits.length;
|
const elapsed = Date.now() - startTime;
|
||||||
|
await db.insert(syncLogs).values({
|
||||||
// 分批处理,每批最多 30 条 commits
|
id: uuid(),
|
||||||
const batches: typeof group.commits[] = [];
|
source: 'ai_okr',
|
||||||
for (let i = 0; i < group.commits.length; i += MAX_COMMITS_PER_BATCH) {
|
status: 'success',
|
||||||
batches.push(group.commits.slice(i, i + MAX_COMMITS_PER_BATCH));
|
message: `${dryRun ? '[DRY RUN] ' : ''}No unanalyzed commits, skipped in ${elapsed}ms`,
|
||||||
}
|
recordsProcessed: 0,
|
||||||
|
syncedAt: new Date(),
|
||||||
for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
|
});
|
||||||
const batchCommits = batches[batchIdx];
|
return { totalCommits: 0, reposProcessed: 0, actionsExecuted: 0, summaries: [] };
|
||||||
const batchGroup = { ...group, commits: batchCommits };
|
|
||||||
console.info(`[AI-OKR] Analyzing ${batchCommits.length} commits for repo: ${group.repoName} (batch ${batchIdx + 1}/${batches.length})`);
|
|
||||||
|
|
||||||
// 构建 prompt 并调用 AI
|
|
||||||
const userPrompt = await buildUserPrompt(batchGroup);
|
|
||||||
const rawResponse = await callLLM(SYSTEM_PROMPT, userPrompt);
|
|
||||||
const aiResponse = parseLLMJson<AIResponse>(rawResponse);
|
|
||||||
|
|
||||||
if (!aiResponse.actions || !Array.isArray(aiResponse.actions)) {
|
|
||||||
console.warn(`[AI-OKR] Invalid AI response for ${group.repoName}, skipping batch`);
|
|
||||||
await markCommitsAnalyzed(batchCommits.map(c => c.sha), batchId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (batchIdx === batches.length - 1) {
|
|
||||||
summaries.push({ repo: group.repoName, summary: aiResponse.summary || '' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dryRun) {
|
|
||||||
const count = await executeActions(aiResponse.actions, group.projectId);
|
|
||||||
actionsExecuted += count;
|
|
||||||
await markCommitsAnalyzed(batchCommits.map(c => c.sha), batchId);
|
|
||||||
} else {
|
|
||||||
console.info(`[AI-OKR] [DRY RUN] Would execute ${aiResponse.actions.length} actions for ${group.repoName}`);
|
|
||||||
actionsExecuted += aiResponse.actions.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
|
||||||
console.error(`[AI-OKR] Failed to analyze repo ${group.repoName}: ${msg}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_COMMITS_PER_BATCH = 30;
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
try {
|
||||||
|
totalCommits += group.commits.length;
|
||||||
|
|
||||||
|
// 分批处理,每批最多 30 条 commits
|
||||||
|
const batches: typeof group.commits[] = [];
|
||||||
|
for (let i = 0; i < group.commits.length; i += MAX_COMMITS_PER_BATCH) {
|
||||||
|
batches.push(group.commits.slice(i, i + MAX_COMMITS_PER_BATCH));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
|
||||||
|
const batchCommits = batches[batchIdx];
|
||||||
|
const batchGroup = { ...group, commits: batchCommits };
|
||||||
|
console.info(`[AI-OKR] Analyzing ${batchCommits.length} commits for repo: ${group.repoName} (batch ${batchIdx + 1}/${batches.length})`);
|
||||||
|
|
||||||
|
// 构建 prompt 并调用 AI
|
||||||
|
const userPrompt = await buildUserPrompt(batchGroup);
|
||||||
|
const rawResponse = await callLLM(SYSTEM_PROMPT, userPrompt);
|
||||||
|
const aiResponse = parseLLMJson<AIResponse>(rawResponse);
|
||||||
|
|
||||||
|
if (!aiResponse.actions || !Array.isArray(aiResponse.actions)) {
|
||||||
|
console.warn(`[AI-OKR] Invalid AI response for ${group.repoName}, skipping batch`);
|
||||||
|
await markCommitsAnalyzed(batchCommits.map(c => c.sha), batchId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchIdx === batches.length - 1) {
|
||||||
|
summaries.push({ repo: group.repoName, summary: aiResponse.summary || '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dryRun) {
|
||||||
|
const count = await executeActions(aiResponse.actions, group.projectId);
|
||||||
|
actionsExecuted += count;
|
||||||
|
await markCommitsAnalyzed(batchCommits.map(c => c.sha), batchId);
|
||||||
|
} else {
|
||||||
|
console.info(`[AI-OKR] [DRY RUN] Would execute ${aiResponse.actions.length} actions for ${group.repoName}`);
|
||||||
|
actionsExecuted += aiResponse.actions.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
console.error(`[AI-OKR] Failed to analyze repo ${group.repoName}: ${msg}`);
|
||||||
|
repoErrors.push({ repo: group.repoName, error: msg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录 sync log(成功或部分失败)
|
||||||
|
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({
|
||||||
|
id: uuid(),
|
||||||
|
source: 'ai_okr',
|
||||||
|
status,
|
||||||
|
message: `${dryRun ? '[DRY RUN] ' : ''}Analyzed ${totalCommits} commits from ${groups.length} repos, executed ${actionsExecuted} actions in ${elapsed}ms${errorSuffix}`.slice(0, 1000),
|
||||||
|
recordsProcessed: actionsExecuted,
|
||||||
|
syncedAt: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info(`[AI-OKR] Completed: ${totalCommits} commits, ${groups.length} repos, ${actionsExecuted} actions in ${elapsed}ms`);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录 sync log
|
|
||||||
const elapsed = Date.now() - startTime;
|
|
||||||
await db.insert(syncLogs).values({
|
|
||||||
id: uuid(),
|
|
||||||
source: 'ai_okr',
|
|
||||||
status: 'success',
|
|
||||||
message: `${dryRun ? '[DRY RUN] ' : ''}Analyzed ${totalCommits} commits from ${groups.length} repos, executed ${actionsExecuted} actions in ${elapsed}ms`,
|
|
||||||
recordsProcessed: actionsExecuted,
|
|
||||||
syncedAt: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.info(`[AI-OKR] Completed: ${totalCommits} commits, ${groups.length} repos, ${actionsExecuted} actions in ${elapsed}ms`);
|
|
||||||
return { totalCommits, reposProcessed: groups.length, actionsExecuted, summaries };
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user