6a49f25aef
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>
137 lines
6.5 KiB
Bash
Executable File
137 lines
6.5 KiB
Bash
Executable File
#!/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
|