Files
system-dev-template/scripts/check-legacy-paths.sh
T
Leo 2f5d9f3bb2 feat(W2 Phase 2-3): JDD 文件範本+八條封路 hook+還清兩件舊債
SDD: docs/3-specs/jdd-dual-profile(active)。編號 task 26/33 完成,Phase 4-5 未開工。

■ Phase 2 JDD 文件範本(orchestrator profile)
範本形狀對齊「實際跑出來的那兩份」(總管已寫的 root.md 15 卡、journeys.md J-1 九站),
不是照規格憑空造:
- 卡片是巢狀 bullet(`- **P1** 🟢 …` + 子項放來源/對帳),非規格畫的平行文字行
- 站點索引**巢狀 bullet 不用表格**(表格會把層級壓平,看不出從屬)
- 兩份都保留「這卷還缺什麼(誠實記)」收尾段——規格沒有,但那是防假綠的地方
新增:root.md / journeys.md / sprint.md / triage-map.md 四範本(add-if-missing,
填了就永不覆蓋)+ plugin-load-order.md(W3 插槽,框架不發明平行外掛格式)

■ Phase 3 封路 hook(八條規則落六支檔)
- role-guard(J1+J2+J3)★命門:考生不能改考卷。六組實測含「考題藏在別的 md 裡」也擋
- jdd-format-guard(J4+J5+J8):紅卡缺對帳日/任務缺站號/PM 文件混技術名詞
- station-done-guard(J6):收工判準是站的考題全綠,不是任務全關
- regression-scope(J7):動實作 → 列出要重考哪幾題(只提醒不擋)
- install-artifact-guard(S1):實例不改機制
- orchestrator-scope-guard(S4):總管不進成員 repo 動實作(從實例上收進框架,
  路徑清單改由實例自填,範本零專名)
掛載鏈依「範圍大的擋在前」:改機制 → 角色 → 位置 → 格式 → 既有三支

■ 還清兩件舊債
- update.sh 檔案清單改讀 manifest(舊硬編降為抓不到來源時的 fallback)
  ——install/update 兩份手抄清單漂移的根因全修
- CLAUDE.md 界標補植:舊實例全文原封包進本地區、框架區重鋪、原檔備份、冪等
  ——解開「沒界標⇒不敢覆蓋⇒框架改的憲法永遠送不到既有實例」這個死結

■ 修掉三個自己造的問題(實測抓出來的,不是想出來的)
- jdd-format-guard 誤擋真實 journeys.md 的「這卷還缺什麼」自述段
  → 排除法改**正面圈定**(只掃卡片本體與站內文),說明區/自述段/索引自然不在範圍
- install-artifact-guard 把 pre-write-guard.sh 也擋了——而它的錯誤訊息正叫人去改那支
  → 使用者自訂插槽列為最優先放行
- check-legacy-paths 用 HEAD 當基準會**自我弱化**:改成清單驅動後保護範圍 35→29 條
  → 基準改指最後一次真正發佈的版本

■ 實測(全部貼過輸出)
- G2 考生改考卷:6/6,含 orchestrator 寫 code/engineer 改考題/考題藏別處
- G4 憲法分流:兩環境重裝,總管版技術軌關鍵字 0、成員版上游指針 8,界標 4/4
- G6 實例改機制:4/4,含框架開發標記放行與自訂插槽放行
- G7 CI 擋實例名:注入違規 → 指出檔案行號 exit 1
- G3 進度以站計量:起牀推「J-1 已點亮 2/9 站」、收工列未亮站並禁用任務數當理由
- 回歸考、界標補植冪等、orchestrator-scope-guard 四組:全通

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 00:26:56 +08:00

103 lines
4.6 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.
#!/bin/bash
# check-legacy-paths.sh — 相容閘:已發佈的安裝/更新腳本會抓的每個遠端路徑,都必須還在
#
# 這支存在的理由(SDD jdd-dual-profile 發現⑦ 決策 D3):
# install.sh / update.sh 是**逐檔 curl**:檔案清單與遠端路徑**寫死在腳本裡**。
# 而 update.sh 的「自我更新」在腳本**尾端**——舊實例這一輪跑的是**舊腳本**。
# ⇒ 只要框架把某個既有檔搬走/改名,舊實例的那一輪就是**整排 404**,
# 而且它們不會自動好:更新機制本身壞了,就沒有下一次更新來修它。
#
# 前科:1.16.0「update/install 來源改指 Gitea」——來源一改,
# 舊實例的自動更新當場死掉(CHANGELOG 1.16.0 有記)。同一種病。
#
# 所以鐵律是:**遠端路徑對既有實例是契約,只增不移。**
# 要搬版面 → 先在原路徑留相容檔(轉址說明/原內容),確認本閘綠了才准動。
#
# 作法:
# 從「已發佈版本」的 install.sh / update.sh 裡抽出所有 $REPO_URL/... $TEMPLATE_URL/...
# $SCRIPTS_URL/... 引用,映射回本 repo 的實體路徑,逐一確認檔案存在。
#
# 誠實限制:
# · 只驗「路徑還在不在」,不驗內容還對不對(舊腳本抓到新內容仍可能語意不相容)。
# · 基準是 git 裡的已發佈版本(預設 HEAD);再更老的版本若引用過更多路徑,
# 本閘看不到——要驗更早的版本,用 BASE=<commit> 指定。
#
# 用法:
# bash scripts/check-legacy-paths.sh # 以 HEAD 為已發佈基準
# BASE=dc4fe67 bash scripts/check-legacy-paths.sh
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
# ⚠️ 基準**不能**用 HEAD——這是實測踩到的:
# 把 install.sh 改成清單驅動之後,HEAD 裡的硬編路徑從 35 條掉到 29 條,
# **閘保護的範圍就跟著縮水 6 條**——而那 6 條正是還沒更新的舊實例仍然會去抓的。
# 用 HEAD 當基準,閘會隨著每次提交自我弱化,最後變成「只保護今天的自己」。
# ⇒ 基準必須是「**外面實際在跑的那一版**」=最後一次真的發佈出去的 commit。
# 發新版、且確認真的送出去之後,才把下面這行往前挪。
LAST_RELEASED_DEFAULT="dc4fe67" # 1.18.0
BASE="${BASE:-$LAST_RELEASED_DEFAULT}"
# ── 取出基準版本的兩支腳本 ────────────────────────────
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
for s in install.sh update.sh; do
if ! git show "$BASE:scripts/$s" > "$TMP/$s" 2>/dev/null; then
echo "⚠️ 基準 $BASE 沒有 scripts/$s,略過" >&2
: > "$TMP/$s"
fi
done
# ── 抽出遠端引用 → 映射成本地實體路徑 ──────────────────
# $REPO_URL/x → template/x
# $TEMPLATE_URL/x → template/x
# $SCRIPTS_URL/x → scripts/x
extract_paths() {
grep -oE '\$\{?(REPO_URL|TEMPLATE_URL|SCRIPTS_URL)\}?/[A-Za-z0-9._/-]+' "$1" 2>/dev/null \
| sed -e 's|\${*REPO_URL}*/|template/|' \
-e 's|\${*TEMPLATE_URL}*/|template/|' \
-e 's|\${*SCRIPTS_URL}*/|scripts/|' \
|| true
}
ALL="$( { extract_paths "$TMP/install.sh"; extract_paths "$TMP/update.sh"; } | sort -u )"
if [ -z "$ALL" ]; then
echo "⚠️ 在 $BASE 的腳本裡找不到任何遠端引用——抽取規則可能過時,本閘等同關閉" >&2
exit 1
fi
TOTAL=0
MISSING=0
MISSING_LIST=""
while IFS= read -r p; do
[ -z "$p" ] && continue
TOTAL=$((TOTAL + 1))
if [ ! -e "$p" ]; then
MISSING=$((MISSING + 1))
MISSING_LIST="${MISSING_LIST}${p}"$'\n'
fi
done <<< "$ALL"
if [ "$MISSING" -gt 0 ]; then
cat >&2 <<EOF
════════════════════════════════════════════════
❌ 相容閘:已發佈腳本會抓的路徑不見了($MISSING / $TOTAL
════════════════════════════════════════════════
$MISSING_LIST
後果(不是理論,是 1.16.0 真的發生過):
舊實例跑 update 時對上面每個路徑 curl 404 → 更新失敗 →
**更新機制本身壞掉,之後也不會有下一次更新來修它**。
正解:搬版面時在原路徑留相容檔(轉址說明或原內容),
本閘綠了才准繼續。遠端路徑是契約,只增不移。
EOF
exit 1
fi
echo "✅ 相容閘:已發佈腳本(基準 $BASE)引用的 $TOTAL 個路徑全部還在"
exit 0