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:
uncle6me-web
2026-07-03 07:13:15 +08:00
parent c830150da1
commit 5d00e71275
190 changed files with 39486 additions and 14 deletions
+31
View File
@@ -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
+106
View File
@@ -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.1Service Binding 新增警示
# ─────────────────────────────────────────────────────────────────────────────
# 偵測在 wrangler.toml 新增 [[services]] 的 echo/cat/sed 操作(非 100% 準確,但夠用)
if echo "$CMD" | grep -qE "echo.*\[\[services\]\].*>>"; then
block "3.1" \
"偵測到要在 wrangler.toml 新增 [[services]] binding" \
"零件串接一律走 HTTP URLcypher 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
+98
View File
@@ -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
+257
View File
@@ -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: contentEdit: new_stringMultiEdit: 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.1registry/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.2cypher-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.goTinyGo"
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.tshttp_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
+64
View File
@@ -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 加掛這支
#
# 掛在 PreToolUsematcher: 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
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# PreToolUse hook — 動 code 前檢查有沒有對應 SDD
# wishlist §2:把 /sdd-check 從「命令要人打」升級成「hook 自動攔」。
#
# 掛在 settings.json 的 PreToolUsematcher: 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 變動前必須有對應 SDDdesign.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
+73
View File
@@ -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 URLcypher 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 的 SDDdesign.md + tasks.md
3. 在回覆開頭宣告(格式見 .claude/rules/00-sdd-protocol.md):
📋 已讀 SDD<檔案清單>
🎯 本次對應 task<task 編號>
📐 本次 task 的 SDD 規範摘要:<重點>
🚧 執行範圍:修改/建立/刪除 <檔案>
4. 每完成一個 task,立刻更新 tasks.md 的 [x],不批次
🔥 當前進行中 PhaseCredential Primitives TS → WASM
SDDdocs/3-specs/arcrun/credential-primitives-wasm/
已完成:Phase 0.1-0.5(核心合併 + u6u-core 刪除)
未完成硬前置:
- Phase 0.6 wasi-shim 加 host functionskv_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
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
# SessionStart hook — 開 session 自動注入 status.md 重點
# wishlist §1 主路徑:不靠 CC 自覺、不用人說,開 session 就把進度推到眼前。
#
# 掛在 settings.json 的 SessionStartmatcher: 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/3principles(全文,行動前必服從)──
# 放最前:原則是「會被遺忘的盲區」,要第一眼看見。全文成本低(一行一條、≤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/3mistakes(標題清單 + 一行症狀,全文按需展開)──
# 不全文注入(可能累積很多、含長 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
+47
View File
@@ -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
+113
View File
@@ -0,0 +1,113 @@
#!/bin/bash
# PreToolUse hook — 寫入 wiki 前掃機敏資訊(L3 硬攔截)
#
# 為什麼存在:wiki 的 ignore 規則(.wikiignore + 行內標記)是「協議層」,靠 CC 遵守。
# 但密碼/金鑰/個資外洩是「不可逆」後果——只靠口頭約束太危險。
# 這支 hook 是機械式底線:CC 真的把機敏資訊寫進 system-dev/wiki/ 的那一刻 → exit 2 擋下。
#
# 掛在 settings.json 的 PreToolUsematcher: 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 用 contentEdit 用 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