fix: replace static progress table with per-step comments + add intent routing for @铸渊 queries
Bug A: syslog-issue-pipeline.yml now posts individual progress comments for each step (1-8) instead of a static table that never updates. zhuyuan-issue-reply.yml syslog-ack simplified to receipt confirmation. Bug B: zhuyuan-issue-reply.js now uses intent-router.js to parse @铸渊 queries. Pipeline status queries (#92闭环状态) are routed correctly instead of dumping team overview data. Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
d7147274f8
commit
2bd9a84abb
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue