forked from Gitlink/gitlink-cli
Compare commits
14 Commits
master
...
fix/issue-
| Author | SHA1 | Date |
|---|---|---|
|
|
602c09c8dd | |
|
|
3965579e48 | |
|
|
c7842c849b | |
|
|
d9189e5ef4 | |
|
|
34d66521ad | |
|
|
9811cd8fdc | |
|
|
c5f892bc92 | |
|
|
4d6f224bc4 | |
|
|
7bde356bac | |
|
|
ed89db94a0 | |
|
|
608fc70831 | |
|
|
8589eb9280 | |
|
|
40609fa0f8 | |
|
|
3a1565422c |
|
|
@ -69,7 +69,7 @@ and --page and --limit for pagination.`,
|
|||
if u := ctx.Arg("until"); u != "" {
|
||||
q.Set("until", u)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/builds", q)
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1%s/actions/runs", ctx.RepoPath()), q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -365,6 +365,7 @@ Prompts for confirmation before stopping the build.`,
|
|||
Flags: []common.Flag{
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
{Name: "name", Short: "n", Usage: "Pipeline name (optional)"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -373,6 +374,9 @@ Prompts for confirmation before stopping the build.`,
|
|||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
if n := ctx.Arg("name"); n != "" {
|
||||
q.Set("name", n)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1%s/pipelines/run_results", ctx.RepoPath()), q)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -7,7 +7,15 @@
|
|||
# ================================================================
|
||||
set -e
|
||||
|
||||
# ---- 参数解析 ----
|
||||
# 使用本地编译的 gitlink-cli(支持完整命令)
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
LOCAL_CLI="$(cd "$SCRIPT_DIR/../../.." && pwd)/gitlink-cli.exe"
|
||||
if [ -f "$LOCAL_CLI" ]; then
|
||||
CLI="$LOCAL_CLI"
|
||||
else
|
||||
CLI="gitlink-cli"
|
||||
fi
|
||||
|
||||
OWNER=${1:?"Usage: $0 <owner> <repo>"}
|
||||
REPO=${2}
|
||||
DRY_RUN=${DRY_RUN:-true}
|
||||
|
|
@ -21,15 +29,14 @@ echo "[ Target: $OWNER/$REPO ]"
|
|||
echo "[ DRY_RUN: $DRY_RUN ]"
|
||||
echo "[========================================]"
|
||||
|
||||
# ---- Step 1: 获取所有打开的 Issue ----
|
||||
# Step 1: 获取所有打开的 Issue
|
||||
echo "[INFO] 获取所有打开的 Issue..."
|
||||
ISSUES=$(gitlink-cli issue +list --owner "$OWNER" --repo "$REPO" --state open --format json 2>&1) || {
|
||||
ISSUES=$($CLI issue +list --owner "$OWNER" --repo "$REPO" --state open --format json 2>&1) || {
|
||||
echo "[ERROR] 获取 Issue 列表失败: $ISSUES"
|
||||
exit 1
|
||||
}
|
||||
echo "$ISSUES" > "$OUTPUT_DIR/issues-open.json"
|
||||
|
||||
# 解析 Issue 数量(支持两种 data 格式:数组或 {total_count, issues[]})
|
||||
TOTAL=$(echo "$ISSUES" | node -e "
|
||||
let s='';
|
||||
process.stdin.on('data',d=>s+=d).on('end',()=>{
|
||||
|
|
@ -40,25 +47,22 @@ TOTAL=$(echo "$ISSUES" | node -e "
|
|||
" 2>/dev/null || echo "0")
|
||||
|
||||
echo "[INFO] 发现 $TOTAL 个待处理的 Issue"
|
||||
|
||||
if [ "$TOTAL" -eq 0 ]; then
|
||||
echo "[INFO] 没有待处理的 Issue,跳过"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---- Step 2: 获取可用标签 ----
|
||||
# Step 2: 获取可用标签
|
||||
echo "[INFO] 获取可用标签列表..."
|
||||
LABELS=$(gitlink-cli label +list --owner "$OWNER" --repo "$REPO" --format json 2>&1) || {
|
||||
echo "[WARN] 获取标签列表失败,使用默认标签: $LABELS"
|
||||
LABELS=$($CLI label +list --owner "$OWNER" --repo "$REPO" --format json 2>&1) || {
|
||||
echo "[WARN] 获取标签列表失败"
|
||||
LABELS='{"data":[]}'
|
||||
}
|
||||
echo "$LABELS" > "$OUTPUT_DIR/labels.json"
|
||||
|
||||
# ---- Step 3: 逐 Issue 分析 ----
|
||||
# Step 3: 逐 Issue 分析
|
||||
echo "[INFO] 开始分析每个 Issue..."
|
||||
|
||||
for i in $(seq 0 $((TOTAL - 1))); do
|
||||
# 提取 Issue 基本信息
|
||||
ISSUE_DATA=$(echo "$ISSUES" | node -e "
|
||||
let s='';
|
||||
process.stdin.on('data',d=>s+=d).on('end',()=>{
|
||||
|
|
@ -79,35 +83,52 @@ for i in $(seq 0 $((TOTAL - 1))); do
|
|||
echo ""
|
||||
echo "[INFO] --- Issue #$NUMBER: $SUBJECT ---"
|
||||
|
||||
# 获取 Issue 详情(含 description)
|
||||
DETAIL=$(gitlink-cli issue +view --owner "$OWNER" --repo "$REPO" --number "$NUMBER" --format json 2>&1) || {
|
||||
# 获取 Issue 详情
|
||||
DETAIL=$($CLI issue +view --owner "$OWNER" --repo "$REPO" --number "$NUMBER" --format json 2>&1) || {
|
||||
echo "[WARN] 获取 Issue #$NUMBER 详情失败,使用标题分析"
|
||||
DETAIL="{\"data\":{\"subject\":\"$SUBJECT\",\"description\":\"\"}}"
|
||||
}
|
||||
echo "$DETAIL" > "$OUTPUT_DIR/issue-$NUMBER-detail.json"
|
||||
|
||||
# ---- 关键词权重分析(AI 在此处执行) ----
|
||||
# AI Agent 读取 title + description,按权重矩阵计算分类得分
|
||||
# 注意:以下分析由 AI 在运行脚本时根据实际内容进行
|
||||
echo "[INFO] AI 分析中...(关键词权重评分)"
|
||||
echo "[INFO] 分析依据:gitlink-issue-triage/SKILL.md 权重分类矩阵"
|
||||
echo "[INFO] 参考命令:issue +view -> labels -> issue +update + issue +comment"
|
||||
|
||||
# 生成分类建议(占位,AI 在实际运行时填入)
|
||||
# ---- Step 4: 关键词权重分析(自动分类) ----
|
||||
CATEGORY="needs-triage"
|
||||
LABEL_ID=""
|
||||
URGENCY="P3"
|
||||
COMMENT_BODY=""
|
||||
LABEL_NAME=""
|
||||
COMMENT_TEXT=""
|
||||
|
||||
# ---- Step 4: 打标签(dry-run 保护) ----
|
||||
# 简单关键词匹配
|
||||
case "$SUBJECT" in
|
||||
*[Bb]ug*|*错误*|*失败*|*异常*|*crash*|*报错*)
|
||||
CATEGORY="bug"; LABEL_ID="325573"; LABEL_NAME="缺陷"
|
||||
COMMENT_TEXT="AI 自动分类结果:🐛 Bug(缺陷)"
|
||||
;;
|
||||
*建议*|*希望*|*feature*|*新增*|*支持*|*Shortcut*|*命令*|*兼容*|*封装*|*API*|*子任务*|*Webhook*|*Wiki*|*Snippet*)
|
||||
CATEGORY="enhancement"; LABEL_ID="325574"; LABEL_NAME="功能"
|
||||
COMMENT_TEXT="AI 自动分类结果:✨ 功能需求"
|
||||
;;
|
||||
*文档*|*README*|*拼写*|*帮助*|*doc*|*说明*)
|
||||
CATEGORY="documentation"; LABEL_ID="325580"; LABEL_NAME="文档"
|
||||
COMMENT_TEXT="AI 自动分类结果:📝 文档"
|
||||
;;
|
||||
*demo*|*测试*|*test*)
|
||||
CATEGORY="test"; LABEL_ID="325581"; LABEL_NAME="测试"
|
||||
COMMENT_TEXT="AI 自动分类结果:🧪 测试"
|
||||
;;
|
||||
esac
|
||||
echo "[INFO] 分类结果: $CATEGORY (标签: $LABEL_NAME)"
|
||||
|
||||
# ---- Step 5: 打标签 + 加评论 ----
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "[DRY-RUN] 将为 Issue #$NUMBER 添加标签: \$CATEGORY"
|
||||
echo "[DRY-RUN] 将添加引导评论"
|
||||
echo "[DRY-RUN] 将为 Issue #$NUMBER 添加标签: $LABEL_NAME ($LABEL_ID)"
|
||||
echo "[DRY-RUN] 将添加引导评论: $COMMENT_TEXT"
|
||||
else
|
||||
echo "[INFO] 为 Issue #$NUMBER 打标签和添加评论..."
|
||||
# 实际执行(由 AI 填入具体参数)
|
||||
# gitlink-cli issue +update --owner "$OWNER" --repo "$REPO" --number "$NUMBER" --labels "$LABEL_ID"
|
||||
# gitlink-cli issue +comment --owner "$OWNER" --repo "$REPO" --number "$NUMBER" --body "$COMMENT_BODY"
|
||||
if [ -n "$LABEL_ID" ]; then
|
||||
$CLI issue +batch-update --owner "$OWNER" --repo "$REPO" --numbers "$NUMBER" --label "$LABEL_ID" 2>&1 || echo "[WARN] 打标签失败"
|
||||
fi
|
||||
if [ -n "$COMMENT_TEXT" ]; then
|
||||
$CLI issue +comment --owner "$OWNER" --repo "$REPO" --number "$NUMBER" --body "$COMMENT_TEXT" 2>&1 || echo "[WARN] 添加评论失败"
|
||||
fi
|
||||
echo "[INFO] Issue #$NUMBER 处理完成"
|
||||
fi
|
||||
done
|
||||
|
|
|
|||
|
|
@ -25,13 +25,16 @@ PRS=$(gitlink-cli pr +list --owner "$OWNER" --repo "$REPO" --state open --format
|
|||
}
|
||||
echo "$PRS" > "$OUTPUT_DIR/prs-open.json"
|
||||
|
||||
# 过滤真实 PR(pull_request_status=0 表示打开中的 PR)
|
||||
# 过滤打开的 PR(status 可能是数字 0 或字符串 "open"/"opened")
|
||||
REAL_PRS=$(echo "$PRS" | node -e "
|
||||
let s='';
|
||||
process.stdin.on('data',d=>s+=d).on('end',()=>{
|
||||
const d=JSON.parse(s).data||[];
|
||||
const items=Array.isArray(d)?d:(d.pull_requests||d);
|
||||
const real=items.filter(p=>(p.pull_request_status||p.status||0)==0);
|
||||
const d=JSON.parse(s).data||{};
|
||||
const items=Array.isArray(d)?d:(d.pulls||d.pull_requests||[]);
|
||||
const real=items.filter(p=>{
|
||||
const st=p.pull_request_status||p.status||'';
|
||||
return st===0||st==='0'||st==='open'||st==='opened';
|
||||
});
|
||||
process.stdout.write(JSON.stringify(real));
|
||||
});
|
||||
" 2>/dev/null || echo "[]")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# ================================================================
|
||||
# Phase 3: CI 状态检查
|
||||
# 功能:检查 PR 关联的 CI 构建状态
|
||||
# 功能:检查 PR 关联的 CI 构建状态(尝试多个端点)
|
||||
# 使用:bash 03-ci-check.sh <owner> <repo> <pr_id>
|
||||
# ================================================================
|
||||
set -e
|
||||
|
|
@ -18,31 +18,55 @@ echo "[ Phase 3: CI 状态检查 ]"
|
|||
echo "[ PR #$PR_ID ($OWNER/$REPO) ]"
|
||||
echo "[========================================]"
|
||||
|
||||
# Step 1: 获取 CI 构建列表
|
||||
echo "[INFO] 获取 CI 构建列表..."
|
||||
BUILDS=$(gitlink-cli ci +builds --owner "$OWNER" --repo "$REPO" --format json 2>&1) || {
|
||||
echo "[WARN] 获取构建列表失败: $BUILDS"
|
||||
BUILDS='{"data":[]}'
|
||||
}
|
||||
echo "$BUILDS" > "$OUTPUT_DIR/ci-builds.json"
|
||||
CI_PASSING="unknown"
|
||||
CI_DETAIL=""
|
||||
|
||||
# Step 2: 获取 CI 运行记录
|
||||
echo "[INFO] 获取 CI 运行记录..."
|
||||
RUNS=$(gitlink-cli ci +runs --owner "$OWNER" --repo "$REPO" --format json 2>&1) || {
|
||||
echo "[WARN] 获取运行记录失败(可能不支持): $RUNS"
|
||||
RUNS='{"data":[]}'
|
||||
}
|
||||
echo "$RUNS" > "$OUTPUT_DIR/ci-runs.json"
|
||||
# 方法1: ci +builds(Actions 系统)
|
||||
echo "[INFO] 查询 CI 运行记录(Actions API)..."
|
||||
BUILDS=$(gitlink-cli ci +builds --owner "$OWNER" --repo "$REPO" --format json 2>&1) || BUILDS=""
|
||||
if echo "$BUILDS" | grep -q '"runs"'; then
|
||||
echo "$BUILDS" > "$OUTPUT_DIR/ci-builds.json"
|
||||
CI_PASSING=$(echo "$BUILDS" | node -e "
|
||||
let s='';
|
||||
process.stdin.on('data',d=>s+=d).on('end',()=>{
|
||||
try {
|
||||
const d=JSON.parse(s).data||{};
|
||||
const runs=d.runs||[];
|
||||
if(runs.length===0){process.stdout.write('unknown');return;}
|
||||
const r=runs.some(x=>x.status==='running'||x.status==='pending');
|
||||
const ok=runs.some(x=>x.status==='success'||x.status==='completed');
|
||||
const fail=runs.some(x=>x.status==='fail'||x.status==='error');
|
||||
process.stdout.write(r?'running':ok&&!fail?'passing':fail?'failing':'unknown');
|
||||
}catch(e){process.stdout.write('unknown');}
|
||||
});
|
||||
" 2>/dev/null || echo "unknown")
|
||||
CI_DETAIL="Actions API: ${CI_PASSING}"
|
||||
fi
|
||||
|
||||
# Step 3: AI 分析 CI 状态
|
||||
echo "[INFO] AI 分析 CI 状态中..."
|
||||
echo "[INFO] 检查:PR 分支关联的最新构建状态"
|
||||
# 方法2: ci +run-results(DevOps/建木 系统)
|
||||
if [ "$CI_PASSING" = "unknown" ]; then
|
||||
echo "[INFO] 查询 DevOps 流水线状态(DevOps API)..."
|
||||
RESULTS=$(gitlink-cli ci +run-results --owner "$OWNER" --repo "$REPO" --limit 5 --format json 2>&1) || RESULTS=""
|
||||
echo "$RESULTS" > "$OUTPUT_DIR/ci-run-results.json"
|
||||
fi
|
||||
|
||||
# AI Agent 在此处解析 CI 数据,判断是否全部通过
|
||||
# 输出:CI 状态布尔值、详细信息
|
||||
# 最终结果
|
||||
if [ "$CI_PASSING" = "unknown" ]; then
|
||||
echo "[INFO] 当前无法获取 CI 状态"
|
||||
echo "[INFO] 原因:GitLink 的 Actions API 和 DevOps API 均未返回运行记录"
|
||||
echo "[INFO] 提示:流水线可能通过 DevOps/建木引擎运行,该引擎的 API 未在 CLI 中完整实现"
|
||||
CI_DETAIL="CLI 暂不支持查询 DevOps 流水线状态"
|
||||
fi
|
||||
|
||||
# 写入状态文件供 Phase 4 读取
|
||||
echo "$CI_PASSING" > "$OUTPUT_DIR/ci-status-$PR_ID.txt"
|
||||
echo "$CI_DETAIL" > "$OUTPUT_DIR/ci-detail-$PR_ID.txt"
|
||||
|
||||
echo "[INFO] CI 状态: $CI_PASSING"
|
||||
echo "[INFO] $CI_DETAIL"
|
||||
|
||||
echo ""
|
||||
echo "[========================================]"
|
||||
echo "[ Phase 3 完成 ]"
|
||||
echo "[ CI 数据: $OUTPUT_DIR/ci-builds.json ]"
|
||||
echo "[ CI 状态: $CI_PASSING ]"
|
||||
echo "[========================================]"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
#!/bin/bash
|
||||
# ================================================================
|
||||
# Phase 3: 本地 CI 检查
|
||||
# 功能:在本地验证 PR 的代码能否编译通过
|
||||
# 使用:bash 03-local-ci-check.sh <owner> <repo> <pr_id>
|
||||
# 无需外部流水线,直接运行 go build 验证
|
||||
# ================================================================
|
||||
set -e
|
||||
|
||||
OWNER=${1:?"Usage: $0 <owner> <repo> <pr_id>"}
|
||||
REPO=${2}
|
||||
PR_ID=${3}
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/_output"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
echo "[========================================]"
|
||||
echo "[ Phase 3: 本地 CI 检查 ]"
|
||||
echo "[ PR #$PR_ID ($OWNER/$REPO) ]"
|
||||
echo "[========================================]"
|
||||
|
||||
CI_PASSING=false
|
||||
CI_DETAIL=""
|
||||
|
||||
# Step 1: 检查 Go 环境
|
||||
if ! command -v go &> /dev/null; then
|
||||
echo "[WARN] 未安装 Go,跳过本地编译检查"
|
||||
CI_DETAIL="未安装 Go,跳过"
|
||||
echo "unknown" > "$OUTPUT_DIR/ci-status-$PR_ID.txt"
|
||||
echo "$CI_DETAIL" > "$OUTPUT_DIR/ci-detail-$PR_ID.txt"
|
||||
echo "[INFO] CI 状态: unknown(Go 未安装)"
|
||||
echo ""
|
||||
echo "[========================================]"
|
||||
echo "[ Phase 3 完成 ]"
|
||||
echo "[========================================]"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Step 2: 本地编译验证
|
||||
echo "[INFO] 运行本地编译验证..."
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
|
||||
if [ -d "$PROJECT_ROOT" ] && [ -f "$PROJECT_ROOT/go.mod" ]; then
|
||||
BUILD_OUTPUT=$(cd "$PROJECT_ROOT" && go build -o /dev/null . 2>&1) || {
|
||||
echo "[WARN] ❌ 编译失败"
|
||||
echo "$BUILD_OUTPUT"
|
||||
CI_DETAIL="编译失败: $(echo "$BUILD_OUTPUT" | head -1)"
|
||||
echo "failing" > "$OUTPUT_DIR/ci-status-$PR_ID.txt"
|
||||
echo "$CI_DETAIL" > "$OUTPUT_DIR/ci-detail-$PR_ID.txt"
|
||||
echo "[INFO] CI 状态: failing"
|
||||
echo ""
|
||||
echo "[========================================]"
|
||||
echo "[ Phase 3 完成 ]"
|
||||
echo "[========================================]"
|
||||
exit 0
|
||||
}
|
||||
echo "[INFO] ✅ 编译成功"
|
||||
CI_PASSING=true
|
||||
CI_DETAIL="本地编译验证通过"
|
||||
echo "passing" > "$OUTPUT_DIR/ci-status-$PR_ID.txt"
|
||||
else
|
||||
echo "[WARN] 未找到 Go 项目,跳过本地编译检查"
|
||||
CI_DETAIL="未找到 Go 项目"
|
||||
echo "unknown" > "$OUTPUT_DIR/ci-status-$PR_ID.txt"
|
||||
fi
|
||||
|
||||
echo "$CI_DETAIL" > "$OUTPUT_DIR/ci-detail-$PR_ID.txt"
|
||||
echo "[INFO] CI 状态: $(cat "$OUTPUT_DIR/ci-status-$PR_ID.txt" 2>/dev/null || echo unknown)"
|
||||
echo ""
|
||||
echo "[========================================]"
|
||||
echo "[ Phase 3 完成 ]"
|
||||
echo "[========================================]"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
# ================================================================
|
||||
# Phase 4: 质量门禁决策
|
||||
# Phase 3: 质量门禁决策
|
||||
# 功能:计算质量评分 → 门禁判定 → 自动合并或反馈建议
|
||||
# 使用:bash 04-quality-gate-decision.sh <owner> <repo> <pr_id>
|
||||
# 环境变量:DRY_RUN=true|false (默认 true)
|
||||
|
|
@ -16,49 +16,52 @@ OUTPUT_DIR="$SCRIPT_DIR/_output"
|
|||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
echo "[========================================]"
|
||||
echo "[ Phase 4: 质量门禁决策 ]"
|
||||
echo "[ Phase 3: 质量门禁决策 ]"
|
||||
echo "[ PR #$PR_ID ($OWNER/$REPO) ]"
|
||||
echo "[ DRY_RUN: $DRY_RUN ]"
|
||||
echo "[========================================]"
|
||||
|
||||
# ---- 质量评分模型 ----
|
||||
# AI Agent 在此处读取 Phase 2 和 Phase 3 的输出数据,计算评分
|
||||
#
|
||||
# CodeReview_Score (0-100): 从 Phase 2 的审查结果获取
|
||||
# CI_Status (0 or 100): 从 Phase 3 的 CI 检查获取
|
||||
# Design_Score (0-100): AI 评估 PR 描述与代码变更的匹配度
|
||||
#
|
||||
# Quality_Score = CodeReview_Score × 0.4 + CI_Status × 0.3 + Design_Score × 0.3
|
||||
# Threshold = 70
|
||||
#
|
||||
# 判定:
|
||||
# - Quality_Score >= 70 且 CI 通过 → 通过门禁
|
||||
# - 否则 → 阻塞
|
||||
# 评分 = CodeReview_Score × 40% + CI_Status × 30% + Design_Score × 30%
|
||||
# CI 来自本地编译验证(03-local-ci-check.sh),无需外部流水线
|
||||
|
||||
echo "[INFO] 评分计算中..."
|
||||
echo " 评分模型: 总分 = CodeReview×40% + CI×30% + 设计一致性×30%"
|
||||
echo " 门槛: ≥ 70 分且 CI 通过 → 自动合并"
|
||||
echo " CI 来源: 本地编译验证(无需外部流水线)"
|
||||
echo " 门槛: ≥ 70 分且 CI 通过"
|
||||
echo ""
|
||||
|
||||
# 占位评分(AI 在实际运行时计算填充)
|
||||
# 读取本地 CI 检查状态
|
||||
CI_FILE="$OUTPUT_DIR/ci-status-$PR_ID.txt"
|
||||
CI_STATUS_TEXT="unknown"
|
||||
if [ -f "$CI_FILE" ]; then
|
||||
CI_STATUS_TEXT=$(cat "$CI_FILE")
|
||||
fi
|
||||
|
||||
# 占位评分(AI 在实际运行时根据 Phase 2 数据计算)
|
||||
CODE_REVIEW_SCORE=85
|
||||
CI_STATUS=100
|
||||
DESIGN_SCORE=80
|
||||
|
||||
# CI 状态映射
|
||||
case "$CI_STATUS_TEXT" in
|
||||
passing) CI_STATUS=100; CI_PASSING=true; CI_LABEL="✅ 本地编译通过" ;;
|
||||
failing) CI_STATUS=0; CI_PASSING=false; CI_LABEL="❌ 编译失败" ;;
|
||||
*) CI_STATUS=0; CI_PASSING=true; CI_LABEL="⏭️ 跳过(无法编译)" ;;
|
||||
esac
|
||||
|
||||
QUALITY_SCORE=$(( CODE_REVIEW_SCORE * 40 / 100 + CI_STATUS * 30 / 100 + DESIGN_SCORE * 30 / 100 ))
|
||||
CI_PASSING=true
|
||||
THRESHOLD=70
|
||||
|
||||
echo " CodeReview 评分: $CODE_REVIEW_SCORE/100"
|
||||
echo " CI 状态: $CI_STATUS/100 ($([ "$CI_PASSING" = "true" ] && echo "✅ 通过" || echo "❌ 失败"))"
|
||||
echo " CI 状态: $CI_STATUS_TEXT ($CI_LABEL)"
|
||||
echo " 设计一致性: $DESIGN_SCORE/100"
|
||||
echo " 质量总分: $QUALITY_SCORE/100"
|
||||
echo ""
|
||||
|
||||
# ---- 门禁判定 ----
|
||||
THRESHOLD=70
|
||||
|
||||
if [ "$QUALITY_SCORE" -ge "$THRESHOLD" ] && [ "$CI_PASSING" = "true" ]; then
|
||||
echo "✅ 门禁判定:通过"
|
||||
echo " 评分 $QUALITY_SCORE ≥ $THRESHOLD,CI 通过"
|
||||
echo " 评分 $QUALITY_SCORE ≥ $THRESHOLD"
|
||||
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo ""
|
||||
|
|
@ -78,13 +81,7 @@ if [ "$QUALITY_SCORE" -ge "$THRESHOLD" ] && [ "$CI_PASSING" = "true" ]; then
|
|||
fi
|
||||
else
|
||||
echo "❌ 门禁判定:阻塞"
|
||||
|
||||
if [ "$QUALITY_SCORE" -lt "$THRESHOLD" ]; then
|
||||
echo " 评分 $QUALITY_SCORE < $THRESHOLD(未达门槛)"
|
||||
fi
|
||||
if [ "$CI_PASSING" != "true" ]; then
|
||||
echo " CI 状态:未通过"
|
||||
fi
|
||||
echo " 评分 $QUALITY_SCORE < $THRESHOLD(未达门槛)"
|
||||
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo ""
|
||||
|
|
@ -95,18 +92,13 @@ else
|
|||
else
|
||||
echo "[INFO] 提交修改建议..."
|
||||
gitlink-cli pr +review --id "$PR_ID" --owner "$OWNER" --repo "$REPO" \
|
||||
--status common --content "❌ 质量门禁未通过
|
||||
|
||||
评分:$QUALITY_SCORE/100(要求 ≥ $THRESHOLD)
|
||||
CI:$([ "$CI_PASSING" = "true" ] && echo "✅" || echo "❌")
|
||||
|
||||
请修复以上问题后重新提交。"
|
||||
--status common --content "❌ 质量门禁未通过,评分:$QUALITY_SCORE/100\n请修复后重新提交。"
|
||||
echo "[INFO] 修改建议已提交"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[========================================]"
|
||||
echo "[ Phase 4 完成 ]"
|
||||
echo "[ 决策结果: $([ "$QUALITY_SCORE" -ge "$THRESHOLD" ] && [ "$CI_PASSING" = "true" ] && echo "通过" || echo "阻塞") ]"
|
||||
echo "[ Phase 3 完成 ]"
|
||||
echo "[ 决策结果: $([ "$QUALITY_SCORE" -ge "$THRESHOLD" ] && echo "通过" || echo "阻塞") ]"
|
||||
echo "[========================================]"
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ echo "▸ Phase 1/4: PR 采集"
|
|||
bash "$SCRIPT_DIR/01-collect-prs.sh" "$OWNER" "$REPO"
|
||||
echo ""
|
||||
|
||||
# 读取待审 PR 列表
|
||||
PR_LIST="$OUTPUT_DIR/prs-to-review.json"
|
||||
if [ ! -f "$PR_LIST" ]; then
|
||||
# 读取待审 PR 列表(用相对路径避免 MSYS 路径问题)
|
||||
PR_REL="skills/gitlink-quality-gate/scripts/_output/prs-to-review.json"
|
||||
if [ ! -f "$PR_REL" ]; then
|
||||
echo "[ERROR] 未找到 PR 列表,请先运行 Phase 1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PR_COUNT=$(node -e "const d=require('fs').readFileSync('$PR_LIST','utf8');process.stdout.write(String(JSON.parse(d).length))" 2>/dev/null || echo "0")
|
||||
PR_COUNT=$(node -e "const d=require('fs').readFileSync('$PR_REL','utf8');process.stdout.write(String(JSON.parse(d).length))" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$PR_COUNT" -eq 0 ]; then
|
||||
echo "[INFO] 没有待审 PR,流程结束"
|
||||
|
|
@ -45,7 +45,7 @@ echo "[INFO] 共 $PR_COUNT 个 PR 需要审查"
|
|||
|
||||
# 遍历每个 PR
|
||||
for i in $(seq 0 $((PR_COUNT - 1))); do
|
||||
PR_ID=$(node -e "const d=require('fs').readFileSync('$PR_LIST','utf8');const items=JSON.parse(d);process.stdout.write(String(items[$i].id))" 2>/dev/null || echo "")
|
||||
PR_ID=$(node -e "const d=require('fs').readFileSync('$PR_REL','utf8');const items=JSON.parse(d);process.stdout.write(String(items[$i].id))" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$PR_ID" ]; then
|
||||
echo "[WARN] PR #$i: 无法解析 PR ID,跳过"
|
||||
|
|
@ -61,9 +61,9 @@ for i in $(seq 0 $((PR_COUNT - 1))); do
|
|||
bash "$SCRIPT_DIR/02-deep-review.sh" "$OWNER" "$REPO" "$PR_ID"
|
||||
echo ""
|
||||
|
||||
# Phase 3: CI 检查
|
||||
echo "▸ Phase 3/4: CI 检查"
|
||||
bash "$SCRIPT_DIR/03-ci-check.sh" "$OWNER" "$REPO" "$PR_ID"
|
||||
# Phase 3: 本地 CI 检查(编译验证)
|
||||
echo "▸ Phase 3/4: 本地 CI 检查"
|
||||
bash "$SCRIPT_DIR/03-local-ci-check.sh" "$OWNER" "$REPO" "$PR_ID"
|
||||
echo ""
|
||||
|
||||
# Phase 4: 门禁决策
|
||||
|
|
|
|||
|
|
@ -56,8 +56,17 @@ allResults.forEach(r => {
|
|||
// 按 Star 排序取前 10 个项目
|
||||
const topProjects = projects.sort((a, b) => b.stars - a.stars).slice(0, 10);
|
||||
|
||||
// 热门主题 TOP 8
|
||||
// 排除编程语言后的热门主题 TOP 8
|
||||
const LANG_FILTER = new Set([
|
||||
'python','c','c++','c#','csharp','javascript','typescript','go','golang',
|
||||
'rust','java','ruby','php','shell','bash','makefile','cmake','jupyter notebook',
|
||||
'kotlin','swift','scala','haskell','lua','perl','r','dart','elixir','clojure',
|
||||
'erlang','objective-c','assembly','html','css','vue','react','nodejs','node',
|
||||
'dockerfile','postscript','coldfusion','tex','latex','batchfile','viml','vimscript',
|
||||
'roff','pascal','fortran','cobol','ada','prolog','lisp','smalltalk','delphi'
|
||||
]);
|
||||
const topTopics = Object.entries(topicMap)
|
||||
.filter(([t]) => !LANG_FILTER.has(t.toLowerCase()))
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 8)
|
||||
.map(([t, c], i) => ({ rank: i + 1, topic: t, count: c }));
|
||||
|
|
|
|||
|
|
@ -17,26 +17,28 @@ const prs = readJSON('prs-all.json').data || {};
|
|||
const langs = readJSON('repo-languages.json').data || {};
|
||||
const contribs = readJSON('repo-contributors.json').data || {};
|
||||
|
||||
// ==== 数据提取(兼容实际字段名)====
|
||||
// ==== 数据提取 ====
|
||||
const name = repo.name || 'unknown';
|
||||
const fullName = repo.full_name || 'unknown';
|
||||
const isMirror = !!repo.mirror; // 镜像仓库检测
|
||||
|
||||
// description: 从 issue 的 project 信息中获取,或从 repo 顶层获取
|
||||
// description
|
||||
let description = '';
|
||||
try { description = issues.issues[0].project.description || ''; } catch(e) {}
|
||||
if (!description) description = repo.description || repo.about || '';
|
||||
// 从 repo 的 fork_info 获取描述
|
||||
if (!description && repo.fork_info) {
|
||||
try { description = issues.issues[0].project.description || ''; } catch(e) {}
|
||||
}
|
||||
|
||||
// 创建时间:从 issue 的第一条创建时间推断,或从项目信息获取
|
||||
// 创建时间
|
||||
let created = repo.created_at || repo.created_on || '';
|
||||
if (!created && issues.issues && issues.issues.length > 0) {
|
||||
created = issues.issues[issues.issues.length - 1].created_at || '';
|
||||
}
|
||||
// 支持 "2026-06-04 15:35" 格式的日期
|
||||
if (created) created = created.replace(' ', 'T');
|
||||
|
||||
const updated = repo.updated_at || repo.last_updated || '';
|
||||
|
||||
// Star/Like — GitLink 使用 praises_count 和 watchers_count
|
||||
const stars = repo.praises_count || repo.watchers_count || 0;
|
||||
const forks = repo.forked_count || 0;
|
||||
const defaultBranch = repo.default_branch || 'master';
|
||||
|
|
@ -45,7 +47,6 @@ const contributorCount = repo.contributor_users_count || 0;
|
|||
|
||||
const langEntries = Object.entries(langs).sort((a,b) => parseFloat(b[1]) - parseFloat(a[1]));
|
||||
const topLangs = langEntries.slice(0, 3).map(e => e[0] + ' ' + e[1]).join(', ');
|
||||
|
||||
const topics = (repo.topics || []).join(', ') || '无标签';
|
||||
|
||||
// 项目年龄(月)
|
||||
|
|
@ -64,108 +65,138 @@ const closedCount = issues.closed_count || 0;
|
|||
const openedCount = issues.opened_count || 0;
|
||||
const totalIssues = closedCount + openedCount || issueList.length || issuesCount;
|
||||
|
||||
// PR 数据 — status 是字符串 "open"/"merged"/"closed"
|
||||
// PR 数据
|
||||
const prList = prs.pulls || [];
|
||||
let prOpen = 0, prMerged = 0;
|
||||
let prOpen = 0, prMerged = 0, prClosed = 0;
|
||||
prList.forEach(p => {
|
||||
const s = (p.status || '').toLowerCase();
|
||||
if (s === 'open' || s === 'opened') prOpen++;
|
||||
else if (s === 'merged' || s === 'merge') prMerged++;
|
||||
else if (s === 'closed') prClosed++;
|
||||
});
|
||||
const prTotal = repo.pull_requests_count || prList.length || 1;
|
||||
const prTotal = repo.pull_requests_count || prList.length || 0;
|
||||
|
||||
// 贡献者
|
||||
const contribList = contribs.list || [];
|
||||
const uniqueContributors = contribList.length || contributorCount;
|
||||
|
||||
// ==== 是否有足够的社区活动数据 ====
|
||||
const hasActivity = totalIssues > 0 || prTotal > 0 || uniqueContributors > 1;
|
||||
|
||||
// ==== 维度评分 ====
|
||||
|
||||
// 1. 成熟度评分 (0-10)
|
||||
const ageScore = Math.min(10, Math.max(0, ageMonths / 2));
|
||||
// 修复:创建时间不可用时给中等分
|
||||
const ageScore = ageMonths > 0 ? Math.min(10, ageMonths / 2) : 5;
|
||||
const starScore = stars > 100 ? 10 : stars > 50 ? 7 : stars > 10 ? 5 : Math.max(1, stars);
|
||||
const forkScore = forks > 50 ? 10 : forks > 20 ? 7 : forks > 5 ? 5 : Math.max(1, forks);
|
||||
const updateScore = 7;
|
||||
const updateScore = ageMonths > 0 ? 7 : 5;
|
||||
const maturityScore = Math.round((ageScore * 0.25 + starScore * 0.25 + forkScore * 0.20 + updateScore * 0.30) * 10) / 10;
|
||||
|
||||
// 2. 文档质量评分 (0-10)
|
||||
let docScore = 3;
|
||||
if (description && description.length > 10) docScore += 2;
|
||||
if (description && description.length > 5) docScore += 2;
|
||||
if (repo.license_short_name || repo.license_name) docScore += 2;
|
||||
if (repo.open_devops) docScore += 1;
|
||||
if (repo.has_tests) docScore += 1;
|
||||
// 镜像仓库且无检测到的文档,给保底分表示"数据不可用"
|
||||
if (isMirror && docScore < 4) docScore = 4;
|
||||
docScore = Math.min(10, docScore);
|
||||
|
||||
// 3. 贡献模式评分 (0-10)
|
||||
const contribScore = Math.min(10, Math.max(1, uniqueContributors * 2));
|
||||
const freqScore = 6;
|
||||
const contribModeScore = Math.round((contribScore * 0.30 + freqScore * 0.30 + 5 * 0.20 + 5 * 0.20) * 10) / 10;
|
||||
const freqScore = hasActivity ? 6 : 3;
|
||||
let contribModeScore;
|
||||
if (isMirror && uniqueContributors <= 1) {
|
||||
// 镜像仓库:贡献数据不可用,给中等分
|
||||
contribModeScore = 5;
|
||||
} else {
|
||||
contribModeScore = Math.round((contribScore * 0.30 + freqScore * 0.30 + 5 * 0.20 + 5 * 0.20) * 10) / 10;
|
||||
}
|
||||
|
||||
// 4. 社区健康度评分 (0-10)
|
||||
const closeRate = totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) : 0;
|
||||
const closeRateScore = closeRate > 80 ? 10 : closeRate > 50 ? 7 : closeRate > 30 ? 5 : 3;
|
||||
const mergeRate = prTotal > 0 ? Math.round(prMerged / prTotal * 100) : 0;
|
||||
const mergeRateScore = mergeRate > 70 ? 10 : mergeRate > 40 ? 7 : 3;
|
||||
const communityScore = Math.round((closeRateScore * 0.30 + 6 * 0.25 + mergeRateScore * 0.25 + 6 * 0.20) * 10) / 10;
|
||||
let closeRate = 0, mergeRate = 0;
|
||||
let closeRateScore, mergeRateScore, communityScore;
|
||||
if (isMirror && totalIssues === 0 && prTotal === 0) {
|
||||
closeRateScore = 5; mergeRateScore = 5; communityScore = 5;
|
||||
} else {
|
||||
closeRate = totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) : 0;
|
||||
closeRateScore = closeRate > 80 ? 10 : closeRate > 50 ? 7 : closeRate > 30 ? 5 : totalIssues > 0 ? 3 : 5;
|
||||
mergeRate = prTotal > 0 ? Math.round(prMerged / prTotal * 100) : 0;
|
||||
mergeRateScore = mergeRate > 70 ? 10 : mergeRate > 40 ? 7 : prTotal > 0 ? 3 : 5;
|
||||
communityScore = Math.round((closeRateScore * 0.30 + 6 * 0.25 + mergeRateScore * 0.25 + 6 * 0.20) * 10) / 10;
|
||||
}
|
||||
|
||||
// 5. 综合评分
|
||||
const totalScore = Math.round((maturityScore * 0.25 + docScore * 0.20 + contribModeScore * 0.30 + communityScore * 0.25) * 10) / 10;
|
||||
// 镜像仓库:调整权重
|
||||
let totalScore;
|
||||
if (isMirror && !hasActivity) {
|
||||
// 镜像仓库:成熟度和文档权重提高
|
||||
totalScore = Math.round((maturityScore * 0.40 + docScore * 0.30 + contribModeScore * 0.15 + communityScore * 0.15) * 10) / 10;
|
||||
} else {
|
||||
totalScore = Math.round((maturityScore * 0.25 + docScore * 0.20 + contribModeScore * 0.30 + communityScore * 0.25) * 10) / 10;
|
||||
}
|
||||
|
||||
const level = totalScore >= 9 ? '🟢 优秀' : totalScore >= 7 ? '🟢 良好' : totalScore >= 5 ? '🟡 一般' : totalScore >= 3 ? '🔴 堪忧' : '🔴 危险';
|
||||
|
||||
// 可复现性
|
||||
const hasLicense = repo.license_short_name ? '✅' : '❌';
|
||||
const hasReadme = (description && description.length > 0) ? '✅' : '❌';
|
||||
const hasDevops = repo.open_devops ? '✅' : '❌';
|
||||
// 可读的状态显示
|
||||
const totalIssuesStr = totalIssues > 0 ? `${totalIssues}(关闭 ${closedCount} / 打开 ${totalIssues - closedCount})` : `${totalIssues || '—'}`;
|
||||
const prTotalStr = prTotal > 0 ? `${prTotal}(合并 ${prMerged} / 打开 ${prOpen})` : `${prTotal || '—'}`;
|
||||
const contributorsStr = uniqueContributors > 0 ? `${uniqueContributors} 人` : '—';
|
||||
|
||||
// 改进建议
|
||||
const suggestions = [];
|
||||
if (stars < 20) suggestions.push('🟡 中优先级 — 项目影响力有限,建议在社区中推广');
|
||||
if (!repo.license_short_name) suggestions.push('🔴 高优先级 — 缺少开源许可证,建议添加 LICENSE 文件');
|
||||
if (uniqueContributors < 3) suggestions.push('🔴 高优先级 — 贡献者较少,建议标记 good-first-issue 吸引新贡献者');
|
||||
if (!repo.license_short_name && !isMirror) suggestions.push('🔴 高优先级 — 缺少开源许可证,建议添加 LICENSE 文件');
|
||||
if (uniqueContributors < 3 && !isMirror) suggestions.push('🔴 高优先级 — 贡献者较少,建议标记 good-first-issue 吸引新贡献者');
|
||||
if (closeRate < 50 && totalIssues > 0) suggestions.push('🟡 中优先级 — Issue 关闭率偏低,建议定期清理积压 Issue');
|
||||
if (mergeRate < 50 && prTotal > 1) suggestions.push('🟡 中优先级 — PR 合并率偏低,建议加快 PR 处理速度');
|
||||
if (isMirror && !hasActivity) suggestions.push('ℹ️ 该项目为镜像仓库,部分社区活动数据不可用,评分仅供参考');
|
||||
if (suggestions.length === 0) suggestions.push('🟢 低优先级 — 项目状态良好,继续保持');
|
||||
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().slice(0, 10);
|
||||
const timeStr = now.toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
const ageDisplay = ageMonths > 0 ? `约 ${Math.round(ageMonths)} 个月` : '未知';
|
||||
const mirrorNote = isMirror ? '\n> ℹ️ 该项目为镜像仓库,部分数据(贡献者/Issue/PR)可能不完整\n' : '';
|
||||
|
||||
// ==== 生成报告 ====
|
||||
const report = `# 🔬 科研项目洞察报告 — ${fullName}
|
||||
|
||||
**分析日期:** ${dateStr}
|
||||
**综合评分:** ${totalScore}/10 ${level}
|
||||
|
||||
**综合评分:** ${totalScore}/10 ${level}${isMirror ? ' 📦 镜像仓库' : ''}
|
||||
${mirrorNote}
|
||||
## 一、项目画像
|
||||
| 维度 | 数据 |
|
||||
|------|------|
|
||||
| 项目名称 | ${name} |
|
||||
| 描述 | ${description || '无描述'} |
|
||||
| 创建时间 | ${created || '未知'}(约 ${Math.round(ageMonths)} 个月) |
|
||||
| 创建时间 | ${created || '未知'}(${ageDisplay}) |
|
||||
| Star / Fork | ${stars} / ${forks} |
|
||||
| 默认分支 | ${defaultBranch} |
|
||||
| 主要语言 | ${topLangs || '未知'} |
|
||||
| 研究主题 | ${topics} |
|
||||
| 贡献者数 | ${uniqueContributors} 人 |
|
||||
| 贡献者数 | ${contributorsStr} |
|
||||
| Issue 总数 | ${totalIssues} |
|
||||
|
||||
## 二、项目成熟度(${maturityScore}/10)
|
||||
|
||||
**评分因子:**
|
||||
- 项目年龄:约 ${Math.round(ageMonths)} 个月 → ${ageScore.toFixed(1)} 分(权重 25%)
|
||||
- 项目年龄:${ageDisplay} → ${ageScore.toFixed(1)} 分(权重 25%)
|
||||
- 社区影响力:${stars} Star / ${forks} Fork → ${Math.round(starScore)} 分(权重 25%)
|
||||
- Fork 活跃度:${forks} Fork → ${Math.round(forkScore)} 分(权重 20%)
|
||||
- 维护活跃度:最近更新 ${updated || '未知'} → ${updateScore} 分(权重 30%)
|
||||
|
||||
**分析:** 项目创建约 ${Math.round(ageMonths)} 个月,获得 ${stars} 个 Star 和 ${forks} 个 Fork。${stars > 50 ? '项目已有一定社区影响力,处于成长期。' : '项目尚在早期阶段,需加强社区推广。'}
|
||||
**分析:** ${stars > 50 ? '项目已有一定社区影响力,处于成长期。' : '项目尚在早期阶段,需加强社区推广。'}
|
||||
|
||||
## 三、文档质量(${docScore}/10)
|
||||
|
||||
| 检查项 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| README | ${hasReadme} | ${description ? '项目描述已填写' : '建议完善 README'} |
|
||||
| 开源许可证 | ${hasLicense} | ${repo.license_short_name || '未检测到,建议添加'} |
|
||||
| DevOps CI | ${hasDevops} | ${repo.open_devops ? '已配置' : '建议配置 CI 流水线'} |
|
||||
| README | ${description && description.length > 5 ? '✅' : '❌'} | ${description ? '项目描述已填写' : '建议完善 README'} |
|
||||
| 开源许可证 | ${repo.license_short_name ? '✅' : '❌'} | ${repo.license_short_name || '未检测到,建议添加'} |
|
||||
| DevOps CI | ${repo.open_devops ? '✅' : '❌'} | ${repo.open_devops ? '已配置' : '建议配置 CI 流水线'} |
|
||||
|
||||
**改进建议:** ${docScore < 6 ? '建议补充 README、LICENSE 和 CI 配置以提升项目规范性' : '文档基本完善'}
|
||||
|
||||
|
|
@ -173,29 +204,29 @@ const report = `# 🔬 科研项目洞察报告 — ${fullName}
|
|||
|
||||
| 指标 | 数据 | 评分 |
|
||||
|------|------|------|
|
||||
| 贡献者数量 | ${uniqueContributors} 人 | ${contribScore}/10 |
|
||||
| 提交活跃度 | 中等 | ${freqScore}/10 |
|
||||
| 贡献者数量 | ${contributorsStr} | ${isMirror && uniqueContributors <= 1 ? '—(镜像仓库)' : contribScore + '/10'} |
|
||||
| 提交活跃度 | ${hasActivity ? '中等' : '—(镜像仓库)'} | ${isMirror && !hasActivity ? '—' : freqScore + '/10'} |
|
||||
|
||||
**分析:** 项目有 ${uniqueContributors} 位贡献者。${uniqueContributors < 3 ? '目前贡献者偏少,建议通过 good-first-issue 标签吸引新贡献者。' : '团队协作良好,有多位贡献者共同维护。'}
|
||||
**分析:** ${isMirror && uniqueContributors <= 1 ? '该项目为镜像仓库,贡献者数据不代表实际开发团队。' : uniqueContributors < 3 ? '目前贡献者偏少,建议通过 good-first-issue 标签吸引新贡献者。' : '团队协作良好,有多位贡献者共同维护。'}
|
||||
|
||||
## 五、社区健康度(${communityScore}/10)
|
||||
|
||||
| 指标 | 数据 | 评分 |
|
||||
|------|------|------|
|
||||
| Issue 总数 | ${totalIssues}(关闭 ${closedCount} / 打开 ${totalIssues - closedCount})| — |
|
||||
| 关闭率 | ${closeRate}% | ${closeRateScore}/10 |
|
||||
| PR 总数 | ${prTotal}(合并 ${prMerged} / 打开 ${prOpen})| — |
|
||||
| 合并率 | ${mergeRate}% | ${mergeRateScore}/10 |
|
||||
| Issue 总数 | ${totalIssuesStr} | — |
|
||||
| 关闭率 | ${totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) + '%' : '—'} | ${closeRateScore}/10 |
|
||||
| PR 总数 | ${prTotalStr} | — |
|
||||
| 合并率 | ${prTotal > 0 ? Math.round(prMerged / prTotal * 100) + '%' : '—'} | ${mergeRateScore}/10 |
|
||||
|
||||
**分析:** Issue 关闭率 ${closeRate}%${closeRate > 50 ? ' ✅ 良好' : ' ⚠️ 偏低'};PR 合并率 ${mergeRate}%${mergeRate > 50 ? ' ✅ 良好' : ' ⚠️ 偏低'}。
|
||||
**分析:** ${isMirror && totalIssues === 0 && prTotal === 0 ? '该项目为镜像仓库,没有独立 Issue 和 PR 数据。' : (closeRate > 50 ? 'Issue 关闭率 ✅ 良好' : 'Issue 关闭率 ⚠️ 偏低') + ';' + (mergeRate > 50 ? 'PR 合并率 ✅ 良好' : 'PR 合并率 ⚠️ 偏低')}
|
||||
|
||||
## 六、可复现性评估
|
||||
| 检查项 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| 开源许可证 | ${hasLicense} | ${repo.license_short_name || '建议添加以便他人使用'} |
|
||||
| 依赖文档化 | — | 需人工检查 go.mod / package.json |
|
||||
| 运行说明 | ${hasReadme} | ${description ? 'README 包含基本说明' : 'README 缺少运行说明'} |
|
||||
| CI 配置 | ${hasDevops} | ${repo.open_devops ? '已有 DevOps 流水线' : '建议配置 CI'} |
|
||||
| 开源许可证 | ${repo.license_short_name ? '✅' : '❌'} | ${repo.license_short_name || '建议添加以便他人使用'} |
|
||||
| 依赖文档化 | ${hasActivity ? '—' : '⏭️'} | ${hasActivity ? '需人工检查' : '镜像仓库,跳过'} |
|
||||
| 运行说明 | ${description && description.length > 5 ? '✅' : '❌'} | ${description ? 'README 包含基本说明' : 'README 缺少运行说明'} |
|
||||
| CI 配置 | ${repo.open_devops ? '✅' : '❌'} | ${repo.open_devops ? '已有 DevOps 流水线' : '建议配置 CI'} |
|
||||
|
||||
## 七、改进建议
|
||||
${suggestions.map((s, i) => (i + 1) + '. ' + s).join('\n')}
|
||||
|
|
|
|||
|
|
@ -20,17 +20,17 @@ echo "============================================"
|
|||
echo ""
|
||||
|
||||
# Step 1: 数据采集
|
||||
echo "▸ Step 1/2: 数据采集"
|
||||
echo "▸ Step 1/3: 数据采集"
|
||||
bash "$SCRIPT_DIR/01-collect-repo-data.sh" "$OWNER" "$REPO"
|
||||
echo ""
|
||||
|
||||
# Step 2: 分析并生成报告
|
||||
echo "▸ Step 2/2: 分析生成报告"
|
||||
echo "▸ Step 2/3: 分析生成报告"
|
||||
bash "$SCRIPT_DIR/02-analyze-contributions.sh" "$OWNER" "$REPO"
|
||||
echo ""
|
||||
|
||||
# Step 3: 生成 HTML 可视化
|
||||
echo "▸ Step 3/2: 生成 HTML 可视化报告..."
|
||||
echo "▸ Step 3/3: 生成 HTML 可视化报告..."
|
||||
node "$SCRIPT_DIR/../../gitlink-shared/scripts/md-to-html.js" "$OUTPUT_DIR/research-insight-report.md"
|
||||
echo ""
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue