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:
copilot-swe-agent[bot] 2026-03-13 15:44:55 +00:00
parent d7147274f8
commit 2bd9a84abb
5 changed files with 607 additions and 142 deletions

View File

@ -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: 📡 推送广播到 GitHubPhase 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);

View File

@ -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');

237
scripts/intent-router.js Normal file
View File

@ -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
};

View File

@ -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 };

View File

@ -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)