diff --git a/.github/workflows/syslog-issue-pipeline.yml b/.github/workflows/syslog-issue-pipeline.yml index abac3fd3..8c262df8 100644 --- a/.github/workflows/syslog-issue-pipeline.yml +++ b/.github/workflows/syslog-issue-pipeline.yml @@ -56,42 +56,15 @@ jobs: - name: Install dependencies run: npm ci --ignore-scripts - - name: 📋 进度通报 · 管道已启动 - id: progress - uses: actions/github-script@v7 - with: - script: | - const number = context.payload.issue.number; - const now = new Date().toISOString(); - - const body = [ - '## ⚙️ SYSLOG 管道运行中', - '', - '> 管道启动时间: ' + now, - '', - '| 阶段 | 状态 | 说明 |', - '|------|------|------|', - '| ① 接收提交 | ✅ 已完成 | 系统已收到 |', - '| ② 解析内容 | 🔄 进行中 | 正在解析广播编号、邮箱、内容 |', - '| ③ 模块验证 | ⏳ 等待中 | 检测模块上传状态 |', - '| ④ 唤醒核心大脑 | ⏳ 等待中 | 铸渊核心大脑处理 |', - '| ⑤ 创建 Notion 工单 | ⏳ 等待中 | 推送霜砚工单 |', - '| ⑥ 生成广播 | ⏳ 等待中 | 生成新广播内容 |', - '| ⑦ 邮件通知 | ⏳ 等待中 | 发送结果到邮箱 |', - '| ⑧ 闭环确认 | ⏳ 等待中 | Issue 回复 + 关闭 |', - '', - '> *管道正在运行,请勿关闭此 Issue*' - ].join('\n'); - - const { data: comment } = await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: number, - body: body - }); - - core.setOutput('comment_id', comment.id); - console.log('📋 进度通报已发送, comment_id=' + comment.id); + - name: 📋 步骤 1/8 · 接收提交 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '1' + STEP_NAME: '接收提交' + STEP_STATUS: 'ok' + STEP_DETAIL: '系统已收到您的 SYSLOG 提交,管道正在运行中…' + run: node scripts/pipeline-reporter.js - name: 🔍 Parse submission id: parse @@ -148,6 +121,28 @@ jobs: console.log(`📡 解析完成: 广播=${broadcastId}, 类型=${type}, 邮箱=${email}`); + - name: 📋 步骤 2/8 · 解析内容(成功) + if: success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '2' + STEP_NAME: '解析内容' + STEP_STATUS: 'ok' + STEP_DETAIL: '广播编号: `${{ steps.parse.outputs.broadcast_id }}` · 类型: ${{ steps.parse.outputs.type }} · 邮箱: ${{ steps.parse.outputs.email }}' + run: node scripts/pipeline-reporter.js + + - name: 📋 步骤 2/8 · 解析内容(失败) + if: failure() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '2' + STEP_NAME: '解析内容' + STEP_STATUS: 'error' + STEP_DETAIL: '解析失败,请检查提交格式是否正确(需包含广播编号、类型、邮箱、内容)' + run: node scripts/pipeline-reporter.js + - name: 🔍 铸渊 Agent · 模块上传验证 id: verify env: @@ -156,6 +151,28 @@ jobs: AUTHOR: ${{ steps.parse.outputs.author }} run: node scripts/verify-modules.js + - name: 📋 步骤 3/8 · 模块验证(成功) + if: always() && steps.verify.outcome == 'success' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '3' + STEP_NAME: '模块验证' + STEP_STATUS: 'ok' + STEP_DETAIL: '模块上传验证已完成' + run: node scripts/pipeline-reporter.js + + - name: 📋 步骤 3/8 · 模块验证(失败) + if: always() && steps.verify.outcome == 'failure' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '3' + STEP_NAME: '模块验证' + STEP_STATUS: 'error' + STEP_DETAIL: '模块验证异常(不影响闭环继续)' + run: node scripts/pipeline-reporter.js + - name: 🧠 Auto-detect and wake up persona id: persona env: @@ -172,6 +189,28 @@ jobs: FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }} run: node scripts/wake-persona.js + - name: 📋 步骤 4/8 · 唤醒核心大脑(成功) + if: success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '4' + STEP_NAME: '唤醒核心大脑' + STEP_STATUS: 'ok' + STEP_DETAIL: '铸渊核心大脑已完成处理' + run: node scripts/pipeline-reporter.js + + - name: 📋 步骤 4/8 · 唤醒核心大脑(失败) + if: failure() && steps.parse.outcome == 'success' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '4' + STEP_NAME: '唤醒核心大脑' + STEP_STATUS: 'error' + STEP_DETAIL: 'API 调用失败,请检查 Secrets: LLM_API_KEY / LLM_BASE_URL 是否配置正确' + run: node scripts/pipeline-reporter.js + - name: 📋 创建标准化 Notion 工单(Phase B1) id: ticket if: ${{ secrets.NOTION_API_TOKEN }} @@ -187,7 +226,41 @@ jobs: SYSLOG_RAW: ${{ steps.parse.outputs.content }} run: node scripts/create-standardized-ticket.js + - name: 📋 步骤 5/8 · 创建 Notion 工单(成功) + if: always() && steps.ticket.outcome == 'success' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '5' + STEP_NAME: '创建 Notion 工单' + STEP_STATUS: 'ok' + STEP_DETAIL: '已写入霜砚工单簿' + run: node scripts/pipeline-reporter.js + + - name: 📋 步骤 5/8 · 创建 Notion 工单(跳过) + if: always() && steps.ticket.outcome == 'skipped' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '5' + STEP_NAME: '创建 Notion 工单' + STEP_STATUS: 'skip' + STEP_DETAIL: 'Notion Token 未配置,跳过' + run: node scripts/pipeline-reporter.js + + - name: 📋 步骤 5/8 · 创建 Notion 工单(失败) + if: always() && steps.ticket.outcome == 'failure' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '5' + STEP_NAME: '创建 Notion 工单' + STEP_STATUS: 'error' + STEP_DETAIL: 'Notion API 写入失败' + run: node scripts/pipeline-reporter.js + - name: 📡 推送广播到 GitHub(Phase B4) + id: broadcast if: steps.persona.outcome == 'success' && steps.parse.outputs.type == 'syslog' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -196,7 +269,41 @@ jobs: BROADCAST_CONTENT: ${{ steps.persona.outputs.result }} run: node scripts/push-broadcast-to-github.js + - name: 📋 步骤 6/8 · 生成广播(成功) + if: always() && steps.broadcast.outcome == 'success' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '6' + STEP_NAME: '生成广播' + STEP_STATUS: 'ok' + STEP_DETAIL: '新广播已生成并推送' + run: node scripts/pipeline-reporter.js + + - name: 📋 步骤 6/8 · 生成广播(跳过) + if: always() && steps.broadcast.outcome == 'skipped' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '6' + STEP_NAME: '生成广播' + STEP_STATUS: 'skip' + STEP_DETAIL: '非 SYSLOG 类型或上游步骤未成功,跳过广播生成' + run: node scripts/pipeline-reporter.js + + - name: 📋 步骤 6/8 · 生成广播(失败) + if: always() && steps.broadcast.outcome == 'failure' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '6' + STEP_NAME: '生成广播' + STEP_STATUS: 'error' + STEP_DETAIL: '广播生成或推送失败' + run: node scripts/pipeline-reporter.js + - name: 📧 Send email to developer + id: email env: SMTP_USER: ${{ secrets.SMTP_USER }} SMTP_PASS: ${{ secrets.SMTP_PASS }} @@ -270,6 +377,17 @@ jobs: }); " + - name: 📋 步骤 7/8 · 邮件通知 + if: always() && steps.persona.outcome == 'success' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + STEP_NUM: '7' + STEP_NAME: '邮件通知' + STEP_STATUS: 'ok' + STEP_DETAIL: '邮件通知步骤已执行' + run: node scripts/pipeline-reporter.js + - name: 💬 Reply to Issue uses: actions/github-script@v7 with: @@ -280,7 +398,6 @@ jobs: const email = '${{ steps.parse.outputs.email }}'; const modulesUploaded = '${{ steps.verify.outputs.modules_uploaded }}' === 'true'; const moduleCount = '${{ steps.verify.outputs.module_count }}' || '0'; - const progressCommentId = '${{ steps.progress.outputs.comment_id }}'; const typeLabel = type === 'syslog' ? 'SYSLOG 闭环处理' : '问题解答'; const maskedEmail = email.length > 4 @@ -290,37 +407,8 @@ jobs: ? 'ℹ️ 未检测到模块引用' : (modulesUploaded ? '✅ 全部已上传' : '⚠️ 部分模块未上传'); - // Update progress comment to show all completed - if (progressCommentId && !isNaN(Number(progressCommentId))) { - try { - const progressBody = [ - '## ⚙️ SYSLOG 管道运行完成 ✅', - '', - '| 阶段 | 状态 | 说明 |', - '|------|------|------|', - '| ① 接收提交 | ✅ 已完成 | 系统已收到 |', - '| ② 解析内容 | ✅ 已完成 | 广播编号: `' + broadcastId + '` |', - '| ③ 模块验证 | ✅ 已完成 | ' + moduleStatus + ' |', - '| ④ 唤醒核心大脑 | ✅ 已完成 | 铸渊核心大脑已处理 |', - '| ⑤ 创建 Notion 工单 | ✅ 已完成 | 霜砚工单已推送 |', - '| ⑥ 生成广播 | ✅ 已完成 | 新广播已生成 |', - '| ⑦ 邮件通知 | ✅ 已完成 | 已发送至 `' + maskedEmail + '` |', - '| ⑧ 闭环确认 | ✅ 已完成 | 见下方最终结果 |', - ].join('\n'); - - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: Number(progressCommentId), - body: progressBody - }); - } catch (e) { - console.log('⚠️ 更新成功进度评论失败: ' + e.message); - } - } - const body = [ - '## ✅ 已处理 · ' + typeLabel, + '## 🎉 闭环处理完成 · ' + typeLabel, '', '| 项目 | 内容 |', '|------|------|', @@ -364,40 +452,14 @@ jobs: const number = context.payload.issue?.number; if (!number) return; - const progressCommentId = '${{ steps.progress.outputs.comment_id }}'; const actionsUrl = 'https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions'; - // Update progress comment to reflect failure - if (progressCommentId && !isNaN(Number(progressCommentId))) { - try { - const progressBody = [ - '## ⚙️ SYSLOG 管道运行异常 ❌', - '', - '| 阶段 | 状态 | 说明 |', - '|------|------|------|', - '| ① 接收提交 | ✅ 已完成 | 系统已收到 |', - '| ② ~ ⑧ | ❌ 异常 | 管道处理过程中出错 |', - '', - '> 请检查提交格式或查看 [Actions 日志](' + actionsUrl + ')', - ].join('\n'); - - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: Number(progressCommentId), - body: progressBody - }); - } catch (e) { - console.log('⚠️ 更新失败进度评论失败: ' + e.message); - } - } - try { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: number, - body: '❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 错误详情请查看 [Actions 日志](' + actionsUrl + ')。\n>\n> *—— 铸渊(ICE-GL-ZY001)*' + body: '## ⚠️ 闭环处理异常\n\n❌ **处理失败** · 请检查提交格式是否正确,或联系管理员。\n\n> 部分步骤异常,已记录日志。冰朔会在下次巡检时处理。\n> 错误详情请查看 [Actions 日志](' + actionsUrl + ')。\n>\n> *—— 铸渊(ICE-GL-ZY001)*' }); } catch (err) { console.log('⚠️ 失败告警回复失败: ' + err.message); diff --git a/.github/workflows/zhuyuan-issue-reply.yml b/.github/workflows/zhuyuan-issue-reply.yml index 3d5eff6e..90b44c60 100644 --- a/.github/workflows/zhuyuan-issue-reply.yml +++ b/.github/workflows/zhuyuan-issue-reply.yml @@ -51,23 +51,10 @@ jobs: '> **类型**: ' + submitType, '> **接收时间**: ' + now, '', - '### 📋 处理进度', + '💡 SYSLOG 自动管道已启动,预计 2-5 分钟完成全部处理。', + '每完成一步都会在下方更新进度评论,你刷新页面即可看到最新状态。', '', - '| 阶段 | 状态 | 说明 |', - '|------|------|------|', - '| ① 接收提交 | ✅ 已完成 | 系统已收到你的 SYSLOG |', - '| ② 解析内容 | ⏳ 等待中 | 解析广播编号、邮箱、内容 |', - '| ③ 模块验证 | ⏳ 等待中 | 检测模块是否已上传到仓库 |', - '| ④ 唤醒核心大脑 | ⏳ 等待中 | 铸渊核心大脑处理 SYSLOG |', - '| ⑤ 创建 Notion 工单 | ⏳ 等待中 | 推送霜砚工单到 Notion |', - '| ⑥ 生成广播 | ⏳ 等待中 | 生成新广播内容 |', - '| ⑦ 邮件通知 | ⏳ 等待中 | 发送结果到你的邮箱 |', - '| ⑧ 闭环确认 | ⏳ 等待中 | Issue 回复 + 关闭 |', - '', - '> 💡 SYSLOG 自动管道已启动,预计 2-5 分钟完成全部处理。', - '> 完成后本 Issue 下方会出现最终结果,你的邮箱也会收到通知。', - '>', - '> 如超过 10 分钟无后续回复,请在下方评论中 @铸渊 查询状态。', + '> 如超过 10 分钟无后续回复,请在下方评论中 `@铸渊 查询#' + number + '闭环状态`。', '>', '> *—— 铸渊(ICE-GL-ZY001)· 代码守护人格体*' ].join('\n'); diff --git a/scripts/intent-router.js b/scripts/intent-router.js new file mode 100644 index 00000000..2ab49946 --- /dev/null +++ b/scripts/intent-router.js @@ -0,0 +1,237 @@ +/** + * ━━━ 意图路由模块 ━━━ + * 文件位置:scripts/intent-router.js + * + * 解析用户在 Issue 评论中 @铸渊 的意图,路由到对应处理逻辑。 + * 用于替换原来的「无脑倒全部数据」行为。 + */ + +const https = require('https'); +const fs = require('fs'); + +/** + * 解析用户在 Issue 评论中 @铸渊 的意图 + * @param {string} commentBody - 评论原文 + * @returns {object} 意图对象 + */ +function parseIntent(commentBody) { + // 去掉 @铸渊 前缀,提取核心内容 + const content = commentBody.replace(/@铸渊[,,]?\s*/g, '').trim(); + + // 意图1:查询某个 Issue 的闭环状态 + // 匹配:「查询#92闭环状态」「#92进度」「Issue 92 状态」「闭环状态」等 + const issueMatch = content.match(/#(\d+)/); + if (issueMatch && /闭环|状态|进度|处理/.test(content)) { + return { + type: 'check_pipeline_status', + issueNumber: parseInt(issueMatch[1]), + raw: content + }; + } + + // 意图2:查询某个开发者的状态 + // 匹配:「DEV-004状态」「之之进度」「查询肥猫」 + const devMatch = content.match(/DEV-(\d+)|页页|肥猫|燕樊|之之|小草莓|花尔|桔子|匆匆那年|Awen|小兴|时雨/); + if (devMatch && /状态|进度|查询/.test(content)) { + return { + type: 'check_dev_status', + devId: devMatch[0], + raw: content + }; + } + + // 意图3:团队总览(显式请求) + // 匹配:「团队状态」「全部进度」「总览」 + if (/团队|全部|总览|所有人/.test(content)) { + return { + type: 'team_overview', + raw: content + }; + } + + // 意图4:未识别 → 返回 unknown + return { + type: 'unknown', + raw: content + }; +} + +/** + * 查询 Issue 闭环管道状态(通过评论历史) + * @param {number} issueNumber - 要查询的 Issue 编号 + * @returns {string} 格式化的状态回复 + */ +async function checkPipelineStatus(issueNumber) { + const [owner, repo] = (process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab').split('/'); + + // 获取该 Issue 的评论历史 + let comments; + try { + comments = await githubAPI('GET', + `/repos/${owner}/${repo}/issues/${issueNumber}/comments?per_page=100`); + } catch (e) { + return `📋 **Issue #${issueNumber} 闭环状态查询**\n\n` + + `查询失败: ${e.message}\n` + + `请确认 Issue 编号是否正确。`; + } + + if (!Array.isArray(comments) || comments.length === 0) { + return `📋 **Issue #${issueNumber} 闭环状态查询**\n\n` + + `未找到该 Issue 的评论记录。\n` + + `可能原因:该 Issue 不存在或没有任何评论。`; + } + + // 筛选铸渊的进度评论(包含「步骤 X/8」的) + const progressComments = comments.filter(c => + c.body && c.body.includes('步骤') && c.body.includes('/8') && c.body.includes('铸渊') + ); + + // 检查是否有闭环完成汇总 + const completeComment = comments.find(c => + c.body && (c.body.includes('闭环处理完成') || c.body.includes('闭环处理异常')) + ); + + if (progressComments.length === 0 && !completeComment) { + // 检查是否有旧版静态进度表格 + const staticTable = comments.find(c => + c.body && c.body.includes('管道运行') && c.body.includes('等待中') + ); + + if (staticTable) { + return `📋 **Issue #${issueNumber} 闭环状态查询**\n\n` + + `⏳ 管道已启动但后续步骤未上报进度。\n` + + `可能原因:管道处理脚本未触发,或中间步骤出错。\n\n` + + `> 建议检查 Actions 运行日志。`; + } + + return `📋 **Issue #${issueNumber} 闭环状态查询**\n\n` + + `未找到该 Issue 的闭环进度记录。\n` + + `可能原因:闭环尚未启动,或该 Issue 不是 SYSLOG 提交。`; + } + + // 有进度评论:提取最新状态 + if (completeComment) { + const isSuccess = completeComment.body.includes('闭环处理完成'); + const hasFailed = comments.some(c => c.body && c.body.includes('❌')); + + return `📋 **Issue #${issueNumber} 闭环状态**\n\n` + + `${isSuccess && !hasFailed ? '✅ 闭环已完成' : '⚠️ 闭环有步骤异常'}\n` + + `进度评论数: ${progressComments.length} 条\n` + + `最后更新: ${completeComment.created_at}`; + } + + // 进行中 + const lastProgress = progressComments[progressComments.length - 1]; + const stepMatch = lastProgress.body.match(/步骤 (\d+)\/8/); + const lastStep = stepMatch ? parseInt(stepMatch[1]) : 0; + const hasFailed = progressComments.some(c => c.body.includes('❌')); + + let statusLine; + if (hasFailed) { + statusLine = '❌ 闭环有步骤失败(详见该 Issue 下各步骤评论)'; + } else { + statusLine = `⏳ 闭环进行中 · 已完成到步骤 ${lastStep}/8`; + } + + return `📋 **Issue #${issueNumber} 闭环状态**\n\n` + + `${statusLine}\n` + + `进度评论数: ${progressComments.length} 条\n` + + `最后更新: ${lastProgress.created_at}`; +} + +/** + * 查询指定开发者的状态 + * @param {string} devIdOrName - DEV-XXX 或开发者昵称 + * @param {object} devStatus - dev-status.json 数据 + * @returns {string} 格式化的状态回复 + */ +function checkDevStatus(devIdOrName, devStatus) { + const team = devStatus.team_status || devStatus.team || []; + const dev = team.find(d => + d.dev_id === devIdOrName || + d.name === devIdOrName || + (d.dev_id && d.dev_id.toUpperCase() === devIdOrName.toUpperCase()) + ); + + if (!dev) { + return `未找到开发者 ${devIdOrName} 的记录。`; + } + + const modules = dev.modules + ? dev.modules.join('、') + : (dev.module || '未知'); + + return `📊 **${dev.dev_id} ${dev.name} · 当前状态**\n\n` + + `- 📌 模块: ${modules}\n` + + `- 📊 状态: ${dev.status}\n` + + `- ⏳ 等待中: ${dev.waiting_for || dev.waiting || '无'}\n` + + `- 👉 下一步: ${dev.next_step || dev.current || '无'}\n` + + `- 🔥 连胜: ${dev.streak || 0}`; +} + +/** + * 团队总览 + * @param {object} devStatus - dev-status.json 数据 + * @returns {string} 格式化的总览 + */ +function formatTeamOverview(devStatus) { + const team = devStatus.team_status || devStatus.team || []; + let reply = `## ⚒️ 铸渊回复 · 团队进度总览\n\n`; + team.forEach(dev => { + reply += `**${dev.dev_id} ${dev.name}** · ${dev.status}\n`; + }); + reply += `\n---\n*最后同步:${devStatus.last_synced || devStatus.last_sync}*`; + return reply; +} + +/** + * 生成「未识别意图」的帮助提示 + * @returns {string} + */ +function unknownIntentHelp() { + return `🤔 我没理解你的问题。你可以试试:\n\n` + + `- \`@铸渊 查询#92闭环状态\` — 查某个 SYSLOG 提交的处理进度\n` + + `- \`@铸渊 DEV-004状态\` — 查某个开发者的进度\n` + + `- \`@铸渊 团队总览\` — 查所有人的状态`; +} + +// ━━━ GitHub API 工具 ━━━ +function githubAPI(method, path, data) { + return new Promise((resolve, reject) => { + const body = data ? JSON.stringify(data) : null; + const req = https.request({ + hostname: 'api.github.com', + path: path, + method: method, + headers: { + 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'Zhuyuan-Intent-Router', + 'Content-Type': 'application/json', + ...(body ? { 'Content-Length': Buffer.byteLength(body) } : {}) + } + }, (res) => { + let responseBody = ''; + res.on('data', chunk => { responseBody += chunk; }); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { resolve(JSON.parse(responseBody)); } + catch { resolve(responseBody); } + } else { + reject(new Error(`GitHub API ${res.statusCode}: ${responseBody.slice(0, 200)}`)); + } + }); + }); + req.on('error', reject); + if (body) req.write(body); + req.end(); + }); +} + +module.exports = { + parseIntent, + checkPipelineStatus, + checkDevStatus, + formatTeamOverview, + unknownIntentHelp +}; diff --git a/scripts/pipeline-reporter.js b/scripts/pipeline-reporter.js new file mode 100644 index 00000000..e11dabed --- /dev/null +++ b/scripts/pipeline-reporter.js @@ -0,0 +1,140 @@ +/** + * ━━━ 实时进度上报模块 ━━━ + * 文件位置:scripts/pipeline-reporter.js + * + * 每完成一步,在 Issue 评论区发一条新评论,开发者刷新即可看到实时进度。 + * + * 使用方式(CLI): + * ISSUE_NUMBER=92 STEP_NUM=2 STEP_NAME="解析内容" STEP_STATUS=ok \ + * STEP_DETAIL="广播编号: BC-M22-007-AW" GITHUB_TOKEN=xxx \ + * node scripts/pipeline-reporter.js + * + * 或在 Node.js 中 require: + * const { reportStep, reportComplete } = require('./pipeline-reporter'); + */ + +const https = require('https'); + +const OWNER = process.env.GITHUB_REPOSITORY + ? process.env.GITHUB_REPOSITORY.split('/')[0] + : 'qinfendebingshuo'; +const REPO = process.env.GITHUB_REPOSITORY + ? process.env.GITHUB_REPOSITORY.split('/')[1] + : 'guanghulab'; + +/** + * 发送 GitHub API 请求 + */ +function githubAPI(method, path, data) { + return new Promise((resolve, reject) => { + const body = data ? JSON.stringify(data) : null; + const req = https.request({ + hostname: 'api.github.com', + path: path, + method: method, + headers: { + 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'Zhuyuan-Pipeline-Reporter', + 'Content-Type': 'application/json', + ...(body ? { 'Content-Length': Buffer.byteLength(body) } : {}) + } + }, (res) => { + let responseBody = ''; + res.on('data', chunk => { responseBody += chunk; }); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { resolve(JSON.parse(responseBody)); } + catch { resolve(responseBody); } + } else { + reject(new Error(`GitHub API ${res.statusCode}: ${responseBody.slice(0, 200)}`)); + } + }); + }); + req.on('error', reject); + if (body) req.write(body); + req.end(); + }); +} + +/** + * 每完成一步,在 Issue 评论区发一条新评论 + * @param {number} issueNumber - Issue 编号 + * @param {number} stepNum - 步骤编号(1-8) + * @param {string} stepName - 步骤名称 + * @param {'ok'|'error'|'skip'} status - 步骤状态 + * @param {string} detail - 详情说明 + */ +async function reportStep(issueNumber, stepNum, stepName, status, detail) { + const icon = status === 'ok' ? '✅' : status === 'error' ? '❌' : '⏭️'; + const statusText = status === 'ok' ? '已完成' : status === 'error' ? '失败' : '跳过'; + + const body = [ + `${icon} **步骤 ${stepNum}/8 · ${stepName}** · ${statusText}`, + '', + detail, + '', + `——铸渊(ICE-GL-ZY001)· ${new Date().toISOString()}` + ].join('\n'); + + await githubAPI('POST', `/repos/${OWNER}/${REPO}/issues/${issueNumber}/comments`, { body }); +} + +/** + * 闭环完成时发最终汇总 + * @param {number} issueNumber - Issue 编号 + * @param {boolean} success - 是否全部成功 + * @param {string} summary - 汇总内容 + */ +async function reportComplete(issueNumber, success, summary) { + const icon = success ? '🎉' : '⚠️'; + const title = success ? '闭环处理完成' : '闭环处理异常'; + + const body = [ + `## ${icon} ${title}`, + '', + summary, + '', + success + ? '> 新广播已生成,结果将发送到您的邮箱。' + : '> 部分步骤异常,已记录日志。冰朔会在下次巡检时处理。', + '', + `——铸渊(ICE-GL-ZY001)· ${new Date().toISOString()}` + ].join('\n'); + + await githubAPI('POST', `/repos/${OWNER}/${REPO}/issues/${issueNumber}/comments`, { body }); +} + +// ━━━ CLI 模式:直接 node scripts/pipeline-reporter.js ━━━ +if (require.main === module) { + const issueNumber = parseInt(process.env.ISSUE_NUMBER); + const stepNum = parseInt(process.env.STEP_NUM); + const stepName = process.env.STEP_NAME || ''; + const status = process.env.STEP_STATUS || 'ok'; + const detail = process.env.STEP_DETAIL || ''; + const isComplete = process.env.REPORT_COMPLETE === 'true'; + const isSuccess = process.env.REPORT_SUCCESS !== 'false'; + + if (!issueNumber) { + console.error('❌ 缺少 ISSUE_NUMBER'); + process.exit(1); + } + + (async () => { + try { + if (isComplete) { + await reportComplete(issueNumber, isSuccess, detail); + console.log(`📋 闭环汇总已发送 · Issue #${issueNumber}`); + } else { + await reportStep(issueNumber, stepNum, stepName, status, detail); + console.log(`📋 步骤 ${stepNum}/8 · ${stepName} · ${status} 已上报 · Issue #${issueNumber}`); + } + } catch (err) { + console.error(`⚠️ 进度上报失败: ${err.message}`); + // 上报失败不应阻断管道 + process.exit(0); + } + })(); +} + +module.exports = { reportStep, reportComplete }; diff --git a/scripts/zhuyuan-issue-reply.js b/scripts/zhuyuan-issue-reply.js index 68089494..da558105 100644 --- a/scripts/zhuyuan-issue-reply.js +++ b/scripts/zhuyuan-issue-reply.js @@ -1,5 +1,6 @@ const fs = require('fs'); const https = require('https'); +const { parseIntent, checkPipelineStatus, checkDevStatus, formatTeamOverview, unknownIntentHelp } = require('./intent-router'); // === 知识库匹配阈值:问题词中至少40%出现在Issue文本中才算命中 === const FAQ_MATCH_THRESHOLD = 0.4; @@ -166,53 +167,91 @@ async function handleCommentTrigger() { return handleCollaboratorComment(user); } -// === 冰朔评论处理 === +// === 冰朔评论处理(含意图路由)=== async function handleBingshuoComment() { console.log('🧊 冰朔指令识别中...'); - // 冰朔可以查询任何人的状态 - if (commentBody.includes('进度') || commentBody.includes('状态')) { - if (devId && devInfo) { - const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n` - + `**${devInfo.name}(${devInfo.dev_id})当前状态:**\n` - + `- 📌 模块:${devInfo.modules.join('、')}\n` - + `- 📊 状态:${devInfo.status}\n` - + `- ⏳ 等待中:${devInfo.waiting_for}\n` - + `- 👉 下一步:${devInfo.next_step}\n\n` - + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced}*\n` + // 使用意图路由器解析评论 + const intent = parseIntent(commentBody); + console.log('[意图识别]', JSON.stringify(intent)); + + switch (intent.type) { + case 'check_pipeline_status': { + // 查询某个 Issue 的闭环管道状态 + const statusReply = await checkPipelineStatus(intent.issueNumber); + const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n${statusReply}\n\n` + + `---\n*—— 铸渊(ICE-GL-ZY001)*`; + await postComment(reply); + return; + } + + case 'check_dev_status': { + // 查询某个开发者的状态 + const devReply = checkDevStatus(intent.devId, devStatus); + const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n${devReply}\n\n` + + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced || devStatus.last_sync}*\n` + `*—— 铸渊(ICE-GL-ZY001)*`; await postComment(reply); return; } - // 团队总览 - let reply = `## ⚒️ 铸渊回复 · 团队进度总览\n\n`; - devStatus.team_status.forEach(dev => { - reply += `**${dev.dev_id} ${dev.name}** · ${dev.status}\n`; - }); - reply += `\n---\n*最后同步:${devStatus.last_synced}*\n`; - reply += `*—— 铸渊(ICE-GL-ZY001)*`; - await postComment(reply); - return; - } + case 'team_overview': { + // 显式请求团队总览 + const reply = formatTeamOverview(devStatus) + + `\n*—— 铸渊(ICE-GL-ZY001)*`; + await postComment(reply); + return; + } - // 冰朔的一般指令 → 用 AI 处理 - const aiReply = await callYunwuAPI('冰朔指令', commentBody, null); - if (aiReply) { - const reply = `## ⚒️ 铸渊回复 · 冰朔\n\n${aiReply}\n\n` - + `---\n*—— 铸渊(ICE-GL-ZY001)· 冰朔指令已处理*`; - await postComment(reply); - } else { - const reply = `## ⚒️ 铸渊收到\n\n冰朔,已记录你的指令。铸渊会在下次巡检时处理。\n\n` - + `---\n*—— 铸渊(ICE-GL-ZY001)*`; - await postComment(reply); + default: { + // 未识别的意图:尝试 AI 回答,或返回帮助提示 + // 冰朔可以查询任何人的状态(兼容旧逻辑) + if ((commentBody.includes('进度') || commentBody.includes('状态')) && devId && devInfo) { + const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n` + + `**${devInfo.name}(${devInfo.dev_id})当前状态:**\n` + + `- 📌 模块:${devInfo.modules.join('、')}\n` + + `- 📊 状态:${devInfo.status}\n` + + `- ⏳ 等待中:${devInfo.waiting_for}\n` + + `- 👉 下一步:${devInfo.next_step}\n\n` + + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced}*\n` + + `*—— 铸渊(ICE-GL-ZY001)*`; + await postComment(reply); + return; + } + + // 冰朔的一般指令 → 用 AI 处理 + const aiReply = await callYunwuAPI('冰朔指令', commentBody, null); + if (aiReply) { + const reply = `## ⚒️ 铸渊回复 · 冰朔\n\n${aiReply}\n\n` + + `---\n*—— 铸渊(ICE-GL-ZY001)· 冰朔指令已处理*`; + await postComment(reply); + } else { + const helpText = unknownIntentHelp(); + const reply = `## ⚒️ 铸渊收到\n\n冰朔,已记录你的指令。\n\n${helpText}\n\n` + + `---\n*—— 铸渊(ICE-GL-ZY001)*`; + await postComment(reply); + } + } } } -// === 合作者评论处理 === +// === 合作者评论处理(含意图路由)=== async function handleCollaboratorComment(user) { console.log(`👤 合作者 ${user.name}(${user.devId})问题处理中...`); + // 使用意图路由器解析评论 + const intent = parseIntent(commentBody); + console.log('[意图识别]', JSON.stringify(intent)); + + // 合作者查询闭环管道状态 + if (intent.type === 'check_pipeline_status') { + const statusReply = await checkPipelineStatus(intent.issueNumber); + const reply = `## ⚒️ 铸渊回复\n\n${statusReply}\n\n` + + `---\n*—— 铸渊(ICE-GL-ZY001)*`; + await postComment(reply); + return; + } + // 合作者只能查询自己的状态 const selfDevInfo = user.devId ? devStatus.team_status.find(d => d.dev_id === user.devId)