Files
system-dev-template/scripts/check-no-instance-names.sh
Leo 6a49f25aef feat(W2 Phase 0-1): 雙 profile 地基+JDD 兩軸身分+兩支防炸閘
SDD: docs/3-specs/jdd-dual-profile(draft → active,leo 2026-08-05 回「開工」)
範圍:總管指定的「防炸兩件 → Phase 0 → Phase 1」,Phase 2 以後未開工。

■ 防炸(排在所有 task 之前,因為它們炸的是既有的東西)
- check-no-instance-names.sh + instance-names.txt:框架範本不得混入實例專名
  基線實測 22 行命中(非先前誤報的 20)→ 9 行無損泛化改寫、13 行檔級豁免記帳待 W3 搬走
  拒絕假性清理(把專名換成模糊詞=資訊消失、分層問題還在)
- check-legacy-paths.sh:已發佈腳本引用的 35 條遠端路徑只增不移
  舊實例跑的是舊腳本、路徑寫死;搬檔=整排 404 且不會有下一次更新來修它(1.16.0 前科)

■ Phase 0 地基
- template/manifest/{common,repo,orchestrator}.tsv:安裝清單單一真相源
  修好 install/update 兩份硬編清單的既有漂移——install 從不裝 wiki-first-search /
  subagent-wiki-guard / publish-lag-check / decisions-summary,但 update 會
  ⇒ 乾淨安裝反而拿不到 1.16/1.17/1.18 的招牌功能
- .claude/hooks/lib/role-lib.sh:scope×role 兩軸機械判定,零自陳
  身分矩陣六組實測全通過,含「成員 repo × orchestrator」不存在的格子擋下
- .sdt-framework-dev:框架開發標記(官方沒有 --framework-dev 這個參數,實查非記憶)

■ Phase 1 雙 profile
- profiles/{repo,orchestrator}/CLAUDE.md 兩部憲法
- install.sh:--profile + 自動偵測+寫檔前確認、manifest 驅動、
  CLAUDE.md 三段組裝(框架區/本地補充區界標+sha256)、.profile、.template-manifest、
  settings.json 寫入 env.AGENT_ROLE 預設
- update.sh:漂移偵測(不覆蓋手改檔、另存 .new、白話清單)+ 基準快照隨更新前進
- template/CLAUDE.md 原路徑凍結留底(相容)

■ 順手修掉兩個舊 bug(都在本次要動的函式裡)
- add_if_missing 少了 mkdir -p ⇒ 新目錄的檔 curl 失敗但 VERSION 照升(2026-07 記「待回報」至今未修)
- 下載健全性只用 [ -s ]=非空即接受 ⇒ 404 頁面會無聲覆寫好檔
  (SKILL.md 260→1 行的機制;同一支腳本的版本號那條路早就防了,檔案這條沒防)

■ 實測(非推論)
- G4 憲法分流:兩個乾淨環境各裝一次,orchestrator 版含 SDD 三件式關鍵字 0 次、
  repo 版含上游指針 8 次;界標 4/4;sha 宣告與實算相符
- G7 CI 擋實例名:注入違規行 → fail 並指出 sdd-check.md:77,exit 1;還原後 exit 0
- 漂移偵測:手改兩支 hook → 正確報 2 支、手改內容保住、產 .new;
  解掉後歸零;連跑三輪冪等

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

137 lines
6.5 KiB
Bash
Executable File
Raw Permalink 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-no-instance-names.sh — 機械閘 #2:框架範本不得混入實例專名
#
# 出處:《分離導入規格》第六節第 2 條、第七節第四題 Gherkin。
# Scenario: 框架混入實例名被 CI 擋
# Given 範本檔新增一行含 "arcrun"
# When CI 執行
# Then 檢查 fail 並指出檔案與行號
#
# 為什麼需要它(不是潔癖,是分層會自己塌):
# L1 框架的判準是「換一家公司照樣成立」。一旦範本裡寫著某個實例的專案名,
# 下一個裝這套 template 的人就會讀到一段對他不成立的規則——而他無從分辨
# 「這條是普世方法論」還是「這是別人家的家規」。分層一旦糊掉就回不去了。
#
# ── 豁免機制(為什麼要有)─────────────────────────────
# 本閘上線時,範本區已經有 22 行命中(8 個檔)。若不給豁免就硬上線,唯一的
# 通關方式會是「把 arcrun 改寫成『某工作流引擎』」——**資訊消失了,問題還在**
# (假性清理)。所以:真正該搬走的東西標豁免、記在帳上,隨 policy pack 一起搬;
# 純粹是註解舉例的,當場改寫成通用敘述。
# 豁免有兩級:
# · 行級 `sdt-instance-name-ok` → 該行豁免。用在「檔案本身是框架的,只有這行欠著」。
# · 檔級 `sdt-instance-name-ok-file` → 整檔豁免(寫在檔首 30 行內,需附理由)。
# 用在「整支本來就是 L2 政策包產物、只是還沒搬走」——逐行標 13 次是雜訊不是紀律。
#
# 誠實限制:這支只認「字面出現」。它擋不了「把實例假設寫成通用語氣」
# (例如寫「你的工作流引擎會在遠端執行」其實只有某一家成立)。
# 它是底線不是萬能——真正的分層判斷仍要人/CC 動腦。
#
# 用法:
# bash scripts/check-no-instance-names.sh # CI 模式,有未豁免命中 → exit 1
# bash scripts/check-no-instance-names.sh --baseline # 只列清單(含已豁免),永遠 exit 0
#
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
NAMES_FILE="scripts/instance-names.txt"
SCAN_DIR="template"
EXEMPT_MARK="sdt-instance-name-ok"
EXEMPT_FILE_MARK="sdt-instance-name-ok-file"
EXEMPT_HEAD_LINES=30 # 檔級豁免標記必須寫在檔首這麼多行內(強迫它顯眼、不許藏在檔尾)
MODE="ci"
[ "${1:-}" = "--baseline" ] && MODE="baseline"
if [ ! -f "$NAMES_FILE" ]; then
echo "❌ 找不到黑名單設定檔:$NAMES_FILE" >&2
exit 1
fi
if [ ! -d "$SCAN_DIR" ]; then
echo "❌ 找不到範本區:$SCAN_DIR/" >&2
exit 1
fi
# ── 讀黑名單 → 組成 grep 的 alternation pattern ───────────────
PATTERN=""
while IFS= read -r line; do
case "$line" in ''|'#'*) continue ;; esac
term="$(printf '%s' "$line" | tr -d '[:space:]')"
[ -z "$term" ] && continue
if [ -z "$PATTERN" ]; then PATTERN="$term"; else PATTERN="$PATTERN|$term"; fi
done < "$NAMES_FILE"
if [ -z "$PATTERN" ]; then
echo "⚠️ 黑名單是空的,本閘等同關閉($NAMES_FILE"
exit 0
fi
# ── 掃描 ────────────────────────────────────────────
# policy pack 不受此限(L2 本來就是一家之言)。目前尚無 policy pack 目錄,
# 先把慣例路徑寫在這裡,W3 建立時自動生效。
HITS="$(grep -rniE "$PATTERN" "$SCAN_DIR" 2>/dev/null \
| grep -v "^$SCAN_DIR/policy-packs/" || true)"
VIOLATIONS=0
EXEMPTED=0
VIOL_LINES=""
EXEMPT_LINES=""
if [ -n "$HITS" ]; then
while IFS= read -r hit; do
[ -z "$hit" ] && continue
hit_file="${hit%%:*}"
# 檔級豁免:整支本來就是 L2 產物,檔首宣告過就整檔記帳
if [ -f "$hit_file" ] && head -n "$EXEMPT_HEAD_LINES" "$hit_file" 2>/dev/null | grep -q "$EXEMPT_FILE_MARK"; then
EXEMPTED=$((EXEMPTED + 1))
EXEMPT_LINES="${EXEMPT_LINES}${hit}"$'\n'
continue
fi
# 行級豁免:檔案是框架的,只有這行欠著
if printf '%s' "$hit" | grep -q "$EXEMPT_MARK"; then
EXEMPTED=$((EXEMPTED + 1))
EXEMPT_LINES="${EXEMPT_LINES}${hit}"$'\n'
else
VIOLATIONS=$((VIOLATIONS + 1))
VIOL_LINES="${VIOL_LINES}${hit}"$'\n'
fi
done <<< "$HITS"
fi
# ── 輸出 ────────────────────────────────────────────
if [ "$MODE" = "baseline" ]; then
echo "════════════════════════════════════════════════"
echo "📊 實例專名基線報表($SCAN_DIR/"
echo "════════════════════════════════════════════════"
echo "未豁免(違規):$VIOLATIONS 行"
[ -n "$VIOL_LINES" ] && printf '%s' "$VIOL_LINES" | cut -c1-160 | sed 's/^/ ❌ /'
echo "已豁免(記在帳上,待搬遷):$EXEMPTED 行"
[ -n "$EXEMPT_LINES" ] && printf '%s' "$EXEMPT_LINES" | cut -c1-160 | sed 's/^/ ⏳ /'
exit 0
fi
if [ "$VIOLATIONS" -gt 0 ]; then
echo "════════════════════════════════════════════════" >&2
echo "❌ 框架範本混入實例專名($VIOLATIONS 行)" >&2
echo "════════════════════════════════════════════════" >&2
printf '%s' "$VIOL_LINES" | cut -c1-200 | sed 's/^/ /' >&2
cat >&2 <<EOF
鐵律:框架不含實例資料(《分離導入規格》第一節鐵律 2)。
L1 範本要「換一家公司照樣成立」;上面這些行只對某一家成立。
怎麼修(擇一,別選第四條「把詞換掉了事」):
1. 只是註解/舉例 → 改寫成通用敘述(例:「某個 repo」而非具體專案名)
2. 其實是政策包的內容 → 搬進 policy pack(L2),不要留在框架
3. 真的暫時搬不走 → 行尾加註記 $EXEMPT_MARK + 一句「為什麼還不能拿掉」
⚠️ 不准做的:把專名換成模糊詞讓檢查過關——資訊消失了,分層問題還在。
已豁免(不計違規):$EXEMPTED 行
EOF
exit 1
fi
echo "✅ 範本區無未豁免的實例專名(已豁免 $EXEMPTED 行,記在帳上待搬遷)"
exit 0