Files
sdlc/tools/stage-report.sh
T

282 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env sh
# stage-report.sh — SDLC 階段收尾回報(規劃、分析、實作、維護共用)。
#
# 為什麼要有這支腳本:階段結束要回報的東西是固定的——模型閘門判定、工作日誌連結、所有寫入的
# wiki 連結,實作階段再加上工作目錄與三條分支。寫在技能內文裡靠模型自己記,少一項看不出來;
# 所以彙整搬到程式層:頁名換成絕對網址、commit 數與推送狀態由 git 現查、沒有工作日誌就當場
# 警告並把內容存進暫存區,全部由本檔產出,技能只負責把事實餵進來。
#
# 用法:
# stage-report.sh <stage> [選項]
# <stage> = plan | analyze | implement | maintain
#
# 共用選項:
# --page TYPE:PAGE 本階段寫入的 wiki 頁,可重複(例:--page ANALYZE:ANALYZE_D3F1A2B0)
# --worklog PAGE 已寫入的工作日誌頁名(例:LOG_D3F1A2B0)
# --worklog-heading TEXT 日誌條目標題,用來組出導向該條目的錨點連結
# --pending-file FILE 沒有工作日誌時,要暫存起來的日誌內容檔
# --log-hash HASH 暫存歸屬的工作日誌 hash(即 LOG_{HASH} 的 HASH)
#
# 實作階段選項:
# --worktree PATH 工作目錄(worktree 路徑)
# --source-branch NAME 來源分支(本檔自行判定遠端有沒有這一條)
# --work-branch NAME 工作分支(本檔自行算 commit 數與推送狀態)
# --pr URL 目標分支的 PR 連結
# --target-branch NAME 目標分支(省略時等同來源分支)
#
# 輸出: 繁體中文 markdown 階段回報,直接貼給使用者。
# 結束碼: 0=回報完整 1=有警告(缺工作日誌,內容已暫存) 2=用法錯誤 3=相依工具找不到
#
# 陷阱:
# - 結束碼 1 是警告,不是阻擋:階段的工作已經做完了,擋下去只會讓使用者拿不到回報。
# - 缺工作日誌時一定要給 --pending-file 與 --log-hash,否則內容留在對話裡,換一個工作階段就沒了。
# - 模型閘門那一列只轉述 sdlc-gate.sh report 的結果,不自己判定標籤。
set -u
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
plugin_root="${CLAUDE_PLUGIN_ROOT:-$script_dir/..}"
usage() {
cat >&2 <<'EOF'
用法:
stage-report.sh <plan|analyze|implement|maintain> [選項]
--page TYPE:PAGE 本階段寫入的 wiki 頁,可重複
--worklog PAGE 已寫入的工作日誌頁名
--worklog-heading TEXT 日誌條目標題(組錨點用)
--pending-file FILE 沒有工作日誌時要暫存的內容檔
--log-hash HASH 暫存歸屬的工作日誌 hash
--worktree PATH 實作:工作目錄
--source-branch NAME 實作:來源分支
--work-branch NAME 實作:工作分支
--target-branch NAME 實作:目標分支(省略時同來源分支)
--pr URL 實作:PR 連結
結束碼: 0=完整 1=缺工作日誌(已暫存,警告) 2=用法錯誤 3=相依工具找不到
EOF
exit 2
}
# 找相依腳本。兩種版面都要顧到:並排存取庫(開發用)與已安裝 plugin(各版本一個目錄)。
resolve_dep() { # $1=並排目錄名(gitea、hooks、log) $2=plugin 內的相對路徑
_name="$1"; _rel="$2"
for _c in "$plugin_root/../$_name/$_rel" "$plugin_root/../jsc-$_name/$_rel"; do
[ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
done
_c=$(ls -d "$plugin_root"/../../"jsc-$_name"/*/"$_rel" \
"$plugin_root"/../../"$_name"/*/"$_rel" \
"$HOME"/.claude/plugins/cache/*/"jsc-$_name"/*/"$_rel" 2>/dev/null \
| sort | tail -n1)
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
return 1
}
gitea_sh() {
if [ -n "${JSC_GITEA_TOOLS:-}" ] && [ -f "$JSC_GITEA_TOOLS/gitea.sh" ]; then
printf '%s\n' "$JSC_GITEA_TOOLS/gitea.sh"; return 0
fi
resolve_dep gitea tools/gitea.sh
}
sdlc_gate_sh() {
if [ -n "${JSC_HOOKS_DIR:-}" ] && [ -f "$JSC_HOOKS_DIR/sdlc-gate.sh" ]; then
printf '%s\n' "$JSC_HOOKS_DIR/sdlc-gate.sh"; return 0
fi
resolve_dep hooks hooks/sdlc-gate.sh
}
stage="${1:-}"
case "$stage" in
plan|analyze|implement|maintain) shift ;;
*) usage ;;
esac
pages=''
worklog=''
worklog_heading=''
pending_file=''
log_hash=''
worktree=''
source_branch=''
work_branch=''
target_branch=''
pr_url=''
while [ "$#" -gt 0 ]; do
case "$1" in
--page) [ "$#" -ge 2 ] || usage; pages="$pages$2
"; shift 2 ;;
--worklog) [ "$#" -ge 2 ] || usage; worklog="$2"; shift 2 ;;
--worklog-heading) [ "$#" -ge 2 ] || usage; worklog_heading="$2"; shift 2 ;;
--pending-file) [ "$#" -ge 2 ] || usage; pending_file="$2"; shift 2 ;;
--log-hash) [ "$#" -ge 2 ] || usage; log_hash="$2"; shift 2 ;;
--worktree) [ "$#" -ge 2 ] || usage; worktree="$2"; shift 2 ;;
--source-branch) [ "$#" -ge 2 ] || usage; source_branch="$2"; shift 2 ;;
--work-branch) [ "$#" -ge 2 ] || usage; work_branch="$2"; shift 2 ;;
--target-branch) [ "$#" -ge 2 ] || usage; target_branch="$2"; shift 2 ;;
--pr) [ "$#" -ge 2 ] || usage; pr_url="$2"; shift 2 ;;
*) echo "[jsc][階段回報][ERR]:不認得的選項「$1」。" >&2; usage ;;
esac
done
GITEA=$(gitea_sh) || { echo '[jsc][階段回報][ERR]:找不到 jsc-gitea/tools/gitea.sh,無法把頁名換成網址。' >&2; exit 3; }
GATE=$(sdlc_gate_sh) || { echo '[jsc][階段回報][ERR]:找不到 jsc-hooks/hooks/sdlc-gate.sh,無法轉述閘門判定。' >&2; exit 3; }
stage_zh() {
case "$1" in
plan) echo '規劃' ;;
analyze) echo '分析' ;;
implement) echo '實作' ;;
maintain) echo '維護' ;;
esac
}
required_tag() {
case "$1" in
plan|analyze) echo 'reasoning-max' ;;
implement) echo 'coding' ;;
maintain) echo '無(模型 id 可判定即通過)' ;;
esac
}
# 錨點:Gitea wiki 的標題錨點比照 GitHub——轉小寫、空白換連字號、去掉標點;中文原樣保留。
anchor_of() {
printf '%s' "$1" \
| tr 'A-Z' 'a-z' \
| sed 's/[][()#。,、:;!?,.:;!?]//g; s/[[:space:]]\{1,\}/-/g'
}
page_url() { # TYPE PAGE -> 絕對網址;查不到就回空字串
_type="$1"; _page="$2"
_repo=$("$GITEA" wiki-repo "$_type" 2>/dev/null) || return 1
"$GITEA" wiki-url "$_repo" "$_page" 2>/dev/null || return 1
}
# ---- 模型能力標籤 ----
gate_line=$("$GATE" report 2>/dev/null || true)
gate_stage=$(printf '%s' "$gate_line" | awk '{print $2}')
gate_tag=$(printf '%s' "$gate_line" | awk '{print $3}')
gate_model=$(printf '%s' "$gate_line" | awk '{print $4}')
gate_source=$(printf '%s' "$gate_line" | awk '{print $5}')
gate_verdict=$(printf '%s' "$gate_line" | awk '{print $6}')
if [ -z "$gate_line" ]; then
gate_result="查不到閘門狀態(未上鎖或狀態檔不在),請重跑 sdlc-gate.sh lock $stage"
elif [ "$gate_stage" != "$stage" ]; then
gate_result="不符:本階段為 $stage,鎖上的卻是 $gate_stage,請重跑 sdlc-gate.sh lock $stage"
else
gate_result="通過(必要標籤 $(required_tag "$stage"),實際模型 ${gate_model:-未知},來源 ${gate_source:-未知},判定 ${gate_verdict:-未知},鎖上時的標籤 ${gate_tag:-無})"
fi
# ---- 工作日誌 ----
warn=0
worklog_cell=''
if [ -n "$worklog" ]; then
wl_url=$(page_url LOG "$worklog" || true)
if [ -n "$wl_url" ]; then
if [ -n "$worklog_heading" ]; then
worklog_cell="[$worklog_heading]($wl_url#$(anchor_of "$worklog_heading"))"
else
worklog_cell="[$worklog]($wl_url)"
fi
else
worklog_cell="$worklog(取不到網址,請確認 LOG 的 wiki 存取庫設定)"
fi
else
warn=1
worklog_cell='**尚未寫入**'
fi
# ---- 暫存 ----
pending_note=''
if [ "$warn" -eq 1 ]; then
if [ -n "$pending_file" ] && [ -n "$log_hash" ]; then
PENDING=$(resolve_dep log tools/worklog-pending.sh || true)
if [ -n "$PENDING" ]; then
if saved=$("$PENDING" add "$log_hash" "$pending_file" 2>&1); then
pending_note="已暫存到 $saved,下次 jsc-log:worklog 寫入時一併寫進去。"
else
pending_note="暫存失敗:$saved"
fi
else
pending_note='找不到 jsc-log/tools/worklog-pending.sh,這次的內容沒有暫存起來。'
fi
else
pending_note='沒有給 --pending-file 與 --log-hash,這次的內容沒有暫存起來。'
fi
fi
# ---- 分支資訊(實作階段) ----
branch_rows=''
if [ "$stage" = implement ]; then
wt="${worktree:-}"
git_in() { # 在工作目錄裡跑 git
if [ -n "$wt" ]; then git -C "$wt" "$@" 2>/dev/null; else git "$@" 2>/dev/null; fi
}
src_cell='未提供'
if [ -n "$source_branch" ]; then
if git_in show-ref --verify --quiet "refs/remotes/origin/$source_branch"; then
src_cell="origin/$source_branch(遠端)"
else
src_cell="$source_branch(**遠端找不到**,只有本機)"
fi
fi
work_cell='未提供'
if [ -n "$work_branch" ]; then
base="origin/$source_branch"
git_in show-ref --verify --quiet "refs/remotes/origin/$source_branch" || base="$source_branch"
count=$(git_in rev-list --count "$base..$work_branch" || true)
[ -n "$count" ] || count='?'
if git_in show-ref --verify --quiet "refs/remotes/origin/$work_branch"; then
ahead=$(git_in rev-list --count "origin/$work_branch..$work_branch" || true)
if [ "${ahead:-0}" = 0 ]; then push_cell='已 push'; else push_cell="尚有 ${ahead} 個 commit 未 push"; fi
else
push_cell='**未 push**'
fi
work_cell="$work_branch($count commit、$push_cell)"
fi
tgt="${target_branch:-$source_branch}"
if [ -n "$pr_url" ]; then
tgt_cell="${tgt:-未提供}(PR $pr_url)"
else
tgt_cell="${tgt:-未提供}(**尚無 PR**)"
warn=1
fi
branch_rows=$(cat <<EOF
| 工作目錄 | ${worktree:-未提供} |
| 來源分支 | $src_cell |
| 工作分支 | $work_cell |
| 目標分支 | $tgt_cell |
EOF
)
fi
# ---- 輸出 ----
printf '## 階段回報:%s\n\n' "$(stage_zh "$stage")"
printf '| 項目 | 內容 |\n| --- | --- |\n'
printf '| 模型能力標籤 | %s |\n' "$gate_result"
printf '| 工作日誌 | %s |\n' "$worklog_cell"
[ -n "$branch_rows" ] && printf '%s\n' "$branch_rows"
printf '\n### 寫入的 wiki 頁\n\n'
if [ -n "$pages" ]; then
printf '| 頁面 | 連結 |\n| --- | --- |\n'
printf '%s' "$pages" | while IFS= read -r item; do
[ -n "$item" ] || continue
type=${item%%:*}
page=${item#*:}
url=$(page_url "$type" "$page" || true)
printf '| %s | %s |\n' "$page" "${url:-(取不到網址)}"
done
else
printf '本階段沒有寫入任何 wiki 頁。\n'
fi
if [ "$warn" -eq 1 ]; then
printf '\n### 警告\n\n'
[ -n "$worklog" ] || printf -- '- 這個階段還沒有寫入工作日誌,請自行檢查是不是漏了。%s\n' "$pending_note"
[ "$stage" = implement ] && [ -z "$pr_url" ] && printf -- '- 目標分支還沒有 PR,工作尚未交出去。\n'
exit 1
fi
exit 0