Files
Arcrun/cli/harness/hooks/arcrun-guard.sh
T
uncle6me-web 46afea83c2 步驟1 最後一筆:install-harness 交付內容升級到現世代+世代閘
管道本來就是好的(install-harness 功能完整、冪等),**過時的是內容**:
harness skill(4066B)grep「意圖」「>>」= 0 命中,只講世界觀/別寫 Python
⇒ 新裝的封測者拿不到步驟 1 的核心教材(`>>` 意圖語法)。

■ 單一真相源:harness skill 改為建置期由 registry 複製
  build-harness-skill.mjs=head + registry/skills/write_intent_workflow.md 正文 + tail。
  選「建置期複製」的理由:npm files 只收 harness/,registry 不進套件;
  symlink 在 npm pack 與 Windows 不可靠。產物 commit 進 repo(npm 裝的是產物、不跑 build)。
  head/tail 是 harness 專屬(CLI 語境入口/acr 指令表/暴露同意/誠實鐵律),
  install-harness 的 copyTree 跳過 .head/.tail,不鋪進使用者專案。

■ 其餘三件逐份對照現世代事實後更新(過時的直接刪,不留死代碼)
  - CLAUDE.block.md:補 >> 意圖語法、not_found 兩條路、零件 vs recipe 分型、
    腹語術紅線、金鑰只拿名字
  - commands/arcrun.md:步驟改成「先寫意圖 → 丟去查 → 再寫 YAML」,補 acr search/validate
  - hooks/arcrun-guard.sh:**正路提示改為指向 arcrun-mindset Skill +意圖語法**
    (呼應「hook 沒提 skill 反而把 AI 導向 repo 文件」的教訓);
    新增 code 節點腹語術提醒,settings.fragment 補 Write|Edit|MultiEdit matcher

■ 世代閘(防再度脫節)
  check-harness-generation.mjs 檢查四件交付物的現世代指紋,缺指紋 exit 1,
  掛進 npm run build(prepublishOnly 因此也擋)。
  反向驗證:把 skill/CLAUDE.block 換回上一代 → 兩者都被擋下並逐條點名缺哪個指紋。

■ 驗收(考生 haiku/受測物=環境)
  乾淨臨時目錄 acr install-harness → 四件鋪好;重跑冪等(全檔 md5 不變、
  CLAUDE.md 66 行不變、hooks 條目 2 不變、arcrun 區塊仍 1 個)。
  haiku 只讀該目錄的 CLAUDE.md+SKILL.md(明令禁讀 ~/.claude、禁上網;
  兩份教材 md5 與大小均不同,可證非考本機那支)答十題
  → grade-step1.sh **10 / 10 通過**(判分器同時反向驗證仍會抓
  ON_TRUE/ON_FAILURE/第一節點非 input)。
  npm test 18/18、tsc 綠。

SDD:workflow-discovery/tasks.md 3.11

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 15:29:17 +08:00

105 lines
7.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# arcrun-guard.sh — 用戶專案的 arcrun PreToolUse guard(由 acr install-harness 裝進 .claude/hooks/
#
# 對象:在「用 arcrun 開發」的專案裡工作的 CC。擋它走歪(退回自寫 Python / 不用 recipe / 未經同意暴露)。
# 與 arcrun repo 開發版 hook 完全不同(那個擋的是開發 arcrun 本身)。
#
# 鐵則(user-cc-harness design §0.5):每次擋下/提醒都要給「具體怎麼做才對」的正路,不只說「不行」。
#
# 退出碼:0=允許(可附 stderr 提醒);2=硬擋(stderr 回給 CC)。
# 分級(design §4):多數用「提醒不硬擋」(避免誤殺正常 python);硬擋只留給「未經同意暴露資料」。
set -o pipefail
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
remind() {
# 提醒但放行(exit 0)。CC 看到 stderr,自己判斷是否真要繼續。
echo "💡 arcrun 提醒:$1" >&2
echo " 正路:$2" >&2
exit 0
}
block() {
echo "❌ arcrun guard 擋下:$1" >&2
echo " 正路:$2" >&2
exit 2
}
# ── 硬擋:未經人類同意的暴露動作(明確越界,mindset §5)──────────────
# 非互動環境下 CC 自己跑「部署對外 webhook / push recipe」= 替人類決定公開。
#
# 壓測 §9.5 修正(A6):舊版 grep "acr push" 連「在 echo/heredoc 裡**提到** push 字串」
# 都擋 → CC 連「把可貼上的指令印給使用者看」都做不到,反而違反 §0.5「擋下必指正路」鐵則。
# 故先判斷此命令是「展示指令」還是「真的執行 push」,只擋後者。
#
# 誠實限制(mindset §7):shell 命令層**無法 100% 乾淨區分**「執行」與「展示」
# (例:`echo "跑 acr push"` 與 `acr push` 對單純 grep 都命中)。以下是**啟發式**,
# 目的是降低誤殺、讓「印出正路指令」這條合法用途能通,不是完美防護。邊角情況可能漏判,
# 但漏判方向是「偏向放行展示」——而真正的暴露(執行)仍受 TTY/env 把關。
if echo "$CMD" | grep -qE "acr (push|recipe push)\b"; then
# 目標(壓測 §9.5):擋「真的執行 push」,放行「把 push 指令印給使用者看」。
#
# 作法:把 heredoc 主體(cat/印出用的多行文字)先抽掉,剩下的才是「實際會被 shell 執行的命令列」,
# 再看 push 是否出現在那裡的「命令位置」(行首 / ; & | && || 之後)。
# 執行 → 命中:`acr push x`、`cd f && acr push x`、`echo done; acr push x`
# 展示 → 不命中:`echo "跑 acr push x"`、`cat <<EOF\n acr push x \nEOF`push 在 heredoc 主體內)
#
# 抽掉 heredoc 主體:刪掉從 `<<EOF`(或任何 <<TOKEN)那行的下一行起、到單獨一行 TOKEN 為止的內容。
# 用 awk 做簡單狀態機(只處理最常見的 `<<TOKEN` / `<<-TOKEN`,不含複雜巢狀——夠用且誠實)。
EXEC_PART=$(printf '%s\n' "$CMD" | awk '
BEGIN{inhd=0}
{
if (inhd) { if ($0 ~ "^[[:space:]]*" term "[[:space:]]*$") { inhd=0 }; next }
line=$0
if (match(line, /<<-?[[:space:]]*"?'"'"'?[A-Za-z_][A-Za-z0-9_]*"?'"'"'?/)) {
t=substr(line, RSTART, RLENGTH); gsub(/^<<-?[[:space:]]*["'"'"']?/, "", t); gsub(/["'"'"']$/, "", t)
term=t; inhd=1
}
print line
}')
# 誠實限制(mindset §7):shell 層無法 100% 乾淨區分「執行」與「展示」。awk heredoc 抽取只覆蓋
# 常見形式(`<<EOF` / `<<-EOF` / 引號 token);`bash <<EOF ... acr push` 這種「heredoc 內容其實
# 會被執行」的偷渡,這裡會誤放——但它已被上游安全分類器擋下(壓測 §9.6 實證)。取捨上偏向
# 「放行展示」以保住 §0.5「擋下也要能印出正路指令」這條合法用途,不假裝此啟發式完美。
if echo "$EXEC_PART" | grep -qE "(^|[;&|][[:space:]]*)acr[[:space:]]+(push|recipe[[:space:]]+push)\b"; then
if [ ! -t 0 ] && [ "${ARCRUN_HUMAN_CONFIRMED:-}" != "1" ]; then
block "在非互動環境自動執行暴露動作(acr push / recipe push 會讓東西可被外部呼叫)" \
"交人類在終端機執行(真 TTY 會自動放行)。可把指令完整複製給使用者貼上自己跑:\`acr push <你的 workflow.yaml>\`。或使用者先在對話明示同意後親自於終端機執行。不要替使用者決定公開。(部署前的正路見 arcrun-mindset Skill:先 \`acr validate\`"
fi
fi
fi
# ── 提醒(不硬擋):退回自寫 Python/Node 一次性自動化 ──────────────────
# 「我先用 Python 測試」這類退回熟悉工具的傾向。python 不絕對錯(可能跑測試),故提醒不擋。
if echo "$CMD" | grep -qE "(^|[;&| ])(python3?|node)[ ]+[^ ]+\.(py|js|mjs|ts)\b"; then
# 排除明顯的測試 / 既有工具呼叫(pytest / npm test / jest 等)降低誤判
if ! echo "$CMD" | grep -qE "(pytest|jest|vitest|npm (run )?test|mocha|\btest_)"; then
remind "偵測到用 python/node 跑腳本。這專案用 Arcrun,串服務/自動化不要自刻一次性腳本。" \
"讀 arcrun-mindset Skill,先把需求寫成「意圖」串(\`input >> ON_SUCCESS >> <下一步>\`,邊只有 ON_SUCCESS 與「對每個 X」),再用 \`acr search <關鍵字>\` 查哪些零件/recipe 存在,最後才寫 workflow.yaml → \`acr validate\` → \`acr run\`。若這確實不是自動化(例如跑測試/別的工具),忽略本提醒。"
fi
fi
# ── 提醒(不硬擋):自寫打固定 API 的 script,而非 recipe ──────────────
if echo "$CMD" | grep -qE "(curl|fetch|requests\.(get|post)|axios).*https?://"; then
remind "偵測到自己打外部 API。Arcrun 裡「打固定 endpoint」應寫成 recipe,不自刻 HTTP 呼叫。" \
"先 \`acr recipe search <服務名>\` 看有沒有現成的;沒有就自己寫幾行 YAMLcanonical_id/endpoint/method/auth_service)用 \`acr recipe push\` 投稿,workflow 裡用 \`http_request\` 該 recipe 引用它。缺 recipe 就自己補,不必問人。寫法見 arcrun-mindset Skill。"
fi
# ── 提醒(不硬擋):把 code 節點當成缺零件的替代品(「腹語術」)──────────────
# 查詢回 not_found 就改寫成 code = 表面用 Arcrun、實際全寫 JS。這是現世代最常見的走歪。
if [ "$TOOL" = "Write" ] || [ "$TOOL" = "Edit" ] || [ "$TOOL" = "MultiEdit" ]; then
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""')
if echo "$FILE" | grep -qE '\.(ya?ml)$' && echo "$CONTENT" | grep -qE 'component:[[:space:]]*["'"'"']?code\b'; then
# 只在 code 內容看起來在做流程控制/取代零件時提醒(含 if/for/fetch),單純整形不吵
if echo "$CONTENT" | grep -qE '\b(if[[:space:]]*\(|for[[:space:]]*\(|fetch\(|await[[:space:]]+fetch)'; then
remind "workflow 裡的 \`code\` 節點含流程控制/HTTP 呼叫——這可能是「腹語術」(表面用 Arcrun、實際全寫 JS)。" \
"\`code\` 只用於局部整形(例:剝掉 LLM 回應的雜訊、切段落)。缺外部 API → 寫 recipe\`acr recipe push\`);缺計算能力 → 投稿零件 PR;要判斷 → 寫成獨立節點接 \`ON_SUCCESS\`(引擎沒有條件邊)。每個寫進 code 的 if 都是沒被測過的新 bug。見 arcrun-mindset Skill。"
fi
fi
fi
exit 0