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>
This commit is contained in:
+231
-98
@@ -36,29 +36,49 @@ SCRIPTS_URL="$TEMPLATE_SOURCE/scripts"
|
||||
CREATED=()
|
||||
SKIPPED=()
|
||||
|
||||
# ── 解析模組參數 ──────────────────────────────────
|
||||
# ── 解析模組 / profile 參數 ────────────────────────
|
||||
# profile = scope 軸(這個資料夾是「成員 repo」還是「總管」)。
|
||||
# 它決定裝哪一部憲法——agent 在哪裡醒來就只讀得到哪部,**不靠 agent 自我判斷**。
|
||||
#
|
||||
# ⚠️ 契約在檔案,不在這支腳本:真正的 scope 真相源是 `system-dev/.profile`。
|
||||
# 本腳本只是**其中一個寫入者**——「把網址丟給 AI 幫我裝」那條路(主打入口)
|
||||
# 同樣必須寫出這個檔,格式一致即可互通。腳本入口是備援與參考實作,不是唯一路徑。
|
||||
MODULE=""
|
||||
PROFILE=""
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--wiki|--wiki-only) MODULE="wiki" ;;
|
||||
--sdd|--sdd-only) MODULE="sdd" ;;
|
||||
--all) MODULE="all" ;;
|
||||
--wiki|--wiki-only) MODULE="wiki" ;;
|
||||
--sdd|--sdd-only) MODULE="sdd" ;;
|
||||
--all) MODULE="all" ;;
|
||||
--profile=repo) PROFILE="repo" ;;
|
||||
--profile=orchestrator) PROFILE="orchestrator" ;;
|
||||
--profile=*)
|
||||
echo "❌ 不認得的 profile:${arg#--profile=}(只接受 repo / orchestrator)" >&2
|
||||
exit 1 ;;
|
||||
-h|--help)
|
||||
if [ "$IS_ZH" = "yes" ]; then
|
||||
cat <<'HELP'
|
||||
用法:install.sh [--wiki | --sdd | --all]
|
||||
用法:install.sh [--wiki | --sdd | --all] [--profile=repo|orchestrator]
|
||||
--wiki 只裝 LLM Wiki(CC 記憶系統 + 機敏防護)
|
||||
--sdd 只裝 SDD 系統(動 code 前強制要有設計文件)
|
||||
--all 兩個都裝(預設)
|
||||
無參數 互動式詢問要裝哪個
|
||||
|
||||
--profile=repo 成員 repo:實際寫 code 的子專案(預設)
|
||||
--profile=orchestrator 總管:管一群成員 repo 的上層資料夾
|
||||
未指定 → 自動偵測並請你確認一次(結果寫進 system-dev/.profile,之後不再問)
|
||||
HELP
|
||||
else
|
||||
cat <<'HELP'
|
||||
Usage: install.sh [--wiki | --sdd | --all]
|
||||
Usage: install.sh [--wiki | --sdd | --all] [--profile=repo|orchestrator]
|
||||
--wiki Install LLM Wiki only (CC memory system + secret protection)
|
||||
--sdd Install SDD system only (require a design doc before touching code)
|
||||
--all Install both (default)
|
||||
no flag Interactively ask which to install
|
||||
|
||||
--profile=repo Member repo: a project where code actually gets written (default)
|
||||
--profile=orchestrator Orchestrator: the folder that manages several member repos
|
||||
unset -> auto-detect and ask once (result stored in system-dev/.profile)
|
||||
HELP
|
||||
fi
|
||||
exit 0 ;;
|
||||
@@ -107,6 +127,57 @@ echo ""
|
||||
t "📦 安裝模組:$MODULE" "📦 Module: $MODULE"
|
||||
echo ""
|
||||
|
||||
# ── 決定 profile(scope 軸)──────────────────────────
|
||||
# 必須在**寫任何檔案之前**問完(EARS-1.1.3)——裝了一半才問是最差的體驗。
|
||||
#
|
||||
# 偵測規則:目前目錄下有多個「各自帶 .git 的子目錄」= 這裡像是管著一群 repo 的上層
|
||||
# → 建議 orchestrator。偵測只給建議,**仍要人確認一次**(一次性,之後寫進 marker 檔)。
|
||||
detect_profile() {
|
||||
local n=0 d
|
||||
for d in */; do
|
||||
[ -d "$d/.git" ] && n=$((n + 1))
|
||||
[ "$n" -ge 2 ] && break
|
||||
done
|
||||
if [ "$n" -ge 2 ]; then printf 'orchestrator'; else printf 'repo'; fi
|
||||
}
|
||||
|
||||
if [ -z "$PROFILE" ]; then
|
||||
SUGGESTED="$(detect_profile)"
|
||||
if [ -t 0 ]; then
|
||||
echo ""
|
||||
t "🧭 這個資料夾是哪一種?(決定裝哪一部憲法)" \
|
||||
"🧭 What is this folder? (decides which constitution gets installed)"
|
||||
t " 1) 成員 repo —— 實際寫 code 的子專案" \
|
||||
" 1) Member repo — a project where code actually gets written"
|
||||
t " 2) 總管 —— 管一群成員 repo 的上層資料夾" \
|
||||
" 2) Orchestrator — the folder that manages several member repos"
|
||||
echo ""
|
||||
if [ "$SUGGESTED" = "orchestrator" ]; then
|
||||
t " (偵測到底下有多個各自獨立的 repo → 看起來像 2)" \
|
||||
" (found multiple independent repos below -> looks like 2)"
|
||||
else
|
||||
t " (沒偵測到多個獨立 repo → 看起來像 1)" \
|
||||
" (no multiple independent repos found -> looks like 1)"
|
||||
fi
|
||||
tn "請輸入 1 / 2 [預設:$SUGGESTED]:" "Enter 1 / 2 [default: $SUGGESTED]: "
|
||||
read -r pchoice || pchoice=""
|
||||
case "$pchoice" in
|
||||
1) PROFILE="repo" ;;
|
||||
2) PROFILE="orchestrator" ;;
|
||||
*) PROFILE="$SUGGESTED" ;;
|
||||
esac
|
||||
else
|
||||
# 非互動(curl | bash 無 tty)→ 用偵測值,並在結尾大聲說它是怎麼決定的
|
||||
PROFILE="$SUGGESTED"
|
||||
t "🧭 非互動環境 → 自動判定 profile:$PROFILE(可事後改 system-dev/.profile)" \
|
||||
"🧭 Non-interactive -> auto-detected profile: $PROFILE (change system-dev/.profile later)"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
t "🧭 安裝 profile:$PROFILE" "🧭 Profile: $PROFILE"
|
||||
echo ""
|
||||
|
||||
# ── 重複安裝防呆(1.10.1):install 只管「全新安裝」,一切後續歸 update ──
|
||||
# 判準是「裝過沒」,不分新版舊版:
|
||||
# - 新結構 system-dev/ 已存在,或
|
||||
@@ -243,97 +314,93 @@ download_if_missing() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ── 共用結構 ──────────────────────────────────────
|
||||
# 工具自己的文件骨架收進 system-dev/docs/(不污染用戶根目錄、不跟用戶自己的 docs/ 混)。
|
||||
# 注意語義分離:這裡的 system-dev/docs/ 是「工具文件」;用戶的 raw source(原始文件)
|
||||
# 另有其處(見上方 vault 偵測),工具只讀、不搬。
|
||||
# .claude/ 只留 CC 死綁的 commands/ + hooks/,工具資料一律不放這。
|
||||
create_dir "system-dev/docs/1-vision"
|
||||
create_dir "system-dev/docs/2-architecture/decisions"
|
||||
create_dir "system-dev/docs/4-guides"
|
||||
create_dir "system-dev/docs/5-records/incidents"
|
||||
create_dir "system-dev/docs/5-records/test-reports"
|
||||
create_dir "system-dev/docs/6-user"
|
||||
create_dir ".claude/commands"
|
||||
create_dir ".claude/hooks"
|
||||
download_if_missing "system-dev/docs/README.md" "$REPO_URL/system-dev/docs/README.md"
|
||||
# ── Manifest 驅動的安裝(取代原本硬編的檔案清單)──────────────
|
||||
# 為什麼改成這樣(SDD jdd-dual-profile 決策 D1):
|
||||
# 原本清單硬編在這裡,update.sh 另有一份幾乎重複的硬編。兩份手抄必然漂移,
|
||||
# 而且**已經漂了**——install 從來不裝 wiki-first-search.sh / subagent-wiki-guard.sh /
|
||||
# publish-lag-check.sh / decisions-summary.md,但 update 會裝/會保留。
|
||||
# ⇒ 乾淨安裝的人反而拿不到最新三版的招牌功能。
|
||||
# 現在兩支腳本讀同一份 manifest,這類漂移在結構上不可能再發生。
|
||||
#
|
||||
# manifest 欄位:src \t dest \t class \t module \t profile(詳見 template/manifest/common.tsv)
|
||||
|
||||
# Logseq 任務 marker 解析(單一真相源):vault 萃取(/wiki-extract)與 tasks→Project
|
||||
# 投影(tasks-project-sync)共用同一套解析,別各寫一份。放共用區,兩模組都拿得到。
|
||||
download_if_missing "system-dev/docs/4-guides/logseq-markers.md" "$REPO_URL/system-dev/docs/4-guides/logseq-markers.md"
|
||||
MANIFEST_DIR="system-dev/.template-manifest.d"
|
||||
mkdir -p "$MANIFEST_DIR"
|
||||
|
||||
# 工具版號:放 system-dev/,不寄生 .claude/。
|
||||
download_if_missing "system-dev/VERSION" "$REPO_URL/system-dev/VERSION"
|
||||
fetch_manifest() { # $1=名稱(common|repo|orchestrator)
|
||||
local name="$1" out="$MANIFEST_DIR/$1.tsv"
|
||||
if curl -sSL "$REPO_URL/manifest/$name.tsv" -o "$out" 2>/dev/null && [ -s "$out" ]; then
|
||||
return 0
|
||||
fi
|
||||
rm -f "$out"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── WIKI 模組 ─────────────────────────────────────
|
||||
# wiki 是工具資料 → 放 system-dev/wiki/(不放 .claude/)。
|
||||
# commands/ 與 hooks/ 是 CC 機制檔 → 維持 .claude/。
|
||||
if $WANT_WIKI; then
|
||||
create_dir "system-dev/wiki"
|
||||
download_if_missing "system-dev/wiki/INDEX.md" "$REPO_URL/system-dev/wiki/INDEX.md"
|
||||
download_if_missing "system-dev/wiki/TAXONOMY.md" "$REPO_URL/system-dev/wiki/TAXONOMY.md"
|
||||
download_if_missing "system-dev/wiki/status.md" "$REPO_URL/system-dev/wiki/status.md"
|
||||
download_if_missing "system-dev/wiki/mistakes.md" "$REPO_URL/system-dev/wiki/mistakes.md"
|
||||
download_if_missing "system-dev/wiki/principles.md" "$REPO_URL/system-dev/wiki/principles.md"
|
||||
download_if_missing "system-dev/wiki/.wikiignore" "$REPO_URL/system-dev/wiki/.wikiignore"
|
||||
if ! fetch_manifest common; then
|
||||
echo "❌ 抓不到安裝清單($REPO_URL/manifest/common.tsv)——網路或來源網址有問題,中止。" >&2
|
||||
exit 1
|
||||
fi
|
||||
fetch_manifest "$PROFILE" || true # profile 專屬清單可以是空的
|
||||
|
||||
# wiki 改寫產物(AI 自讀定稿卡片)的正式落點:由工具建好,不靠用戶自救。
|
||||
create_dir "system-dev/wiki/cards"
|
||||
# src 前綴 → 實際來源網址
|
||||
resolve_src() { # $1=src 欄
|
||||
case "$1" in
|
||||
T:*) printf '%s/%s' "$REPO_URL" "${1#T:}" ;;
|
||||
S:*) printf '%s/%s' "$SCRIPTS_URL" "${1#S:}" ;;
|
||||
*) printf '' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 要不要裝這一行(依模組)
|
||||
want_module() { # $1=module 欄
|
||||
case "$1" in
|
||||
core) return 0 ;;
|
||||
wiki) $WANT_WIKI && return 0 || return 1 ;;
|
||||
sdd) $WANT_SDD && return 0 || return 1 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 逐行安裝。CLAUDE.md(class=claude-md)不在這裡處理——它要三段組裝,走下方專段。
|
||||
install_from_manifest() { # $1=manifest 檔
|
||||
local f="$1" src dest class module profile url
|
||||
[ -f "$f" ] || return 0
|
||||
while IFS=$'\t' read -r src dest class module profile; do
|
||||
case "$src" in ''|'#'*) continue ;; esac
|
||||
[ -z "${dest:-}" ] && continue
|
||||
want_module "$module" || continue
|
||||
case "$class" in
|
||||
dir)
|
||||
create_dir "$dest"
|
||||
;;
|
||||
claude-md)
|
||||
CLAUDE_MD_SRC="$(resolve_src "$src")" # 交給下方 CLAUDE.md 組裝段
|
||||
;;
|
||||
*)
|
||||
url="$(resolve_src "$src")"
|
||||
[ -z "$url" ] && continue
|
||||
download_if_missing "$dest" "$url"
|
||||
;;
|
||||
esac
|
||||
done < "$f"
|
||||
}
|
||||
|
||||
CLAUDE_MD_SRC=""
|
||||
install_from_manifest "$MANIFEST_DIR/common.tsv"
|
||||
install_from_manifest "$MANIFEST_DIR/$PROFILE.tsv"
|
||||
|
||||
# wiki 卡片落點的 .gitkeep(目錄由 manifest 建,這顆種子檔留在腳本裡)
|
||||
if $WANT_WIKI && [ -d "system-dev/wiki/cards" ]; then
|
||||
[ -f "system-dev/wiki/cards/.gitkeep" ] || { : > "system-dev/wiki/cards/.gitkeep"; CREATED+=("system-dev/wiki/cards/.gitkeep"); }
|
||||
|
||||
download_if_missing ".claude/commands/wiki-init.md" "$REPO_URL/.claude/commands/wiki-init.md"
|
||||
download_if_missing ".claude/commands/wiki-capture.md" "$REPO_URL/.claude/commands/wiki-capture.md"
|
||||
download_if_missing ".claude/commands/wiki-update.md" "$REPO_URL/.claude/commands/wiki-update.md"
|
||||
download_if_missing ".claude/commands/wiki-recall.md" "$REPO_URL/.claude/commands/wiki-recall.md"
|
||||
# vault 增量萃取(Logseq/Obsidian → system-dev/wiki,冪等):給 Routine 反覆跑。
|
||||
download_if_missing ".claude/commands/wiki-extract.md" "$REPO_URL/.claude/commands/wiki-extract.md"
|
||||
|
||||
# wiki 相關 hooks:接關 + 機敏掃描
|
||||
download_if_missing ".claude/hooks/session-start-recall.sh" "$REPO_URL/.claude/hooks/session-start-recall.sh"
|
||||
download_if_missing ".claude/hooks/wiki-secret-scan.sh" "$REPO_URL/.claude/hooks/wiki-secret-scan.sh"
|
||||
|
||||
# Cowork(claude.ai)整理 wiki 用的 skill:與 CC 的 /wiki-init 共用同一套規則
|
||||
# (含 typed-edge、frontmatter 標籤、gloss)。沒這支 → claude.ai 來掃時身上沒規則。
|
||||
download_if_missing "system-dev/docs/SKILL.md" "$REPO_URL/system-dev/docs/SKILL.md"
|
||||
fi
|
||||
|
||||
# ── SDD 模組 ──────────────────────────────────────
|
||||
if $WANT_SDD; then
|
||||
create_dir "system-dev/docs/3-specs"
|
||||
download_if_missing "system-dev/docs/3-specs/TEMPLATE-sdd/design.md" "$REPO_URL/system-dev/docs/3-specs/TEMPLATE-sdd/design.md"
|
||||
download_if_missing "system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md" "$REPO_URL/system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md"
|
||||
download_if_missing "system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md" "$REPO_URL/system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md"
|
||||
|
||||
download_if_missing ".claude/commands/sdd-check.md" "$REPO_URL/.claude/commands/sdd-check.md"
|
||||
download_if_missing ".claude/hooks/sdd-guard.sh" "$REPO_URL/.claude/hooks/sdd-guard.sh"
|
||||
|
||||
# SDD 生命週期鐵律(1.14,issue #6):規則真相源 + 規格變更緩衝區 + 獨立單一活性檢查
|
||||
download_if_missing "system-dev/docs/3-specs/SDD-LIFECYCLE.md" "$REPO_URL/system-dev/docs/3-specs/SDD-LIFECYCLE.md"
|
||||
download_if_missing "system-dev/docs/3-specs/pending-changes.md" "$REPO_URL/system-dev/docs/3-specs/pending-changes.md"
|
||||
download_if_missing "system-dev/scripts/sdd-active-check.sh" "$REPO_URL/scripts/sdd-active-check.sh"
|
||||
|
||||
# ── tasks⇄Project 投影(optional,issue #16)──────────────────
|
||||
# 帶檔 ≠ 啟用:workflow yaml 只是「留記錄+手動啟用素材」,啟用=對話答好且 acr push。
|
||||
# 投影邏輯依附 tasks.md(住 3-specs),故隨 SDD 模組帶下來;裝了不代表開。
|
||||
create_dir "system-dev/workflows"
|
||||
download_if_missing "system-dev/workflows/tasks-project-sync.yaml" "$REPO_URL/system-dev/workflows/tasks-project-sync.yaml"
|
||||
download_if_missing "system-dev/workflows/tasks-project-sync.local.sh" "$REPO_URL/system-dev/workflows/tasks-project-sync.local.sh"
|
||||
fi
|
||||
|
||||
# ── 安裝/更新腳本:一開始就放進 system-dev/scripts/ ──
|
||||
# 為什麼一開始就裝:之後要更新,用戶(或 CC)直接 `bash system-dev/scripts/update.sh`,
|
||||
# 不必每次都記那串 curl。腳本來源在 main/scripts/(不在 template/)。
|
||||
create_dir "system-dev/scripts"
|
||||
download_if_missing "system-dev/scripts/install.sh" "$SCRIPTS_URL/install.sh"
|
||||
download_if_missing "system-dev/scripts/update.sh" "$SCRIPTS_URL/update.sh"
|
||||
|
||||
# ── 共用 hook:專案自訂禁令骨架(預設停用)────────
|
||||
download_if_missing ".claude/hooks/pre-write-guard.sh" "$REPO_URL/.claude/hooks/pre-write-guard.sh"
|
||||
|
||||
# ── 共用指引:GitHub issue 處理(讀/回普世,跨 repo 發要先問,禁自動輪詢)──
|
||||
download_if_missing ".claude/commands/issue-handle.md" "$REPO_URL/.claude/commands/issue-handle.md"
|
||||
# ── scope 軸 marker:這個實例是哪一種 ────────────────
|
||||
# 這是 role-lib.sh 判 scope 的**唯一**來源。AI 代裝路徑也必須寫出同格式的檔。
|
||||
printf '%s\n' "$PROFILE" > "system-dev/.profile"
|
||||
CREATED+=("system-dev/.profile ($PROFILE)")
|
||||
|
||||
chmod +x .claude/hooks/*.sh 2>/dev/null || true
|
||||
chmod +x .claude/hooks/lib/*.sh 2>/dev/null || true
|
||||
chmod +x system-dev/workflows/*.sh 2>/dev/null || true
|
||||
chmod +x system-dev/scripts/*.sh 2>/dev/null || true
|
||||
|
||||
@@ -346,7 +413,9 @@ build_hooks_json() {
|
||||
session_hooks='{ "type": "command", "command": ".claude/hooks/session-start-recall.sh" }'
|
||||
fi
|
||||
|
||||
# PreToolUse 依模組疊加
|
||||
# PreToolUse 依模組疊加。
|
||||
# 順序原則:**範圍大的擋在前**——這樣錯誤訊息會指向最根本的那條規則,
|
||||
# 而不是讓人先修一個表層問題、修完才發現底下還有一條。
|
||||
local pt=()
|
||||
$WANT_SDD && pt+=('{ "type": "command", "command": ".claude/hooks/sdd-guard.sh" }')
|
||||
pt+=('{ "type": "command", "command": ".claude/hooks/pre-write-guard.sh" }')
|
||||
@@ -354,7 +423,15 @@ build_hooks_json() {
|
||||
local IFS=,
|
||||
pretool_hooks="${pt[*]}"
|
||||
|
||||
printf '{\n "hooks": {\n'
|
||||
# role 軸預設值:由 profile 決定,寫進 settings.json 的 env。
|
||||
# 為什麼要寫死一個預設:AGENT_ROLE 沒設時 role-lib.sh 會「依 scope 推定」,
|
||||
# 但推定是保險不是設計——明寫出來,人才看得見自己這個實例預設是什麼身分。
|
||||
local default_role="engineer"
|
||||
[ "$PROFILE" = "orchestrator" ] && default_role="orchestrator"
|
||||
|
||||
printf '{\n'
|
||||
printf ' "env": { "AGENT_ROLE": "%s" },\n' "$default_role"
|
||||
printf ' "hooks": {\n'
|
||||
if [ -n "$session_hooks" ]; then
|
||||
printf ' "SessionStart": [\n { "matcher": "startup|resume|clear",\n "hooks": [ %s ] }\n ],\n' "$session_hooks"
|
||||
fi
|
||||
@@ -369,19 +446,75 @@ else
|
||||
SKIPPED+=(".claude/settings.json $(tn '(已存在,請手動合併 hooks)' '(already exists — merge hooks manually)')")
|
||||
fi
|
||||
|
||||
# ── CLAUDE.md:只在完全不存在時建立 ────────────────
|
||||
# 新建時把偵測到的 raw source 宣告 append 進去(在建立的當下寫入,
|
||||
# 不回頭改使用者既有的 CLAUDE.md,維持「已有不覆蓋」原則)。
|
||||
# ── CLAUDE.md:由 profile 範本三段組裝 ────────────────
|
||||
# 結構(界標用 HTML 註解:md 渲染看不見、grep 定位得到、CC 讀得到):
|
||||
#
|
||||
# <!-- sdt:framework begin profile=X version=Y sha256=Z -->
|
||||
# (profile 憲法範本原文,一字不改)
|
||||
# <!-- sdt:framework end -->
|
||||
# <!-- sdt:local begin -->
|
||||
# (raw source 宣告 + 之後使用者/CC 自由追加)
|
||||
# <!-- sdt:local end -->
|
||||
#
|
||||
# 為什麼要界標:在這之前 CLAUDE.md 是「整份下載 + append」,**沒有任何邊界** ⇒
|
||||
# update 無從分辨「這段是框架的、那段是你寫的」,因此永遠不敢覆蓋,
|
||||
# 框架改了憲法也送不到既有實例;而使用者手改框架段也沒人看得見。
|
||||
# 有了界標+sha256,兩件事同時解決:框架段可安全更新、被手改時抓得到(漂移偵測)。
|
||||
sdt_sha256() { # 跨平台取 sha256(macOS 用 shasum,Linux 多為 sha256sum)
|
||||
if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'
|
||||
elif command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}'
|
||||
else printf 'nohash'; fi
|
||||
}
|
||||
|
||||
if [ ! -f "CLAUDE.md" ]; then
|
||||
download_if_missing "CLAUDE.md" "$REPO_URL/CLAUDE.md"
|
||||
if [ -f "CLAUDE.md" ]; then
|
||||
emit_raw_source_block >> CLAUDE.md
|
||||
CREATED+=("CLAUDE.md $(tn "← 已寫入 raw source 宣告(${VAULT_TYPE})" "← raw source declaration written (${VAULT_TYPE})")")
|
||||
if [ -z "$CLAUDE_MD_SRC" ]; then
|
||||
echo "⚠️ manifest 沒有指定 $PROFILE 的 CLAUDE.md 範本,略過憲法安裝" >&2
|
||||
else
|
||||
FW_TMP="$(mktemp)"
|
||||
if curl -sSL "$CLAUDE_MD_SRC" -o "$FW_TMP" 2>/dev/null && [ -s "$FW_TMP" ]; then
|
||||
FW_SHA="$(sdt_sha256 "$FW_TMP")"
|
||||
TOOL_VER="$(tr -d '[:space:]' < system-dev/VERSION 2>/dev/null || echo 'unknown')"
|
||||
{
|
||||
printf '<!-- sdt:framework begin profile=%s version=%s sha256=%s -->\n' \
|
||||
"$PROFILE" "$TOOL_VER" "$(printf '%s' "$FW_SHA" | cut -c1-12)"
|
||||
cat "$FW_TMP"
|
||||
printf '<!-- sdt:framework end -->\n\n'
|
||||
printf '<!-- sdt:local begin — 這一區是你的,update 永遠不會動它 -->\n'
|
||||
emit_raw_source_block
|
||||
printf '\n<!-- sdt:local end -->\n'
|
||||
} > CLAUDE.md
|
||||
rm -f "$FW_TMP"
|
||||
CREATED+=("CLAUDE.md $(tn "← ${PROFILE} 憲法 + 本地補充區(raw source: ${VAULT_TYPE})" "← ${PROFILE} constitution + local section (raw source: ${VAULT_TYPE})")")
|
||||
else
|
||||
rm -f "$FW_TMP"
|
||||
echo "⚠️ 抓不到 $PROFILE 憲法範本($CLAUDE_MD_SRC),CLAUDE.md 未建立" >&2
|
||||
fi
|
||||
fi
|
||||
else
|
||||
SKIPPED+=("CLAUDE.md $(tn '(已存在,請手動加入對應區塊)' '(already exists — add the block manually)')")
|
||||
SKIPPED+=("CLAUDE.md $(tn '(已存在,未覆蓋——要導入 profile 憲法請跑 update.sh)' '(already exists — run update.sh to adopt the profile constitution)')")
|
||||
fi
|
||||
|
||||
# ── 產生 .template-manifest(漂移偵測的基準)────────────
|
||||
# 記下每個安裝產物「安裝當下」的雜湊。update 時比對:
|
||||
# 實檔 sha == 這裡的 sha → 乾淨,可安全覆蓋成新版
|
||||
# 實檔 sha != 這裡的 sha → **被手改過**,不覆蓋、列進漂移清單
|
||||
# 沒有這張表,就只能「不敢覆蓋」或「盲目覆蓋」二選一,兩個都錯。
|
||||
{
|
||||
printf '# dest\tclass\tversion\tsha256 —— 安裝當下的快照,供 update 判漂移用,勿手改\n'
|
||||
TOOL_VER="$(tr -d '[:space:]' < system-dev/VERSION 2>/dev/null || echo 'unknown')"
|
||||
for mf in "$MANIFEST_DIR/common.tsv" "$MANIFEST_DIR/$PROFILE.tsv"; do
|
||||
[ -f "$mf" ] || continue
|
||||
while IFS=$'\t' read -r m_src m_dest m_class m_module m_profile; do
|
||||
case "$m_src" in ''|'#'*) continue ;; esac
|
||||
[ -z "${m_dest:-}" ] && continue
|
||||
[ "$m_class" = "dir" ] && continue
|
||||
[ -f "$m_dest" ] || continue
|
||||
printf '%s\t%s\t%s\t%s\n' "$m_dest" "$m_class" "$TOOL_VER" "$(sdt_sha256 "$m_dest")"
|
||||
done < "$mf"
|
||||
done
|
||||
} > system-dev/.template-manifest
|
||||
CREATED+=("system-dev/.template-manifest")
|
||||
|
||||
# ── 輸出結果 ──────────────────────────────────────
|
||||
echo ""
|
||||
t "✅ 建立了:" "✅ Created:"
|
||||
|
||||
Reference in New Issue
Block a user