chore: D22 落地——docs/SDD/wiki/CLAUDE.md 進 repo(Gitea private 預設全 push)
頂層 D22 決策(leo 2026-07-03 拍板):推什麼由開發環境歸屬決定, Gitea private=除機敏值/build 產物/.github 外全 push。 解 T1.5 卡點:雲端工人 clone 拿得到 credential-store-migration.md,可就地改寫 SDD。 機敏掃描兩輪通過(新增 189 檔約 2.1MB,node_modules/dist/wasm 照舊排除)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/post-edit-remind-tasks.sh
|
||||
# arcrun PostToolUse hook for Write / Edit / MultiEdit
|
||||
#
|
||||
# 職責:改完 code 後立刻提醒 CC 更新對應 tasks.md,避免批次更新
|
||||
# 退出 code:不 block,只提醒(exit 0)
|
||||
|
||||
set -o pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
|
||||
|
||||
# 只針對程式碼檔案提醒(不含 tasks.md / CLAUDE.md / rules/*.md 本身)
|
||||
if [[ "$FILE_PATH" =~ \.(go|ts|tsx|js|jsx|py|rs|wasm)$ ]] && [[ "$FILE_PATH" != *"tasks.md"* ]]; then
|
||||
cat >&2 <<EOF
|
||||
|
||||
📌 PostEdit 提醒(by arcrun hook)
|
||||
剛修改了:${FILE_PATH}
|
||||
|
||||
下一步動作:
|
||||
1. 找到對應的 docs/3-specs/*/tasks.md
|
||||
2. 如果這個 task 已完成 → 立刻把 [ ] 改成 [x](或把進行中的 [🔄] 改成 [x])
|
||||
3. 如果發現新的 sub-task → 立刻加入 tasks.md
|
||||
4. 不要等到 session 結束才批次更新
|
||||
|
||||
違反 SDD 協議會在 Stop hook 被提醒,建議現在就處理。
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/pre-bash-guard.sh
|
||||
# arcrun PreToolUse guard for Bash
|
||||
#
|
||||
# 職責:擋下會違反 CLAUDE rules 的 shell 指令
|
||||
# 退出 code:
|
||||
# 0 = 允許
|
||||
# 2 = 擋下(stderr 訊息會回傳給 CC)
|
||||
|
||||
set -o pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
|
||||
|
||||
block() {
|
||||
local rule="$1"
|
||||
local reason="$2"
|
||||
local fix="$3"
|
||||
cat >&2 <<EOF
|
||||
❌ BLOCKED by arcrun CLAUDE rules
|
||||
違反項:${rule}
|
||||
指令:${CMD}
|
||||
原因:${reason}
|
||||
正確做法:${fix}
|
||||
參考:.claude/rules/02-forbidden.md
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 1.2 / 3.3:禁止用 mkdir 建立違規的 auth/credential worker 目錄
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if echo "$CMD" | grep -qE "mkdir.*((auth|credential|jwt|oauth)[-_]worker|new[-_](auth|credential|jwt|oauth))"; then
|
||||
block "1.2/3.3" \
|
||||
"偵測到嘗試建立新的 auth/credential/jwt/oauth Worker 目錄" \
|
||||
"auth primitive 放在 registry/components/auth_*/;不需要另建 worker 目錄"
|
||||
fi
|
||||
|
||||
# 禁止建立同名零件的平行目錄
|
||||
if echo "$CMD" | grep -qE "mkdir.*/(gmail|telegram|google[-_]sheets|line[-_]notify|http[-_]request)[-_](v2|v3|new|worker|backup)"; then
|
||||
block "3.3" \
|
||||
"禁止為既有零件建立平行目錄(v2/new/worker/backup)" \
|
||||
"直接改 registry/components/<n>/main.go"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 1.3:禁止 wrangler init / generate auth-* credential-* jwt-*
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if echo "$CMD" | grep -qE "wrangler[[:space:]]+(init|generate).*[[:space:]](auth|credential|jwt|oauth)[-_]"; then
|
||||
block "1.3" \
|
||||
"禁止用 wrangler init/generate 建立 auth/credential/jwt Worker" \
|
||||
"auth primitive 透過 component-worker-template/ 搭配 WASM binary 部署,不要 wrangler init"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 3.1:Service Binding 新增警示
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 偵測在 wrangler.toml 新增 [[services]] 的 echo/cat/sed 操作(非 100% 準確,但夠用)
|
||||
if echo "$CMD" | grep -qE "echo.*\[\[services\]\].*>>"; then
|
||||
block "3.1" \
|
||||
"偵測到要在 wrangler.toml 新增 [[services]] binding" \
|
||||
"零件串接一律走 HTTP URL(cypher binding),不新增 service binding。若有特殊需求,先與 richblack 確認"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 一般性危險指令
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if echo "$CMD" | grep -qE "rm[[:space:]]+-rf[[:space:]]+(/|/\*|~|\\\$HOME|\.)"; then
|
||||
block "general" \
|
||||
"偵測到危險的 rm -rf 指令" \
|
||||
"明確指定要刪的目錄,不要對根目錄 / home / 當前目錄遞迴刪除"
|
||||
fi
|
||||
|
||||
# 禁止 force push 到 main
|
||||
if echo "$CMD" | grep -qE "git[[:space:]]+push.*--force.*(main|master)"; then
|
||||
block "general" \
|
||||
"禁止 force push 到 main/master" \
|
||||
"用 feature branch,或和 richblack 確認後手動操作"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 資料外流警示(data-exfil-warning SDD R2):AI 動手把資料變成可被外部呼叫前先擋
|
||||
# `acr push`(部署 webhook)/ `acr recipe push`(定義資料去向)= 暴露面動作。
|
||||
# 不含 `acr creds push`(上傳加密 credential 是保護,非暴露)。
|
||||
#
|
||||
# 信任修正(2026-05-30):無「旗標放行」捷徑——AI 自己能加的旗標 = 自己批准自己。
|
||||
# 這類動作一律擋,必須由人類在終端機親自執行(CLI 會跳互動、要人類輸入資源名確認)。
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 只在「指令本身就是執行 acr push / acr recipe push」時擋(錨定到指令開頭,
|
||||
# 允許前置 cd .. && 或環境變數)。避免誤判 git commit -m "...acr push..." 這類
|
||||
# 「字串裡剛好提到 acr push」的情況(commit message / echo / grep 不該被擋)。
|
||||
if echo "$CMD" | grep -qE "(^|&&|;|\|)[[:space:]]*(cd[[:space:]][^&;|]*(&&|;)[[:space:]]*)?([A-Za-z_]+=[^[:space:]]*[[:space:]]+)*acr[[:space:]]+(recipe[[:space:]]+)?push\b" \
|
||||
&& ! echo "$CMD" | grep -qE "acr[[:space:]]+creds[[:space:]]+push\b"; then
|
||||
cat >&2 <<'EOF'
|
||||
❌ BLOCKED:資料外流警示(arcrun data-exfil-warning)
|
||||
原因:acr push / acr recipe push 會把資料/workflow 變成「可被外部呼叫」(暴露面)。
|
||||
這種動作你(AI)不能自行執行,也沒有旗標捷徑——需人類明示知情同意(法律憑證)。
|
||||
正確做法:
|
||||
- 把這件事交給人類:請人類在終端機親自執行(CLI 會跳互動、要人類輸入資源名確認)
|
||||
- 人類第一次確認後 server 會記住,之後同資源不用再確認
|
||||
參考:docs/3-specs/data-exfil-warning/
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# Harness: Cold 啟動環境檢查
|
||||
# 用途:init 完成後強制檢查環境就緒,不靠提醒,違反條件直接擋住(exit 2)
|
||||
# 由 acr init 末尾調用
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo ".")"
|
||||
ARCRUN_CONFIG="${HOME}/.arcrun/config.yaml"
|
||||
|
||||
# 顏色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
FAILED=0
|
||||
|
||||
echo -e "${YELLOW}🔍 冷啟動環境檢查${NC}"
|
||||
echo ""
|
||||
|
||||
# 檢查 1: config.yaml 存在
|
||||
if [ ! -f "$ARCRUN_CONFIG" ]; then
|
||||
echo -e "${RED}❌ 缺少 $ARCRUN_CONFIG${NC}"
|
||||
echo " 執行:acr init"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ config.yaml 存在${NC}"
|
||||
fi
|
||||
|
||||
# 檢查 2: api_key 有值
|
||||
if grep -q "^api_key:" "$ARCRUN_CONFIG" 2>/dev/null; then
|
||||
api_key=$(grep "^api_key:" "$ARCRUN_CONFIG" | cut -d' ' -f2)
|
||||
if [ -z "$api_key" ] || [ "$api_key" = "null" ]; then
|
||||
echo -e "${RED}❌ api_key 未設${NC}"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ api_key 已設${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}❌ config.yaml 缺少 api_key${NC}"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# 檢查 3: .mcp.json 存在且指向自己(self-hosted 用)
|
||||
if grep -q "mode.*self-hosted" "$ARCRUN_CONFIG" 2>/dev/null; then
|
||||
if [ ! -f ".mcp.json" ]; then
|
||||
echo -e "${RED}❌ .mcp.json 缺失(self-hosted 必需)${NC}"
|
||||
echo " 執行:acr init 會自動生成"
|
||||
FAILED=1
|
||||
else
|
||||
# 檢查 mcp.json 是否指向自己(不是官方 URL)
|
||||
if grep -q "mcp.arcrun.dev" ".mcp.json" && ! grep -q "workers.dev" ".mcp.json"; then
|
||||
echo -e "${RED}❌ .mcp.json 指向官方 mcp.arcrun.dev(應指向自己的 worker)${NC}"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ .mcp.json 配置正確${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 檢查 4: acr 能執行
|
||||
if ! command -v acr &>/dev/null; then
|
||||
echo -e "${RED}❌ acr 未裝或不在 PATH${NC}"
|
||||
echo " 執行:npm i -g arcrun"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ acr 可執行${NC}"
|
||||
fi
|
||||
|
||||
# 檢查 5: D1 database 存在(如果 self-hosted)
|
||||
if grep -q "mode.*self-hosted" "$ARCRUN_CONFIG" 2>/dev/null; then
|
||||
if grep -q "d1_database_id" "$ARCRUN_CONFIG" 2>/dev/null; then
|
||||
db_id=$(grep "d1_database_id" "$ARCRUN_CONFIG" | cut -d' ' -f2)
|
||||
if [ -z "$db_id" ]; then
|
||||
echo -e "${RED}❌ D1 database_id 未設${NC}"
|
||||
echo " 執行:acr init 會建立 D1,或手動設定 d1_database_id"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ D1 database_id 已設${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ 環境就緒${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ 環境檢查失敗${NC}"
|
||||
echo ""
|
||||
echo "修復步驟:"
|
||||
echo "1. 檢查 ~/.arcrun/config.yaml 是否完整"
|
||||
echo "2. 若 self-hosted,確認 .mcp.json 指向自己的 mcp worker(不是官方)"
|
||||
echo "3. 若缺少 D1,重跑 acr init --self-hosted --account-id=... --api-token=..."
|
||||
echo ""
|
||||
exit 2
|
||||
fi
|
||||
Executable
+257
@@ -0,0 +1,257 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/pre-write-guard.sh
|
||||
# arcrun PreToolUse guard for Write / Edit / MultiEdit
|
||||
#
|
||||
# 職責:擋下會違反 CLAUDE rules 的檔案寫入操作
|
||||
# 退出 code:
|
||||
# 0 = 允許
|
||||
# 2 = 擋下(stderr 訊息會回傳給 CC)
|
||||
#
|
||||
# 依賴:jq
|
||||
|
||||
set -o pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
|
||||
# 取得將要寫入的內容(Write: content;Edit: new_string;MultiEdit: edits[].new_string 全部串起來)
|
||||
CONTENT=$(echo "$INPUT" | jq -r '
|
||||
.tool_input.content
|
||||
// .tool_input.new_string
|
||||
// (.tool_input.edits // [] | map(.new_string // "") | join("\n"))
|
||||
// ""
|
||||
')
|
||||
|
||||
block() {
|
||||
local rule="$1"
|
||||
local reason="$2"
|
||||
local fix="$3"
|
||||
cat >&2 <<EOF
|
||||
❌ BLOCKED by arcrun CLAUDE rules
|
||||
違反項:${rule}
|
||||
檔案:${FILE_PATH}
|
||||
原因:${reason}
|
||||
正確做法:${fix}
|
||||
參考:.claude/rules/02-forbidden.md
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 1.1:registry/components/ 下不准 TS(除非是 AssemblyScript)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" == *"registry/components/"* && "$FILE_PATH" == *.ts ]]; then
|
||||
# 允許 asconfig.json 同目錄的 AssemblyScript
|
||||
COMP_DIR=$(dirname "$FILE_PATH")
|
||||
if [[ ! -f "$COMP_DIR/asconfig.json" ]]; then
|
||||
block "1.1" \
|
||||
"registry/components/ 下禁止 TypeScript(除非是 AssemblyScript 且同目錄有 asconfig.json)" \
|
||||
"零件必須用 TinyGo (main.go) 或 AssemblyScript 實作並編譯成 .wasm"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 1.2:禁止在非法位置新增 auth/credential 實作
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 合法位置:registry/components/auth_static_key | auth_oauth2 | auth_service_account | auth_mtls
|
||||
if [[ "$FILE_PATH" =~ auth[-_](static[-_]key|oauth2|service[-_]account|mtls) ]]; then
|
||||
if [[ "$FILE_PATH" != *"registry/components/auth_"* ]]; then
|
||||
block "1.2" \
|
||||
"auth primitive 實作只能放在 registry/components/auth_<type>/" \
|
||||
"改去 registry/components/auth_static_key/ 等目錄,用 TinyGo 實作 main.go"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 2.1:禁止新增含特定關鍵字的 TS 檔案(credential-injector / jwt-signer 等)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" == *.ts ]]; then
|
||||
BASE=$(basename "$FILE_PATH")
|
||||
# 既有的 credential-injector.ts / jwt-signer.ts 允許修改(為了刪除),但不准新增同名
|
||||
if [[ "$BASE" =~ ^(credential[-_]injector|jwt[-_]signer)\.ts$ ]]; then
|
||||
if [[ ! -f "$FILE_PATH" ]]; then
|
||||
block "2.1" \
|
||||
"禁止新增 ${BASE}(Phase 1-3 的目標是刪除此類檔案,不是重建)" \
|
||||
"credential 注入 / JWT signing 屬於 WASM 零件職責,改去 registry/components/auth_*/"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 2.2:cypher-executor TS 裡不准實作業務邏輯(只准 wasi-shim.ts 做 crypto)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" == *"cypher-executor/src/"* && "$FILE_PATH" == *.ts ]]; then
|
||||
BASE=$(basename "$FILE_PATH")
|
||||
|
||||
# 通用類別:種子資料檔(*-seeds.ts)= installer 要灌進 KV 的 recipe 種子清單。
|
||||
# 其中的 endpoint / {{template}} 字串是「資料欄位」(宣告 recipe 打哪 / 怎麼注入),
|
||||
# 會被序列化寫進 RECIPES KV,**不是** TS 裡的呼叫 / 展開實作;真正執行仍走零件 / WASM primitive。
|
||||
# 故整類豁免 endpoint / template 字串檢查。這是「種子資料檔」這個普遍類別的規則,
|
||||
# 不是為某個零件 / recipe 開的特例(richblack 2026-06-06 原則:不為單一零件改全域規則)。
|
||||
IS_SEED_DATA_FILE=false
|
||||
if [[ "$BASE" == *-seeds.ts ]]; then
|
||||
IS_SEED_DATA_FILE=true
|
||||
fi
|
||||
|
||||
# crypto.subtle.decrypt:只准在 wasi-shim.ts
|
||||
if echo "$CONTENT" | grep -qE "crypto\.subtle\.decrypt"; then
|
||||
if [[ "$BASE" != "wasi-shim.ts" ]]; then
|
||||
block "2.2" \
|
||||
"AES-GCM 解密(crypto.subtle.decrypt)只准出現在 wasi-shim.ts 的 crypto_decrypt host function" \
|
||||
"把解密邏輯移到 wasi-shim.ts 的 host function;或讓 WASM 零件透過 u6u.crypto_decrypt 呼叫"
|
||||
fi
|
||||
fi
|
||||
|
||||
# crypto.subtle.sign with RSASSA:只准在 wasi-shim.ts
|
||||
if echo "$CONTENT" | grep -qE "crypto\.subtle\.sign.*RSASSA"; then
|
||||
if [[ "$BASE" != "wasi-shim.ts" ]]; then
|
||||
block "2.2" \
|
||||
"RS256 簽章只准出現在 wasi-shim.ts 的 crypto_sign_rs256 host function" \
|
||||
"把簽章移到 wasi-shim.ts;或讓 auth_service_account WASM 透過 u6u.crypto_sign_rs256 呼叫"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Template 展開:{{secret.X}} 或 {{runtime.X}} 屬於 WASM 職責
|
||||
# 例外:種子資料檔(*-seeds.ts)裡的 {{secret.X}} / {{runtime.X}} 是「資料字面值」而非 TS 展開邏輯,
|
||||
# 真正的展開仍在 WASM auth primitive 內完成。
|
||||
if [[ "$IS_SEED_DATA_FILE" == "false" ]] && echo "$CONTENT" | grep -qE "\{\{(secret|runtime)\." ; then
|
||||
block "2.2" \
|
||||
"Template 展開({{secret.X}} / {{runtime.X}})屬於 WASM auth primitive 職責" \
|
||||
"把這段邏輯改寫到 registry/components/auth_static_key/main.go(TinyGo)"
|
||||
fi
|
||||
|
||||
# Hard-code 的 BUILTIN_API_RECIPES / BUILTIN_CREDENTIALS_MAP 新增
|
||||
if echo "$CONTENT" | grep -qE "(BUILTIN_API_RECIPES|BUILTIN_CREDENTIALS_MAP)\s*[:=]"; then
|
||||
# 允許「把它設成空物件」或「刪除」,但不准新增實作
|
||||
if echo "$CONTENT" | grep -qE "BUILTIN_API_RECIPES.*=.*\{\s*[a-zA-Z]"; then
|
||||
block "2.2" \
|
||||
"禁止在 TS 裡新增 BUILTIN_API_RECIPES / BUILTIN_CREDENTIALS_MAP 實作" \
|
||||
"API 呼叫邏輯屬於各自的 WASM 零件(gmail.wasm / telegram.wasm 等),cypher-executor 只做 routing"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Hard-code API endpoint 實作
|
||||
# 例外:種子資料檔(*-seeds.ts)的 endpoint 字串(sheets.googleapis.com 等)是 recipe 的
|
||||
# 「資料欄位」(宣告這個 recipe 預設打哪),會被序列化寫進 RECIPES KV,**不是** TS 裡的呼叫實作;
|
||||
# 真正的 API 呼叫仍走原本零件 / http_request 路徑。
|
||||
HARDCODED_APIS=(
|
||||
"gmail\.googleapis\.com/gmail/v1/users/me/messages/send"
|
||||
"api\.telegram\.org/bot.*sendMessage"
|
||||
"sheets\.googleapis\.com/v4/spreadsheets"
|
||||
"notify-api\.line\.me/api/notify"
|
||||
)
|
||||
for PATTERN in "${HARDCODED_APIS[@]}"; do
|
||||
if echo "$CONTENT" | grep -qE "$PATTERN"; then
|
||||
# 允許 wasi-shim.ts(http_request host function proxy)+ 種子資料檔(*-seeds.ts)
|
||||
if [[ "$BASE" != "wasi-shim.ts" && "$IS_SEED_DATA_FILE" == "false" ]]; then
|
||||
block "2.2" \
|
||||
"禁止在 cypher-executor TS 裡 hard-code API endpoint(偵測到: $PATTERN)" \
|
||||
"把 API 呼叫移到對應的 WASM 零件(registry/components/gmail/main.go 等)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# exchangeGoogleJwt / 類似 token exchange function
|
||||
if echo "$CONTENT" | grep -qE "(exchangeGoogleJwt|exchangeServiceAccountJwt|signGoogleJwt)"; then
|
||||
if [[ "$BASE" != "wasi-shim.ts" ]]; then
|
||||
block "2.2" \
|
||||
"Token exchange 邏輯屬於 auth_service_account WASM 零件" \
|
||||
"改到 registry/components/auth_service_account/main.go"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 3.3:禁止建立 *-v2 / new-* / *-worker 類複製貼上目錄
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" =~ /(auth|credential|jwt|oauth|gmail|telegram|google-sheets|line-notify|http-request)[-_](v2|v3|new|worker|backup|temp)/ ]]; then
|
||||
block "3.3" \
|
||||
"禁止為同一零件建立平行目錄(v2/new/worker/backup 等)" \
|
||||
"直接修改 registry/components/<name>/main.go 即可;需要版本管理請用 git branch"
|
||||
fi
|
||||
if [[ "$FILE_PATH" =~ /new-(auth|credential|jwt|oauth|gmail|telegram)/ ]]; then
|
||||
block "3.3" \
|
||||
"禁止為同一零件建立 new-<name>/ 平行目錄" \
|
||||
"直接修改 registry/components/<name>/main.go"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 7.x:薄殼原則(能力長在 API,介面只暴露)— 見 .claude/rules/07-thin-shell.md
|
||||
# 只擋語法層可偵測的反例;recipe 層拼裝 / 藏在 helper 的邏輯需 code review 把關(07 §6)。
|
||||
# 範圍:薄殼介面 = cli/src/ 與 arcrun-mcp/src/(不含 cypher-executor,那是 API 本體)。
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
block7() {
|
||||
cat >&2 <<EOF
|
||||
❌ BLOCKED by arcrun CLAUDE rules
|
||||
違反項:7(薄殼原則)
|
||||
檔案:${FILE_PATH}
|
||||
原因:$1
|
||||
正確做法:$2
|
||||
參考:.claude/rules/07-thin-shell.md
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
if [[ ( "$FILE_PATH" == *"/cli/src/"* || "$FILE_PATH" == *"arcrun-mcp/src/"* ) \
|
||||
&& ( "$FILE_PATH" == *.ts || "$FILE_PATH" == *.js ) ]]; then
|
||||
|
||||
# 7.1 seed 邏輯不准寫在介面層(§4.1 反例)。seed 是 API 行為,由 API 端點完成。
|
||||
# 允許薄殼「呼叫一個 seed 端點」(單一 fetch),但不准在介面層定義 seed 編排函式。
|
||||
if echo "$CONTENT" | grep -qE "(function|const|async)\s+(seedApiRecipes|seedAuthRecipes|seedRecipes)\b"; then
|
||||
block7 \
|
||||
"seed 編排邏輯寫在介面層(CLI/MCP),這是壓測 §4.1 的根因——seed 應是 API 行為" \
|
||||
"把 seed 改成 API 端點(cypher-executor POST /init/seed 或部署完成時自動 seed),薄殼只呼叫該端點一次"
|
||||
fi
|
||||
|
||||
# 7.2 「先 update 失敗再 insert」/「先 GET 找有則 PATCH 無則 POST」這類 upsert 拼裝
|
||||
# 在同一函式內同時出現 update/patch 與 insert/post 的條件分支 = 把 API 缺的能力拼在介面層。
|
||||
if echo "$CONTENT" | grep -qiE "method:\s*['\"]PATCH['\"]" && echo "$CONTENT" | grep -qiE "method:\s*['\"]POST['\"]"; then
|
||||
if echo "$CONTENT" | grep -qiE "(upsert|找到.*PATCH|沒找到.*POST|exists.*update.*else.*insert|if.*found.*patch)"; then
|
||||
block7 \
|
||||
"在介面層拼裝 upsert(先找/PATCH 否則 POST)——這是能力,該在 API 提供單一 upsert 端點" \
|
||||
"請 cypher-executor 提供 upsert 端點(API 內部做 GET→PATCH/POST),薄殼只呼叫該端點"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 7.3 client 端「全部成功才做下一步」這類補 API 缺口的 gate(§4.1 deployFullyOk 反例)
|
||||
if echo "$CONTENT" | grep -qE "(deployFullyOk|全部成功.*test|/全部成功/)"; then
|
||||
block7 \
|
||||
"用 client 端字串判斷 gate 後續行為(deployFullyOk 類)——脆弱且把編排責任推給介面層" \
|
||||
"讓 API 保證後置狀態(部署成功即 recipe 就緒),薄殼不做這種 gate"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 4.3:禁止自行在 docs/3-specs/ 下建新 SDD 目錄
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" == *"docs/3-specs/"* ]]; then
|
||||
# 檢查是否在已知 SDD 目錄內
|
||||
KNOWN_SDDS=(
|
||||
"docs/3-specs/arcrun"
|
||||
"docs/3-specs/arcrun-core-mvp"
|
||||
"docs/3-specs/arcrun-platform-evolution"
|
||||
"docs/3-specs/component-registry-canon"
|
||||
"docs/3-specs/component-gatekeeping" # 2026-05-29 richblack 確認新建(Phase 3 把關)
|
||||
"docs/3-specs/data-exfil-warning" # 2026-05-30 richblack 確認新建(資料外流警示)
|
||||
"docs/3-specs/user-cc-harness" # 2026-06-03 richblack 確認新建(用戶 CC harness 安裝)
|
||||
"docs/3-specs/llm-interface" # 2026-05-16 richblack 確認新建(AI 操盤手介面)
|
||||
"docs/3-specs/recipe-system" # richblack 確認新建(recipe 系統)
|
||||
"docs/3-specs/resumable-workflow" # richblack 確認新建(可恢復工作流)
|
||||
"docs/3-specs/workflow-discovery" # 2026-06-27 總管 issue #8 交辦新建(工作流 description slot + search_workflow,北極星入口缺口)
|
||||
"docs/3-specs/thin-shell-alignment" # 2026-06-27 總管 issue #11 交辦新建(CLI/MCP 薄殼漂移全面盤點 + 防複發機制)
|
||||
)
|
||||
IN_KNOWN=false
|
||||
for K in "${KNOWN_SDDS[@]}"; do
|
||||
if [[ "$FILE_PATH" == *"$K/"* ]]; then
|
||||
IN_KNOWN=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ "$IN_KNOWN" == "false" ]]; then
|
||||
block "4.3" \
|
||||
"禁止自行在 docs/3-specs/ 下建立新的頂層 SDD 目錄" \
|
||||
"正確程序(見 .claude/rules/00-sdd-protocol.md「新增 SDD 的完整程序」):(1) 先與 richblack 確認要新建此 SDD;(2) 取得明確確認後,在本檔 KNOWN_SDDS 陣列加一行 \"docs/3-specs/<目錄名>\"(執行已授權任務的必要步驟);(3) 白名單放行後才寫 design/tasks。若是現有 SDD 的補充檔,改放已知 SDD 目錄下。"
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse hook 範本骨架 —— 專案自訂禁令(預設空殼,不攔任何東西)
|
||||
#
|
||||
# ⚠️ 定位(讀清楚再用):
|
||||
# 這支跟其他三支 hook 不同——它不是「裝上就生效的警察」,而是一個「按需手填的
|
||||
# 空插槽」。預設狀態下 FORBIDDEN_PATTERNS 是空的,它【不攔任何東西】。
|
||||
# 別誤以為裝了它就有保護——空殼 = 沒保護。
|
||||
#
|
||||
# 🤖 有 CC 在場的話,通常不需要這個範本:
|
||||
# 直接叫你的 CC「幫我寫一支 guard hook,禁止改 X」。CC 現寫的條件邏輯,
|
||||
# 表達力遠勝這裡的 glob FORBIDDEN_PATTERNS(例如「禁子 repo 的 code 但放行 .md」
|
||||
# 這種細緻規則,glob 寫不出來,CC 的條件判斷寫得出來)。
|
||||
# 這個範本只對「不靠 CC、想自己手動 DIY bash」的用戶有價值。
|
||||
#
|
||||
# 要啟用(手動 DIY 路線):
|
||||
# 1. 在下面 FORBIDDEN_PATTERNS 填禁改的路徑/檔名 pattern
|
||||
# 2. 到 .claude/settings.json 的 PreToolUse 加掛這支
|
||||
#
|
||||
# 掛在 PreToolUse(matcher: Write|Edit)。stdin 收 JSON:{ tool_name, tool_input:{ file_path } }
|
||||
# 命中禁令 → exit 2 擋。
|
||||
#
|
||||
# 誠實限制:只擋直接寫檔。bash 繞道、helper 間接改動擋不到。留痕可審 ≠ 技術防偽。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── 專案自訂:禁改的 pattern(一行一個,case glob 語法)──────
|
||||
# 範例(已註解,啟用前請改成自己的):
|
||||
# "*/db/schema.sql" # 禁手改 schema
|
||||
# "*/migrations/*" # migration 一旦建立不可改
|
||||
FORBIDDEN_PATTERNS=(
|
||||
# "*/your/protected/path/*"
|
||||
)
|
||||
|
||||
# 沒設任何禁令 → 空殼狀態,安靜放行。
|
||||
# (不在這裡 print——PreToolUse 每次 Write/Edit 都會跑,每次喊話會洗版。
|
||||
# 「這是空殼」的提醒改由 install.sh / update.sh 安裝時告知,那裡用戶一定看得到。)
|
||||
[ ${#FORBIDDEN_PATTERNS[@]} -eq 0 ] && exit 0
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
else
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
||||
fi
|
||||
|
||||
[ -z "$FILE_PATH" ] && exit 0
|
||||
|
||||
for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
|
||||
# shellcheck disable=SC2254
|
||||
case "$FILE_PATH" in
|
||||
$pattern)
|
||||
cat >&2 <<EOF
|
||||
🚫 專案禁令攔截:$FILE_PATH 命中禁改規則($pattern)。
|
||||
|
||||
這是本專案 .claude/hooks/pre-write-guard.sh 設定的硬底線。
|
||||
要動 → 先和負責人確認,並更新禁令設定。
|
||||
EOF
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
exit 0
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse hook — 動 code 前檢查有沒有對應 SDD
|
||||
# wishlist §2:把 /sdd-check 從「命令要人打」升級成「hook 自動攔」。
|
||||
#
|
||||
# 掛在 settings.json 的 PreToolUse(matcher: Write|Edit)。
|
||||
# stdin 收到 JSON:{ tool_name, tool_input: { file_path, ... } }
|
||||
# 行為:動到 code 檔(.ts/.go/...)但 system-dev/docs/3-specs/ 下沒有任何 SDD → 警告(exit 2 擋)。
|
||||
#
|
||||
# 誠實限制(抄 arcrun):只擋語法層明顯違規(直接寫 code 檔)。
|
||||
# 藏在 helper 裡、用 bash 繞道的改動擋不到。
|
||||
# 價值是「想跳過會被抓到 + 留痕可審」,不是技術防偽。絕不聲稱「不可能繞過」。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
# 解析 file_path。優先用 jq,沒有 jq 退回 grep(容錯)。
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
else
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
||||
fi
|
||||
|
||||
# 拿不到路徑 → 不擋(容錯,寧可放過也不誤殺)
|
||||
[ -z "$FILE_PATH" ] && exit 0
|
||||
|
||||
# 只管 code 檔。docs/markdown/設定檔等放行。
|
||||
case "$FILE_PATH" in
|
||||
*.ts|*.tsx|*.js|*.jsx|*.go|*.py|*.rs|*.java|*.rb|*.php|*.c|*.cpp|*.h|*.hpp|*.swift|*.kt) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# 改 SDD 自己 / 測試檔 → 放行
|
||||
case "$FILE_PATH" in
|
||||
*system-dev/docs/3-specs/*) exit 0 ;;
|
||||
*_test.*|*.test.*|*.spec.*|*/tests/*|*/test/*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# system-dev/docs/3-specs/ 下完全沒有 design.md → 攔
|
||||
SDD_COUNT=0
|
||||
if [ -d "system-dev/docs/3-specs" ]; then
|
||||
SDD_COUNT=$(find system-dev/docs/3-specs -name 'design.md' -not -path '*TEMPLATE*' 2>/dev/null | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
if [ "$SDD_COUNT" -eq 0 ]; then
|
||||
cat >&2 <<EOF
|
||||
🚫 SDD 協議攔截:要動 code 檔 ($FILE_PATH),但 system-dev/docs/3-specs/ 下找不到任何 SDD。
|
||||
|
||||
絕對鐵律:任何 code 變動前必須有對應 SDD(design.md)。
|
||||
|
||||
請先:
|
||||
1. 確認這個改動屬於哪個子系統
|
||||
2. 在 system-dev/docs/3-specs/[子系統]/ 建立 design.md(可用 /sdd-check 協助)
|
||||
3. 在回覆開頭宣告已讀 SDD + 對應 task
|
||||
|
||||
小修改(修 bug、改文字)若確定豁免,請明確說明範圍後由人放行。
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# 有 SDD:放行,但留痕提醒要宣告(stderr 警告,不擋)
|
||||
echo "📋 提醒:system-dev/docs/3-specs/ 下有 SDD。動手前請確認已讀對應 design.md 並在回覆宣告。" >&2
|
||||
exit 0
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/session-start-load-sdd.sh
|
||||
# arcrun SessionStart hook
|
||||
#
|
||||
# 職責:session 啟動時注入當前進度、絕對禁令、SDD 位置,避免 CC 靠記憶猜測
|
||||
# 輸出走 stdout,會注入到 CC 的 context
|
||||
|
||||
set -o pipefail
|
||||
|
||||
cat <<'EOF'
|
||||
============================================================
|
||||
🚨 arcrun 工作規範(SessionStart 注入)
|
||||
============================================================
|
||||
|
||||
📌 絕對禁令(違反會被 pre-write / pre-bash hook 直接 block):
|
||||
|
||||
1. registry/components/ 下禁止 TypeScript 實作零件
|
||||
→ 只能 TinyGo (main.go) 或 AssemblyScript,編譯成 .wasm
|
||||
|
||||
2. cypher-executor TS 裡禁止實作 credential/auth/JWT 業務邏輯
|
||||
→ crypto.subtle.decrypt / sign 只准出現在 wasi-shim.ts 的 host function
|
||||
→ {{secret.X}} template 展開屬於 WASM 零件職責
|
||||
→ 禁止 hard-code gmail/telegram/sheets API endpoint
|
||||
|
||||
3. 禁止新增 Service Binding 綁零件
|
||||
→ 零件串接走 HTTP URL(cypher binding = YAML 裡的 URL 清單)
|
||||
→ Cypher binding 不是 Cloudflare 的 binding 機制
|
||||
|
||||
4. 每個 WASM 零件 = 獨立 Worker = 公開 URL
|
||||
→ 不是從 R2 動態讀(平台零件已 bundle 進各自 Worker)
|
||||
→ R2 只用於 Phase 5 的用戶自製零件(未啟用)
|
||||
|
||||
📋 任何 code 變動前,必須:
|
||||
|
||||
1. 讀 docs/3-specs/arcrun/arcrun.md(總進度)
|
||||
2. 讀對應 task 的 SDD(design.md + tasks.md)
|
||||
3. 在回覆開頭宣告(格式見 .claude/rules/00-sdd-protocol.md):
|
||||
📋 已讀 SDD:<檔案清單>
|
||||
🎯 本次對應 task:<task 編號>
|
||||
📐 本次 task 的 SDD 規範摘要:<重點>
|
||||
🚧 執行範圍:修改/建立/刪除 <檔案>
|
||||
4. 每完成一個 task,立刻更新 tasks.md 的 [x],不批次
|
||||
|
||||
🔥 當前進行中 Phase:Credential Primitives TS → WASM
|
||||
|
||||
SDD:docs/3-specs/arcrun/credential-primitives-wasm/
|
||||
已完成:Phase 0.1-0.5(核心合併 + u6u-core 刪除)
|
||||
未完成硬前置:
|
||||
- Phase 0.6 wasi-shim 加 host functions(kv_get / crypto_decrypt / crypto_sign_rs256)
|
||||
- Phase 0.7 component-loader WASM runner 路徑
|
||||
未完成主要任務:
|
||||
- Phase 1 auth_static_key WASM 零件(TinyGo)
|
||||
- Phase 2 auth_service_account WASM 零件
|
||||
- Phase 3 刪除 cypher-executor 的三套違規 TS:
|
||||
* src/actions/credential-injector.ts(整檔刪)
|
||||
* src/lib/jwt-signer.ts(整檔刪)
|
||||
* src/lib/component-loader.ts 的 BUILTIN_API_RECIPES + BUILTIN_CREDENTIALS_MAP(整段刪)
|
||||
|
||||
⛔ 封測狀態:推遲(richblack 2026-04-19 決定)
|
||||
原因:違規 TS 未清,不封測。
|
||||
|
||||
📚 詳細規範:
|
||||
.claude/rules/00-sdd-protocol.md — SDD 協議
|
||||
.claude/rules/01-tech-stack.md — 技術棧硬限制
|
||||
.claude/rules/02-forbidden.md — 禁止清單(hook 強制)
|
||||
.claude/rules/03-component-architecture.md — 零件架構(R2/cypher binding/service binding 的正確定義)
|
||||
.claude/rules/04-current-progress.md — 當前進度 + SDD 索引
|
||||
.claude/rules/06-mindset.md — mindset(為什麼層):工作流是default/零件稀有/AI→工具/誠實不假綠
|
||||
|
||||
============================================================
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# SessionStart hook — 開 session 自動注入 status.md 重點
|
||||
# wishlist §1 主路徑:不靠 CC 自覺、不用人說,開 session 就把進度推到眼前。
|
||||
#
|
||||
# 掛在 settings.json 的 SessionStart(matcher: startup|resume|clear)。
|
||||
# stdout 會被當成 context 注入給 CC。
|
||||
#
|
||||
# 鐵律:status 是 point-in-time 快照,非即時狀態。
|
||||
# 這個 hook 只負責「把快照推到眼前」,核實快照是 CC 的責任——下面的提醒就是要它別盲信。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
STATUS_FILE="system-dev/wiki/status.md"
|
||||
PRINCIPLES_FILE="system-dev/wiki/principles.md"
|
||||
MISTAKES_FILE="system-dev/wiki/mistakes.md"
|
||||
|
||||
# ── 舊結構防呆(1.9.0 遷移第 2 層保險)──
|
||||
# 新版 wiki 收進 system-dev/。若偵測到舊位置 .claude/wiki/ 還在、但新位置沒 status,
|
||||
# 代表使用者升級了規則卻沒跑遷移(low-code 使用者常見)→ 出聲提示,讓 CC 當場可代為遷移,
|
||||
# 不要默默用不到新結構而出錯。
|
||||
if [ -d ".claude/wiki" ] && [ ! -f "$STATUS_FILE" ]; then
|
||||
echo "⚠️ 偵測到舊版 wiki 結構(.claude/wiki/),尚未遷移到 system-dev/wiki/。"
|
||||
echo " 請跑:bash system-dev/scripts/update.sh"
|
||||
echo " 或直接叫我(CC):「幫我把 wiki 遷移到 system-dev/」——我可以代為搬移。"
|
||||
echo " (未遷移時接關與 /wiki-init 會找錯位置。)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 三個 push 檔都沒有 → 安靜退出,不干擾還沒 /wiki-init 的專案
|
||||
if [ ! -f "$STATUS_FILE" ] && [ ! -f "$PRINCIPLES_FILE" ] && [ ! -f "$MISTAKES_FILE" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── push 1/3:principles(全文,行動前必服從)──
|
||||
# 放最前:原則是「會被遺忘的盲區」,要第一眼看見。全文成本低(一行一條、≤15 條)。
|
||||
if [ -f "$PRINCIPLES_FILE" ] && grep -q '^- ' "$PRINCIPLES_FILE" 2>/dev/null; then
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo "📐 設計原則(行動前必服從,來自 principles.md)"
|
||||
echo "════════════════════════════════════════════════"
|
||||
grep '^- ' "$PRINCIPLES_FILE" # 只注入原則條目本身,不含說明區
|
||||
# context 保護:原則應 ≤15 條(push 全文)。超過 → 提示該合併或下放成 card,不截斷(截斷會漏原則)。
|
||||
P_COUNT=$(grep -c '^- ' "$PRINCIPLES_FILE")
|
||||
if [ "$P_COUNT" -gt 15 ]; then
|
||||
echo ""
|
||||
echo "(⚠️ principles 已 ${P_COUNT} 條 > 15:請考慮合併相近原則、或把較細的下放成 card)"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ── push 2/3:status(全文,當前進度)──
|
||||
if [ -f "$STATUS_FILE" ]; then
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo "📍 接關:上次進度(來自 $STATUS_FILE 快照)"
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo ""
|
||||
cat "$STATUS_FILE"
|
||||
fi
|
||||
|
||||
# ── push 3/3:mistakes(標題清單 + 一行症狀,全文按需展開)──
|
||||
# 不全文注入(可能累積很多、含長 context)。只給「標題行」讓 CC 認出「我正撞到某條」→ 再去讀全文。
|
||||
if [ -f "$MISTAKES_FILE" ] && grep -q 'MISTAKE' "$MISTAKES_FILE" 2>/dev/null; then
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────"
|
||||
echo "⚠️ 已知踩坑(標題;撞到時讀 $MISTAKES_FILE 全文)"
|
||||
grep -E 'MISTAKE' "$MISTAKES_FILE" | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────"
|
||||
echo "⚠️ 以上是 point-in-time 快照,非即時狀態。"
|
||||
echo " 動手前先核實:快照寫的事項是否真的還沒做完?"
|
||||
echo " 需要完整脈絡(decisions / mistakes / SDD)→ 執行 /wiki-recall"
|
||||
echo "════════════════════════════════════════════════"
|
||||
|
||||
# ── 遷移殘留偵測(1.9.x):wiki 已搬到 system-dev/,但 CLAUDE.md 內容裡可能還寫舊路徑 ──
|
||||
# update.sh 鐵則「絕不碰 CLAUDE.md」(用戶資料),所以遷移不會自動改它內容裡的死引用。
|
||||
# 這裡只「偵測 + 提示 CC 代修」——由 CC(看得懂語義、不誤傷用戶自寫內容)來改,不讓腳本盲改。
|
||||
if [ -f "CLAUDE.md" ] && grep -q '\.claude/wiki' CLAUDE.md 2>/dev/null; then
|
||||
echo ""
|
||||
echo "🔧 偵測到 CLAUDE.md 內仍有舊路徑 \`.claude/wiki/\`(wiki 已遷移到 system-dev/wiki/)。"
|
||||
echo " 這些是遷移後的死引用,會讓你(CC)找錯位置。"
|
||||
echo " 請主動幫使用者把 CLAUDE.md 裡的 \`.claude/wiki/\` 改成 \`system-dev/wiki/\`、"
|
||||
echo " 工具 docs 路徑改成 \`system-dev/docs/\`(但 raw source 宣告的 \`docs/\` 保留不動)。"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/stop-check-sync.sh
|
||||
# arcrun Stop hook
|
||||
#
|
||||
# 職責:session 結束前檢查 code 變動是否有對應的 SDD / tasks.md 更新
|
||||
# 退出 code:不 block,只警告
|
||||
|
||||
set -o pipefail
|
||||
|
||||
# 檢查 docs/3-specs 下本次 session 是否有變動
|
||||
SPECS_DIFF=$(git -C "$(pwd)" status --porcelain -- 'docs/3-specs/' 2>/dev/null | head -20)
|
||||
CODE_DIFF=$(git -C "$(pwd)" status --porcelain -- '*.go' '*.ts' '*.tsx' '*.py' 'cypher-executor/' 'registry/' 'cli/' 2>/dev/null | head -20)
|
||||
|
||||
if [[ -n "$CODE_DIFF" && -z "$SPECS_DIFF" ]]; then
|
||||
cat >&2 <<EOF
|
||||
|
||||
⚠️ Stop hook 警告(by arcrun hook)
|
||||
|
||||
偵測到本 session 有程式碼變動,但 docs/3-specs/ 下的 SDD 文件沒有任何變動。
|
||||
|
||||
未 commit 的程式碼變動:
|
||||
$(echo "$CODE_DIFF" | head -10)
|
||||
|
||||
請在結束前確認:
|
||||
1. 對應的 tasks.md 是否已更新 [x]?
|
||||
2. 是否有架構變動需要更新 design.md?
|
||||
3. 是否有 SDD 範圍外的 change 未標記?
|
||||
|
||||
SDD 協議要求:code 和 SDD 必須同步更新。
|
||||
參考:.claude/rules/00-sdd-protocol.md
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
# 若有暫存的 tasks.md 變動,提醒 commit
|
||||
TASKS_DIFF=$(git -C "$(pwd)" status --porcelain -- 'docs/3-specs/**/tasks.md' 2>/dev/null | head -5)
|
||||
if [[ -n "$TASKS_DIFF" ]]; then
|
||||
cat >&2 <<EOF
|
||||
|
||||
📝 提醒:tasks.md 有未 commit 的變動
|
||||
$(echo "$TASKS_DIFF")
|
||||
記得在結束前 commit。
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse hook — 寫入 wiki 前掃機敏資訊(L3 硬攔截)
|
||||
#
|
||||
# 為什麼存在:wiki 的 ignore 規則(.wikiignore + 行內標記)是「協議層」,靠 CC 遵守。
|
||||
# 但密碼/金鑰/個資外洩是「不可逆」後果——只靠口頭約束太危險。
|
||||
# 這支 hook 是機械式底線:CC 真的把機敏資訊寫進 system-dev/wiki/ 的那一刻 → exit 2 擋下。
|
||||
#
|
||||
# 掛在 settings.json 的 PreToolUse(matcher: Write|Edit)。
|
||||
# stdin 收到 JSON:{ tool_name, tool_input: { file_path, content?, new_string? } }
|
||||
# 行為:只在目標路徑是 system-dev/wiki/** 時啟動,掃要寫入的內容,命中機敏特徵 → exit 2。
|
||||
#
|
||||
# 誠實限制(抄 sdd-guard):regex 偵測有偽陰/偽陽。
|
||||
# 擋的是「明顯特徵的機敏字串被自動抄進 wiki」,擋不了刻意混淆/編碼的繞道。
|
||||
# 價值是「意外外洩的機械底線 + 留痕可審」,不是技術防偽。絕不聲稱「不可能繞過」。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
# ── 解析 file_path 與要寫入的內容。優先 jq,無 jq 退回 grep(容錯)──────
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
# Write 用 content;Edit 用 new_string。兩個都抓,合起來掃。
|
||||
CONTENT=$(printf '%s' "$INPUT" | jq -r '[.tool_input.content, .tool_input.new_string] | map(select(. != null)) | join("\n")')
|
||||
else
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
||||
# 無 jq 時內容解析不可靠(JSON 跳脫),退回掃整包 INPUT,寧可多掃不漏掃
|
||||
CONTENT="$INPUT"
|
||||
fi
|
||||
|
||||
# 拿不到路徑 → 不擋(容錯,寧可放過也不誤殺)
|
||||
[ -z "$FILE_PATH" ] && exit 0
|
||||
|
||||
# 只管寫進 wiki 的動作。其他路徑放行(這支專責 wiki 洩漏,不是全域 secret scanner)
|
||||
case "$FILE_PATH" in
|
||||
*system-dev/wiki/*) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
[ -z "$CONTENT" ] && exit 0
|
||||
|
||||
# 行內豁免:若該段內容已被標記為刻意保留(例:範例文件要示範格式),略過該行
|
||||
# 標記:行尾加 # wiki-secret-ok (或 <!-- wiki-secret-ok -->)
|
||||
# 先把標記過的行抽掉再掃。
|
||||
SCAN=$(printf '%s' "$CONTENT" | grep -v -E 'wiki-secret-ok' || true)
|
||||
[ -z "$SCAN" ] && exit 0
|
||||
|
||||
# ── 機敏特徵 pattern。一行一類,命中即攔。──────────────────────────
|
||||
# 設計取捨:偏向高訊號 pattern(有明確結構的金鑰/標記),降低偽陽。
|
||||
# 純「password=xxx」這類也納入,因為那正是使用者最擔心的場景。
|
||||
HITS=""
|
||||
|
||||
check() {
|
||||
local label="$1" regex="$2"
|
||||
# -e 讓以 - 開頭的 pattern(如 PEM 的 -----BEGIN)不被當成選項。
|
||||
# grep 無命中回傳 1,在 set -e 下會中止 → 用 if 包住吸收掉。
|
||||
if printf '%s' "$SCAN" | grep -qiE -e "$regex"; then
|
||||
HITS="${HITS}
|
||||
• ${label}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 密碼/密鑰賦值(password = ..., secret: ..., api_key=...)
|
||||
check "密碼/密鑰賦值 (password/secret/api_key/token = ...)" \
|
||||
'(pass(word)?|secret|api[_-]?key|access[_-]?key|auth[_-]?token|priv(ate)?[_-]?key)[[:space:]]*[:=][[:space:]]*[^[:space:]<>"'"'"']{6,}'
|
||||
|
||||
# 私鑰 PEM 區塊
|
||||
check "私鑰檔內容 (BEGIN ... PRIVATE KEY)" \
|
||||
'-----BEGIN[[:space:]].*PRIVATE KEY-----'
|
||||
|
||||
# 常見雲端/服務金鑰前綴
|
||||
check "服務金鑰特徵 (AWS/GitHub/Slack/Google/Stripe 等)" \
|
||||
'(AKIA[0-9A-Z]{16}|gh[pousr]_[0-9A-Za-z]{20,}|xox[baprs]-[0-9A-Za-z-]{10,}|AIza[0-9A-Za-z_-]{20,}|sk_(live|test)_[0-9A-Za-z]{16,})'
|
||||
|
||||
# JWT
|
||||
check "JWT token" \
|
||||
'eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}'
|
||||
|
||||
# 連線字串內嵌帳密 (proto://user:pass@host)
|
||||
check "連線字串內嵌帳密 (proto://user:pass@host)" \
|
||||
'[a-z][a-z0-9+.-]*://[^[:space:]:/@]+:[^[:space:]:/@]+@'
|
||||
|
||||
# 台灣身分證字號(個資)。BSD/GNU grep 都支援 ERE,避免 \b(BSD 不認),改用字元類邊界。
|
||||
check "台灣身分證字號 (個資)" \
|
||||
'(^|[^A-Za-z0-9])[A-Z][12][0-9]{8}([^0-9]|$)'
|
||||
|
||||
# 信用卡號(個資,粗略 13-16 連續數字,可含空格/連字號分隔)。避免 PCRE,用 ERE 近似。
|
||||
check "疑似信用卡號 (個資)" \
|
||||
'(^|[^0-9])[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{0,4}([^0-9]|$)'
|
||||
|
||||
# Email 不擋(wiki 常需記聯絡人),手機號也不擋(偽陽太高)——刻意留白。
|
||||
|
||||
if [ -n "$HITS" ]; then
|
||||
cat >&2 <<EOF
|
||||
🚫 Wiki 機敏攔截:偵測到可能的機敏資訊要寫進 ${FILE_PATH}。
|
||||
|
||||
命中特徵:${HITS}
|
||||
|
||||
wiki 是會被 CC 反覆讀取、可能進版控的記憶空間。
|
||||
密碼 / 金鑰 / 個資寫進去 = 不可逆外洩風險。
|
||||
|
||||
請改成下列任一做法:
|
||||
1. 不要把機敏值寫進 wiki,改記「位置」(例:「DB 密碼放 1Password / .env,不入 wiki」)
|
||||
2. 確定是誤判(例:在示範格式)→ 該行尾加註記 wiki-secret-ok 後重寫
|
||||
3. 整個來源檔本就機敏 → 加進 system-dev/wiki/.wikiignore,別讓它被編入
|
||||
|
||||
誠實限制:本掃描靠特徵比對,有偽陽/偽陰,是「意外外洩的機械底線」而非保險箱。
|
||||
真正的密鑰本就不該進版控。
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user