create-mr.sh
5.59 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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/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
CURL_META=$(curl -sS -o .tmp/existing.json -w '%{http_code}|%{url_effective}' \
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
"${GITLAB_API_URL}/projects/${GITLAB_PROJECT_ID}/merge_requests?source_branch=${CURRENT_BRANCH}&state=opened")
HTTP_CODE=${CURL_META%%|*}
EFFECTIVE_URL=${CURL_META#*|}
if [ "$HTTP_CODE" != "200" ]; then
echo "[create-mr] ⚠️ 查询已有 MR 失败 (HTTP $HTTP_CODE)" >&2
echo " effective URL: $EFFECTIVE_URL" >&2
FIRST=$(head -c 1 .tmp/existing.json 2>/dev/null || echo "")
case "$FIRST" in
'{'|'[')
echo " 响应(JSON):" >&2
jq -r '.message // .error // .' .tmp/existing.json 2>/dev/null | head -c 200 >&2
echo >&2
;;
'<')
echo " 响应是 HTML(很可能反代/路由失配,把 API 请求送进了 web 处理链):" >&2
head -c 200 .tmp/existing.json >&2
echo >&2
echo " 常见原因:GITLAB_API_URL 错 / GITLAB_PROJECT_ID 不是数字 ID / 反代规范化路径" >&2
;;
*)
echo " 响应非 JSON 非 HTML(前 200 bytes):" >&2
head -c 200 .tmp/existing.json >&2
echo >&2
;;
esac
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"