create-mr.sh
4.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env bash
# create-mr.sh — mr-create 主流程:渲染 description、查已有 MR / 创建新 MR
#
# 用法:
# bash create-mr.sh <module_id> <current_branch> <date>
#
# 输出(stdout,单行,由调用方读取):
# <MR_IID> <MR_URL>
#
# 失败:诊断写 stderr,exit 1。
#
# 设计要点:
# - 模块报告整文只经 sed + awk 管道流入 description 与 GitLab API(curl --rawfile),
# 全程不进 LLM 上下文。
# - 幂等:同一 source_branch 已有 opened MR 时,复用其 iid/url,不再创建。
set -euo pipefail
MODULE_ID="${1:?usage: create-mr.sh <module_id> <current_branch> <date>}"
CURRENT_BRANCH="${2:?missing current_branch}"
DATE="${3:?missing date (YYYY-MM-DD)}"
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
TPL_DIR="$SCRIPT_DIR/../templates"
DESC_TPL="$TPL_DIR/mr-description-template.md"
TITLE_TPL="$TPL_DIR/mr-title-template.md"
REPORT="docs/superpowers/module-reports/${DATE}-${MODULE_ID}.md"
TEST_GATE="docs/superpowers/module-reports/${MODULE_ID}-test-gate.md"
[ -f "$REPORT" ] || { echo "[create-mr] ⚠️ 模块报告不存在: $REPORT" >&2; exit 1; }
[ -f "$TEST_GATE" ] || { echo "[create-mr] ⚠️ test-gate evidence 不存在: $TEST_GATE" >&2; exit 1; }
# 1. 加载凭据
[ -f .env.local ] || { echo "[create-mr] ⚠️ .env.local 不存在" >&2; exit 1; }
set -a; . ./.env.local; set +a
for v in GITLAB_API_URL GITLAB_TOKEN GITLAB_PROJECT_ID; do
eval "val=\${$v:-}"
[ -n "$val" ] || { echo "[create-mr] ⚠️ .env.local 缺少 $v" >&2; exit 1; }
done
# 2. 探测目标分支(origin/HEAD → main → master)
TARGET_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||' || true)
[ -n "$TARGET_BRANCH" ] || TARGET_BRANCH=$(git branch -r --format='%(refname:short)' | grep -E '^origin/(main|master)$' | head -1 | sed 's|^origin/||' || true)
[ -n "$TARGET_BRANCH" ] || { echo "[create-mr] ⚠️ 无法探测默认分支(origin/main 或 origin/master)" >&2; exit 1; }
# 3. 从 docs/08 § 二 提取 module_name
MODULE_NAME=$(awk -v mid="$MODULE_ID" '
$0 ~ "^- "mid" " { sub("^- "mid" ", ""); print; exit }
' docs/08-模块任务管理.md)
[ -n "$MODULE_NAME" ] || { echo "[create-mr] ⚠️ docs/08 § 二 找不到模块 $MODULE_ID" >&2; exit 1; }
# 4. 从 test-gate evidence 提取 conclusion + subagent_id
TEST_SUBAGENT_ID=$(awk '/^- 子会话: / { sub("^- 子会话: ", ""); print; exit }' "$TEST_GATE")
TEST_GATE_CONCLUSION=$(awk '/^结论: / { sub("^结论: ", ""); print; exit }' "$TEST_GATE" | awk '{print $1}')
# 5. 渲染 MR 标题(单行,可进 LLM 上下文)
TITLE=$(cat "$TITLE_TPL")
TITLE="${TITLE//\{\{module_id\}\}/$MODULE_ID}"
TITLE="${TITLE//\{\{module_name\}\}/$MODULE_NAME}"
# 6. 渲染 description(整篇模块报告,全程不进 LLM 上下文)
mkdir -p .tmp
DESC_FILE=.tmp/mr-desc.md
sed -e "s|{{test_gate_conclusion}}|$TEST_GATE_CONCLUSION|g" \
-e "s|{{test_subagent_id}}|$TEST_SUBAGENT_ID|g" \
-e "s|{{module_id}}|$MODULE_ID|g" \
-e "s|{{date}}|$DATE|g" \
"$DESC_TPL" > "$DESC_FILE"
awk -v report="$REPORT" '
/\{\{module_report_contents\}\}/ { while ((getline line < report) > 0) print line; close(report); next }
{ print }
' "$DESC_FILE" > .tmp/mr-desc.final
mv .tmp/mr-desc.final "$DESC_FILE"
# 7. 幂等守门:查已有 opened MR
HTTP_CODE=$(curl -sS -o .tmp/existing.json -w '%{http_code}' \
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
"${GITLAB_API_URL}/projects/${GITLAB_PROJECT_ID}/merge_requests?source_branch=${CURRENT_BRANCH}&state=opened")
if [ "$HTTP_CODE" != "200" ]; then
echo "[create-mr] ⚠️ 查询已有 MR 失败 (HTTP $HTTP_CODE)" >&2
jq -r '.message // .error // .' .tmp/existing.json | head -c 200 >&2
echo >&2
rm -f .tmp/existing.json "$DESC_FILE"
exit 1
fi
EXISTING_IID=$(jq -r '.[0].iid // empty' .tmp/existing.json)
EXISTING_URL=$(jq -r '.[0].web_url // empty' .tmp/existing.json)
rm -f .tmp/existing.json
if [ -n "$EXISTING_IID" ]; then
echo "[create-mr] 复用已有 opened MR: !$EXISTING_IID" >&2
rm -f "$DESC_FILE"
echo "$EXISTING_IID $EXISTING_URL"
exit 0
fi
# 8. 创建新 MR(--rawfile desc 把 description 文件流入 jq → curl,不进 LLM 上下文)
CREATE_RESP=$(curl -sS -X POST \
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
--header "Content-Type: application/json" \
--data "$(jq -n \
--arg src "$CURRENT_BRANCH" \
--arg tgt "$TARGET_BRANCH" \
--arg title "$TITLE" \
--rawfile desc "$DESC_FILE" \
'{source_branch: $src, target_branch: $tgt, title: $title, description: $desc, remove_source_branch: false}')" \
"${GITLAB_API_URL}/projects/${GITLAB_PROJECT_ID}/merge_requests")
MR_IID=$(echo "$CREATE_RESP" | jq -r '.iid // empty')
MR_URL=$(echo "$CREATE_RESP" | jq -r '.web_url // empty')
if [ -z "$MR_IID" ]; then
echo "[create-mr] ⚠️ MR 创建失败:" >&2
echo "$CREATE_RESP" | jq . >&2
rm -f "$DESC_FILE"
exit 1
fi
rm -f "$DESC_FILE"
echo "$MR_IID $MR_URL"