From 558e80b4dae2f66e01762c2f3f1692515410b81f Mon Sep 17 00:00:00 2001 From: uncle6me-web Date: Sat, 27 Jun 2026 17:53:37 +0800 Subject: [PATCH] =?UTF-8?q?chore(wiki):=20wiki-init=20=E8=A3=9C=E9=AA=A8?= =?UTF-8?q?=E6=9E=B6=20+=20system-dev-template=20=E5=AE=89=E8=A3=9D/?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=85=B3=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wiki 已初始化過(push 檔活躍維護),本次補從沒建的 pull 層 + arcrun 化範本: - cards/decisions/ 14 張決策原子卡(含 gloss/實體/typed-edge 三元組): 從 decisions-summary 全量改寫 13 + 新增「薄殼規則晚於實作-MCP漂移是歷史債」1 - TAXONOMY 從 PKM 範本換成 arcrun 軸(子系統 零件架構/cypher/credential/recipe/kbdb/ 薄殼/部署/平台原則 + 形態 架構決策/踩坑/機制說明/禁令/案例經驗) - principles 填 13 條跨全局原則(從 rules/ + mindset 蒸餾) - INDEX 真實視圖(子系統角度 + 決策角度,指向 cards) - system-dev/scripts/ + scripts/ install/update 安裝腳本(template 接入) 純基建/文檔,無業務 code(功能 code 見前一 commit)。 raw source(docs/)0 異動、wiki 卡際連結無斷鏈。 Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/install.sh | 431 ++++++++++++++++ scripts/local-deploy.sh | 0 scripts/update.sh | 234 +++++++++ system-dev/VERSION | 1 + system-dev/scripts/install.sh | 472 ++++++++++++++++++ system-dev/scripts/update.sh | 332 ++++++++++++ system-dev/wiki/.wikiignore | 2 + system-dev/wiki/INDEX.md | 62 +++ system-dev/wiki/TAXONOMY.md | 49 ++ system-dev/wiki/cards/decisions/00-INDEX.md | 34 ++ .../cards/decisions/Haiku能驅動是設計目標.md | 39 ++ .../decisions/R2用途-平台零件不從R2讀.md | 33 ++ .../cards/decisions/Recipe-UUID市場模型.md | 44 ++ .../decisions/embedding是base-optional模組.md | 43 ++ .../same-zone-1042用flag解不用binding.md | 37 ++ .../self-hosted部署-共享install加指紋跳過.md | 43 ++ .../service-binding-vs-cypher-binding.md | 36 ++ .../decisions/不依賴CI-執行鏈路vs零件投稿.md | 36 ++ .../decisions/多worker-ENCRYPTION_KEY同步.md | 35 ++ .../decisions/工作流是default零件是例外.md | 33 ++ .../decisions/碰舊Mira需求先查頂層覆寫.md | 44 ++ .../decisions/自力救濟階梯-缺能力怎麼補.md | 48 ++ .../cards/decisions/薄殼原則-能力長在API.md | 36 ++ .../薄殼規則晚於實作-MCP漂移是歷史債.md | 37 ++ system-dev/wiki/decisions-summary.md | 368 ++++++++++++++ system-dev/wiki/mistakes.md | 402 +++++++++++++++ system-dev/wiki/principles.md | 26 + system-dev/wiki/status.md | 190 +++++++ 28 files changed, 3147 insertions(+) create mode 100755 scripts/install.sh mode change 100644 => 100755 scripts/local-deploy.sh create mode 100755 scripts/update.sh create mode 100644 system-dev/VERSION create mode 100755 system-dev/scripts/install.sh create mode 100755 system-dev/scripts/update.sh create mode 100644 system-dev/wiki/.wikiignore create mode 100644 system-dev/wiki/INDEX.md create mode 100644 system-dev/wiki/TAXONOMY.md create mode 100644 system-dev/wiki/cards/decisions/00-INDEX.md create mode 100644 system-dev/wiki/cards/decisions/Haiku能驅動是設計目標.md create mode 100644 system-dev/wiki/cards/decisions/R2用途-平台零件不從R2讀.md create mode 100644 system-dev/wiki/cards/decisions/Recipe-UUID市場模型.md create mode 100644 system-dev/wiki/cards/decisions/embedding是base-optional模組.md create mode 100644 system-dev/wiki/cards/decisions/same-zone-1042用flag解不用binding.md create mode 100644 system-dev/wiki/cards/decisions/self-hosted部署-共享install加指紋跳過.md create mode 100644 system-dev/wiki/cards/decisions/service-binding-vs-cypher-binding.md create mode 100644 system-dev/wiki/cards/decisions/不依賴CI-執行鏈路vs零件投稿.md create mode 100644 system-dev/wiki/cards/decisions/多worker-ENCRYPTION_KEY同步.md create mode 100644 system-dev/wiki/cards/decisions/工作流是default零件是例外.md create mode 100644 system-dev/wiki/cards/decisions/碰舊Mira需求先查頂層覆寫.md create mode 100644 system-dev/wiki/cards/decisions/自力救濟階梯-缺能力怎麼補.md create mode 100644 system-dev/wiki/cards/decisions/薄殼原則-能力長在API.md create mode 100644 system-dev/wiki/cards/decisions/薄殼規則晚於實作-MCP漂移是歷史債.md create mode 100644 system-dev/wiki/decisions-summary.md create mode 100644 system-dev/wiki/mistakes.md create mode 100644 system-dev/wiki/principles.md create mode 100644 system-dev/wiki/status.md diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..c66b0e5 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,431 @@ +#!/bin/bash +# system-dev-template installer +# 已有專案接入腳本——只建立缺少的東西,已有的一律不動。 +# +# 模組化安裝: +# --wiki 只裝 LLM Wiki(記憶系統 + 機敏防護) +# --sdd 只裝 SDD 系統(動 code 前必須有 design.md) +# --all 兩個都裝(預設) +# 無參數 互動式詢問 +# +# 為什麼留在同一個 repo 用參數選,而不是 fork: +# 使用者多半非專業,最怕「我要去哪個 repo」。一個入口 + 選單最友善。 +# 等未來功能多到 3+ 個再演進成「模板組合器」。模組邊界先在這裡劃好。 + +set -euo pipefail + +# ── i18n:依 locale 選語言,預設英文 ────────────────── +# 為什麼預設英文:curl | bash 常是 LANG=C,外國人預設就該看得懂; +# 台灣使用者 locale 多為 zh_TW,會自動切回繁中。 +case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in + zh*|*Hant*|*Hans*) IS_ZH="yes" ;; + *) IS_ZH="no" ;; +esac +# t "中文" "English" → 依語系印出對應字串 +t() { if [ "$IS_ZH" = "yes" ]; then printf '%s\n' "$1"; else printf '%s\n' "$2"; fi; } +# tn = 不換行版(給 prompt 用) +tn() { if [ "$IS_ZH" = "yes" ]; then printf '%s' "$1"; else printf '%s' "$2"; fi; } + +REPO_URL="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main/template" +CREATED=() +SKIPPED=() + +# ── 解析模組參數 ────────────────────────────────── +MODULE="" +for arg in "$@"; do + case "$arg" in + --wiki|--wiki-only) MODULE="wiki" ;; + --sdd|--sdd-only) MODULE="sdd" ;; + --all) MODULE="all" ;; + -h|--help) + if [ "$IS_ZH" = "yes" ]; then + cat <<'HELP' +用法:install.sh [--wiki | --sdd | --all] + --wiki 只裝 LLM Wiki(CC 記憶系統 + 機敏防護) + --sdd 只裝 SDD 系統(動 code 前強制要有設計文件) + --all 兩個都裝(預設) + 無參數 互動式詢問要裝哪個 +HELP + else + cat <<'HELP' +Usage: install.sh [--wiki | --sdd | --all] + --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 +HELP + fi + exit 0 ;; + esac +done + +echo "" +echo "🔧 system-dev-template installer" +echo "=================================" +t "只建立缺少的目錄和檔案,已有的不動。" \ + "Only creates missing dirs and files; never touches what already exists." +echo "" + +# ── 無參數 → 互動式詢問(給非專業使用者)────────── +if [ -z "$MODULE" ]; then + if [ -t 0 ]; then + t "要安裝哪一塊?" "Which part do you want to install?" + t " 1) LLM Wiki —— 讓 CC 記住決策、不重複犯錯(含機敏防護)" \ + " 1) LLM Wiki — let CC remember decisions and avoid repeating mistakes (with secret protection)" + t " 2) SDD —— 動 code 前強制先有設計文件" \ + " 2) SDD — require a design doc before touching code" + t " 3) 兩個都裝(推薦)" " 3) Install both (recommended)" + echo "" + tn "請輸入 1 / 2 / 3 [預設 3]:" "Enter 1 / 2 / 3 [default 3]: " + read -r choice || choice=3 + case "$choice" in + 1) MODULE="wiki" ;; + 2) MODULE="sdd" ;; + *) MODULE="all" ;; + esac + else + # 非互動環境(如 curl | bash 無 tty)→ 預設全裝 + MODULE="all" + fi +fi + +WANT_WIKI=false +WANT_SDD=false +case "$MODULE" in + wiki) WANT_WIKI=true ;; + sdd) WANT_SDD=true ;; + all) WANT_WIKI=true; WANT_SDD=true ;; +esac + +echo "" +t "📦 安裝模組:$MODULE" "📦 Module: $MODULE" +echo "" + +# ── 偵測 vault 類型 → 決定 raw source(原始文件)路徑 ────────── +# 為什麼:這個模板原本假設「原始文件在 docs/」,但 Logseq / Obsidian +# 這種 PKM vault 有自己的目錄慣例,整理時不能照 docs/ 那套搬動, +# 否則會破壞 vault 結構、讓筆記變不可讀。 +# 偵測結果寫進 CLAUDE.md,讓 CC 和未來的 Cowork skill 都知道 +# 「該讀/該整理哪裡」而不是亂動。 +# 必須在建立 CLAUDE.md 之前跑完。 +VAULT_TYPE="" +RAW_SOURCE="" +IS_VAULT="no" # 只有 logseq/obsidian 這種「筆記軟體 vault」才算 yes +if [ -d "logseq" ]; then + VAULT_TYPE="logseq" + RAW_SOURCE="pages/, journals/" + IS_VAULT="yes" +elif [ -d ".obsidian" ]; then + VAULT_TYPE="obsidian" + RAW_SOURCE="$(tn './ (整個 vault 根目錄的 .md)' './ (all .md under the vault root)')" + IS_VAULT="yes" +else + VAULT_TYPE="docs" + RAW_SOURCE="docs/" +fi +# 偵測到是筆記 vault → 出聲告訴使用者「我看到了,會小心、不破壞你的筆記結構」。 +# 不是筆記(一般開發案等)→ 不囉嗦,默默把 docs/ 當原始文件夾安裝完成。 +if [ "$IS_VAULT" = "yes" ]; then + t "🗂️ 偵測到 ${VAULT_TYPE} 筆記庫 → 原始文件:${RAW_SOURCE}" \ + "🗂️ Detected a ${VAULT_TYPE} note vault → raw source: ${RAW_SOURCE}" + t " (會保留你筆記軟體的目錄/檔名結構,不搬動、不改名)" \ + " (your note app's directory/file structure is preserved — nothing is moved or renamed)" + echo "" +fi + +# 把「raw source 宣告區塊」吐出來,給新建的 CLAUDE.md append 或 +# 給已存在的 CLAUDE.md 當手動補貼的提示。內容對 CC / Cowork 都是 +# 機器可讀的指令(明確路徑 + 不可破壞 vault 結構的約束)。 +# 寫進 CLAUDE.md 的 raw source 宣告區塊。給人也給 AI 看: +# 依 locale 只寫「一種語言」進 CLAUDE.md(雙語會讓每個 session 的 context 更滿)。 +emit_raw_source_block() { + local source_kind + if [ "$IS_ZH" = "yes" ]; then + if [ "$IS_VAULT" = "yes" ]; then source_kind="${VAULT_TYPE} 筆記庫" + else source_kind="一般專案(原始文件放 raw source 路徑)"; fi + cat < 安裝時偵測到的來源型態:**${source_kind}** +> CC 與 Cowork 整理/讀取「人寫的原始文件」時,**只在這裡找、只在這裡動**。 + +| 項目 | 值 | +|------|----| +| 來源型態 | \`${source_kind}\` | +| raw source | \`${RAW_SOURCE}\` | + +**約束(CC 與 Cowork 都必須遵守)** + +- 整理 wiki/知識時,原始文件**一律從上方 raw source 路徑讀取**,不要假設是 \`docs/\`。 +BLOCK + if [ "$IS_VAULT" = "yes" ]; then + cat < Source type detected at install time: **${source_kind}** +> When CC and Cowork curate/read human-written raw source, **look only here and act only here**. + +| Item | Value | +|------|-------| +| Source type | \`${source_kind}\` | +| raw source | \`${RAW_SOURCE}\` | + +**Constraints (both CC and Cowork must obey)** + +- When curating the wiki/knowledge, **always read raw source from the path above** — don't assume \`docs/\`. +BLOCK + if [ "$IS_VAULT" = "yes" ]; then + cat </dev/null || true + +# ── 依模組產生 settings.json 的 hooks 區塊 ──────── +# settings.json 因模組而異,不能直接下載單一靜態檔,改條件組裝。 +build_hooks_json() { + local session_hooks="" pretool_hooks="" + + if $WANT_WIKI; then + session_hooks='{ "type": "command", "command": ".claude/hooks/session-start-recall.sh" }' + fi + + # PreToolUse 依模組疊加 + local pt=() + $WANT_SDD && pt+=('{ "type": "command", "command": ".claude/hooks/sdd-guard.sh" }') + pt+=('{ "type": "command", "command": ".claude/hooks/pre-write-guard.sh" }') + $WANT_WIKI && pt+=('{ "type": "command", "command": ".claude/hooks/wiki-secret-scan.sh" }') + local IFS=, + pretool_hooks="${pt[*]}" + + printf '{\n "hooks": {\n' + if [ -n "$session_hooks" ]; then + printf ' "SessionStart": [\n { "matcher": "startup|resume|clear",\n "hooks": [ %s ] }\n ],\n' "$session_hooks" + fi + printf ' "PreToolUse": [\n { "matcher": "Write|Edit",\n "hooks": [ %s ] }\n ]\n' "$pretool_hooks" + printf ' }\n}\n' +} + +if [ ! -f ".claude/settings.json" ]; then + build_hooks_json > .claude/settings.json + CREATED+=(".claude/settings.json $(tn "(依 $MODULE 模組產生)" "(generated for module: $MODULE)")") +else + SKIPPED+=(".claude/settings.json $(tn '(已存在,請手動合併 hooks)' '(already exists — merge hooks manually)')") +fi + +# ── CLAUDE.md:只在完全不存在時建立 ──────────────── +# 新建時把偵測到的 raw source 宣告 append 進去(在建立的當下寫入, +# 不回頭改使用者既有的 CLAUDE.md,維持「已有不覆蓋」原則)。 +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})")") + fi +else + SKIPPED+=("CLAUDE.md $(tn '(已存在,請手動加入對應區塊)' '(already exists — add the block manually)')") +fi + +# ── 輸出結果 ────────────────────────────────────── +echo "" +t "✅ 建立了:" "✅ Created:" +# 注意:macOS bash 3.2 在 set -u 下展開「空陣列」會炸 unbound variable, +# 所以這裡先確認有元素才展開(SKIPPED 區塊在下方本來就有守,CREATED 補上)。 +if [ ${#CREATED[@]} -gt 0 ]; then + for item in "${CREATED[@]}"; do echo " + $item"; done +fi + +if [ ${#SKIPPED[@]} -gt 0 ]; then + echo "" + t "⚠️ 跳過(已存在):" "⚠️ Skipped (already exists):" + for item in "${SKIPPED[@]}"; do echo " - $item"; done +fi + +echo "" +echo "─────────────────────────────────" + +# CLAUDE.md 已存在 → 依模組提醒手動加區塊 +if [ -f "CLAUDE.md" ]; then + if ! grep -q "raw source" CLAUDE.md; then + echo "" + t "📌 CLAUDE.md 已存在但缺少 raw source 宣告。" \ + "📌 CLAUDE.md exists but lacks a raw source declaration." + t " 請手動把以下區塊貼進去,讓 CC 與 Cowork 知道原始文件在哪、不要亂動既有結構:" \ + " Paste the block below in so CC and Cowork know where the raw source is and won't disturb your structure:" + emit_raw_source_block | sed 's/^/ /' + fi + if $WANT_WIKI && ! grep -q "wiki/status.md" CLAUDE.md; then + echo "" + t "📌 CLAUDE.md 已存在但缺少 wiki 讀取順序,請手動加入:" \ + "📌 CLAUDE.md exists but lacks the wiki reading order — please add it manually:" + echo "" + if [ "$IS_ZH" = "yes" ]; then + cat <<'SNIP' + ## Wiki 讀取順序 + | 檔案 | 時機 | 用途 | + |------|------|------| + | `.claude/wiki/status.md` | session 開始第一件事 | 當前進度 | + | `.claude/wiki/mistakes.md` | 做新功能前 | 已知誤解 | + | `.claude/wiki/decisions-summary.md` | 設計判斷時 | 架構決策 | +SNIP + else + cat <<'SNIP' + ## Wiki reading order + | File | When | Purpose | + |------|------|---------| + | `.claude/wiki/status.md` | first thing at session start | current progress | + | `.claude/wiki/mistakes.md` | before building a new feature | known misconceptions | + | `.claude/wiki/decisions-summary.md` | when making design calls | architecture decisions | +SNIP + fi + fi + if $WANT_SDD && ! grep -q "docs/3-specs" CLAUDE.md; then + echo "" + t "📌 CLAUDE.md 已存在但缺少 SDD 鐵律,請手動加入:" \ + "📌 CLAUDE.md exists but lacks the SDD iron rule — please add it manually:" + echo "" + if [ "$IS_ZH" = "yes" ]; then + cat <<'SNIP' + ## 絕對鐵律 + 1. 任何 code 變動前必須有對應 SDD(docs/3-specs/[子系統]/design.md) + 找不到 → 停手問負責人,不要自行建立。 +SNIP + else + cat <<'SNIP' + ## Iron rule + 1. Every code change must have a matching SDD (docs/3-specs/[subsystem]/design.md). + Not found → stop and ask the owner; do not create one on your own. +SNIP + fi + fi +fi + +# settings.json 已存在 → 依模組提醒要合併哪些 hook +if [ -f ".claude/settings.json" ]; then + MISSING_HOOKS=() + $WANT_WIKI && ! grep -q "session-start-recall.sh" .claude/settings.json && MISSING_HOOKS+=("SessionStart: session-start-recall.sh") + $WANT_WIKI && ! grep -q "wiki-secret-scan.sh" .claude/settings.json && MISSING_HOOKS+=("PreToolUse(Write|Edit): wiki-secret-scan.sh") + $WANT_SDD && ! grep -q "sdd-guard.sh" .claude/settings.json && MISSING_HOOKS+=("PreToolUse(Write|Edit): sdd-guard.sh") + if [ ${#MISSING_HOOKS[@]} -gt 0 ]; then + echo "" + t "📌 .claude/settings.json 已存在,請手動把以下 hooks 合併進去(保留既有設定):" \ + "📌 .claude/settings.json exists — merge the hooks below in manually (keep your existing settings):" + for h in "${MISSING_HOOKS[@]}"; do echo " • $h"; done + fi +fi + +# pre-write-guard 是空殼,提醒它預設不攔(避免「以為有保護其實沒有」的安全錯覺) +echo "" +t "ℹ️ .claude/hooks/pre-write-guard.sh 是「按需手填的空插槽」,預設不攔任何東西。" \ + "ℹ️ .claude/hooks/pre-write-guard.sh is an empty slot to fill on demand — by default it blocks nothing." +t " 需要專案禁令?最簡單是叫你的 CC 寫一支貼合的 guard hook(比範本表達力強);" \ + " Need project-specific bans? Easiest is to ask your CC to write a tailored guard hook (more expressive than the template);" +t " 或自己填 FORBIDDEN_PATTERNS 並到 settings.json 掛上才會生效。" \ + " or fill in FORBIDDEN_PATTERNS yourself and wire it into settings.json to take effect." + +echo "" +t "🚀 下一步:" "🚀 Next steps:" +if $WANT_WIKI; then + t " 在 Claude Code 對話裡執行 /wiki-init" \ + " In a Claude Code conversation, run /wiki-init" + t " CC 會掃描現有文件、套用 .wikiignore、建立 wiki。" \ + " CC will scan your existing docs, apply .wikiignore, and build the wiki." +fi +if $WANT_SDD; then + t " 動 code 前先在 docs/3-specs/[子系統]/ 建 design.md(可用 /sdd-check 協助)" \ + " Before touching code, create design.md under docs/3-specs/[subsystem]/ (use /sdd-check to help)" +fi +t " GitHub issue:CC 可直接 /issue-handle 讀回自己 repo 的 issue(禁自動輪詢)" \ + " GitHub issues: CC can use /issue-handle to read issues from its own repo (no auto-polling)" +echo "" diff --git a/scripts/local-deploy.sh b/scripts/local-deploy.sh old mode 100644 new mode 100755 diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..4961c2b --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,234 @@ +#!/bin/bash +# system-dev-template updater +# 已安裝舊版的人,一鍵更新到新版。 +# +# 核心安全原則:只覆蓋「模板/邏輯檔」,絕不碰「使用者資料檔」。 +# ✅ 可覆蓋:hooks/*.sh、commands/*.md、TEMPLATE-*、wiki/INDEX.md +# ——這些由模板維護,使用者不會手改,新版直接換掉。 +# 🔒 絕不碰:wiki/status.md、mistakes.md、decisions-summary.md、TAXONOMY.md、.wikiignore、 +# settings.json、CLAUDE.md +# ——這些是使用者自己填的內容,覆蓋=清空他的記憶與設定。 +# +# 「第一次更新」的雞生蛋問題: +# 舊版本機沒有 update.sh。所以第一次靠 README 那行 curl 從遠端抓這支腳本來跑。 +# 跑完它會把自己也更新進 scripts/update.sh,之後就能直接跑本機的 `bash scripts/update.sh`。 + +set -euo pipefail + +# ── i18n:依 locale 選語言,預設英文(curl | bash 常為 LANG=C)── +case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in + zh*|*Hant*|*Hans*) IS_ZH="yes" ;; + *) IS_ZH="no" ;; +esac +t() { if [ "$IS_ZH" = "yes" ]; then printf '%s\n' "$1"; else printf '%s\n' "$2"; fi; } +tn() { if [ "$IS_ZH" = "yes" ]; then printf '%s' "$1"; else printf '%s' "$2"; fi; } + +REPO_RAW="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main" +TEMPLATE_URL="$REPO_RAW/template" + +UPDATED=() +KEPT=() +NEW=() +TEMPLATED=() + +# ── 版本比對:先看本機 vs 遠端,給使用者「值不值得更新」的判斷 ── +LOCAL_VER="$(tn '(未知)' '(unknown)')" +[ -f ".claude/VERSION" ] && LOCAL_VER="$(tr -d '[:space:]' < .claude/VERSION)" +REMOTE_VER="$(curl -sSL "$TEMPLATE_URL/.claude/VERSION" 2>/dev/null | tr -d '[:space:]' || echo '')" + +echo "" +echo "🔄 system-dev-template updater" +echo "=================================" +t " 本機版本:${LOCAL_VER}" " Local version: ${LOCAL_VER}" +t " 最新版本:${REMOTE_VER:-取不到(檢查網路)}" \ + " Latest version: ${REMOTE_VER:-unavailable (check network)}" +echo "" + +if [ -z "$REMOTE_VER" ]; then + t "❌ 取不到遠端版本,可能是網路問題。請稍後再試。" \ + "❌ Could not fetch the remote version (likely a network issue). Please try again later." + exit 1 +fi + +if [ "$LOCAL_VER" = "$REMOTE_VER" ]; then + t "✅ 已是最新版(${LOCAL_VER}),不需更新。" \ + "✅ Already up to date (${LOCAL_VER}), nothing to update." + t " (仍會同步模板邏輯檔,確保 hooks/commands 與最新一致。)" \ + " (Template logic files will still be synced to keep hooks/commands in line with the latest.)" + echo "" +fi + +# ── 工具函式 ─────────────────────────────────────── +# 覆蓋更新:模板/邏輯檔,無條件抓最新版蓋掉。 +update_file() { + local dest="$1" src="$2" + mkdir -p "$(dirname "$dest")" + if [ -f "$dest" ]; then + if curl -sSL "$src" -o "$dest.tmp" 2>/dev/null && [ -s "$dest.tmp" ]; then + if cmp -s "$dest" "$dest.tmp"; then + rm -f "$dest.tmp" # 內容相同,不算更新 + else + mv "$dest.tmp" "$dest" + UPDATED+=("$dest") + fi + else + rm -f "$dest.tmp" + t " ⚠️ 抓取失敗,保留原檔:$dest" " ⚠️ Download failed, keeping the original: $dest" + fi + else + if curl -sSL "$src" -o "$dest" 2>/dev/null && [ -s "$dest" ]; then + NEW+=("$dest") # 新功能:舊版沒有的檔 + else + rm -f "$dest" + t " ⚠️ 抓取失敗:$dest" " ⚠️ Download failed: $dest" + fi + fi +} + +# 保留:使用者資料檔,只記錄「有保留」,永遠不動。 +keep_file() { + [ -f "$1" ] && KEPT+=("$1") || true +} + +# 客製檔:使用者一定會手填內容(如 pre-write-guard.sh)。 +# - 已存在 → 絕不覆蓋,但把最新模板版抓到 <檔名>.template.sh 旁邊,供使用者自行 diff 採納。 +# - 不存在 → 視同新檔,直接抓本體(第一次安裝才會走這條)。 +keep_with_template() { + local dest="$1" src="$2" + if [ -f "$dest" ]; then + KEPT+=("$dest") + local tmpl="${dest%.sh}.template.sh" + if curl -sSL "$src" -o "$tmpl.tmp" 2>/dev/null && [ -s "$tmpl.tmp" ]; then + if [ -f "$tmpl" ] && cmp -s "$tmpl" "$tmpl.tmp"; then + rm -f "$tmpl.tmp" # 模板版沒變,不重複提示 + else + mv "$tmpl.tmp" "$tmpl" + TEMPLATED+=("$tmpl") + fi + else + rm -f "$tmpl.tmp" + fi + else + update_file "$dest" "$src" # 還沒裝過 → 當新檔處理 + fi +} + +# ── 偵測已安裝哪些模組(依現有檔案判斷,更新只動已裝的)── +HAS_WIKI=false +HAS_SDD=false +[ -d ".claude/wiki" ] && HAS_WIKI=true +if [ -f ".claude/hooks/sdd-guard.sh" ] || [ -d "docs/3-specs/TEMPLATE-sdd" ]; then HAS_SDD=true; fi + +t "📦 偵測到已安裝模組:" "📦 Detected installed modules:" +$HAS_WIKI && echo " • LLM Wiki" +$HAS_SDD && echo " • SDD" +{ $HAS_WIKI || $HAS_SDD; } || \ + t " (未偵測到任何模組——這裡可能還沒安裝,請改跑 install.sh)" \ + " (No modules detected — nothing installed here yet; run install.sh instead.)" +echo "" + +# ── 客製檔:使用者手填的 guardrail,永不覆蓋(issue #3)── +# pre-write-guard.sh 的定位是「空白客製模板,使用者沒配置前不提供保護」(CHANGELOG 1.2.0)。 +# 下游通常已塞滿自己的 enforcement,直接覆蓋=無聲關掉整套 guardrail。 +# 改為:保留原檔不動,新版範本另存 pre-write-guard.template.sh,由使用者自行 diff 採納。 +keep_with_template ".claude/hooks/pre-write-guard.sh" "$TEMPLATE_URL/.claude/hooks/pre-write-guard.sh" + +# ── 模板/邏輯檔:覆蓋更新 ────────────────────────── +# 共用 hook 與指引 +update_file ".claude/commands/issue-handle.md" "$TEMPLATE_URL/.claude/commands/issue-handle.md" +update_file ".claude/VERSION" "$TEMPLATE_URL/.claude/VERSION" + +if $HAS_WIKI; then + # wiki 的「邏輯檔」:導航與 hooks,可覆蓋 + update_file ".claude/wiki/INDEX.md" "$TEMPLATE_URL/.claude/wiki/INDEX.md" + update_file ".claude/hooks/session-start-recall.sh" "$TEMPLATE_URL/.claude/hooks/session-start-recall.sh" + update_file ".claude/hooks/wiki-secret-scan.sh" "$TEMPLATE_URL/.claude/hooks/wiki-secret-scan.sh" + update_file ".claude/commands/wiki-init.md" "$TEMPLATE_URL/.claude/commands/wiki-init.md" + update_file ".claude/commands/wiki-capture.md" "$TEMPLATE_URL/.claude/commands/wiki-capture.md" + update_file ".claude/commands/wiki-update.md" "$TEMPLATE_URL/.claude/commands/wiki-update.md" + update_file ".claude/commands/wiki-recall.md" "$TEMPLATE_URL/.claude/commands/wiki-recall.md" + # Cowork(claude.ai)的 wiki 整理 skill:規則檔,可覆蓋 + update_file "docs/SKILL.md" "$TEMPLATE_URL/docs/SKILL.md" + + # wiki 的「使用者資料」:絕不碰 + keep_file ".claude/wiki/status.md" + keep_file ".claude/wiki/mistakes.md" + keep_file ".claude/wiki/decisions-summary.md" + keep_file ".claude/wiki/TAXONOMY.md" + keep_file ".claude/wiki/.wikiignore" +fi + +if $HAS_SDD; then + # SDD 範本與 hook:可覆蓋 + update_file "docs/3-specs/TEMPLATE-sdd/design.md" "$TEMPLATE_URL/docs/3-specs/TEMPLATE-sdd/design.md" + update_file "docs/3-specs/TEMPLATE-sdd/tasks.md" "$TEMPLATE_URL/docs/3-specs/TEMPLATE-sdd/tasks.md" + update_file "docs/2-architecture/decisions/TEMPLATE-adr.md" "$TEMPLATE_URL/docs/2-architecture/decisions/TEMPLATE-adr.md" + update_file ".claude/commands/sdd-check.md" "$TEMPLATE_URL/.claude/commands/sdd-check.md" + update_file ".claude/hooks/sdd-guard.sh" "$TEMPLATE_URL/.claude/hooks/sdd-guard.sh" +fi + +# ── 自我更新:把最新的 update.sh 也抓下來(含 install.sh)── +# 這兩支在 main/scripts/ 下,不在 template/。 +update_file "scripts/update.sh" "$REPO_RAW/scripts/update.sh" +update_file "scripts/install.sh" "$REPO_RAW/scripts/install.sh" + +chmod +x .claude/hooks/*.sh scripts/*.sh 2>/dev/null || true + +# ── 使用者資料檔:絕不碰,但提醒「設定可能有新欄位要手動補」── +keep_file ".claude/settings.json" +keep_file "CLAUDE.md" + +# ── 結果輸出 ─────────────────────────────────────── +echo "" +echo "─────────────────────────────────" +if [ ${#NEW[@]} -gt 0 ]; then + echo "" + t "🆕 新功能(舊版沒有,已加入):" "🆕 New features (absent in the old version, now added):" + for f in "${NEW[@]}"; do echo " + $f"; done +fi +if [ ${#UPDATED[@]} -gt 0 ]; then + echo "" + t "⬆️ 已更新(覆蓋成新版):" "⬆️ Updated (overwritten with the new version):" + for f in "${UPDATED[@]}"; do echo " ~ $f"; done +fi +if [ ${#NEW[@]} -eq 0 ] && [ ${#UPDATED[@]} -eq 0 ]; then + echo "" + t "✨ 模板邏輯檔已全部最新,無需變動。" \ + "✨ All template logic files are already up to date — no changes needed." +fi +if [ ${#KEPT[@]} -gt 0 ]; then + echo "" + t "🔒 完整保留(你的內容/設定,從未碰過):" \ + "🔒 Fully preserved (your content/settings, never touched):" + for f in "${KEPT[@]}"; do echo " = $f"; done +fi +if [ ${#TEMPLATED[@]} -gt 0 ]; then + echo "" + t "📋 客製檔有新版範本(你的原檔沒動,新版另存旁邊,請自行 diff 採納):" \ + "📋 Custom files have a new template version (your original is untouched; the new one is saved alongside — diff and adopt as you like):" + for f in "${TEMPLATED[@]}"; do + echo " → $f" + t " 比對:diff \"${f%.template.sh}.sh\" \"$f\"" \ + " compare: diff \"${f%.template.sh}.sh\" \"$f\"" + done +fi + +# ── settings.json 提醒:新模組 hook 可能要手動補 ── +if [ -f ".claude/settings.json" ]; then + MISSING=() + $HAS_WIKI && ! grep -q "session-start-recall.sh" .claude/settings.json && MISSING+=("SessionStart: session-start-recall.sh") + $HAS_WIKI && ! grep -q "wiki-secret-scan.sh" .claude/settings.json && MISSING+=("PreToolUse(Write|Edit): wiki-secret-scan.sh") + $HAS_SDD && ! grep -q "sdd-guard.sh" .claude/settings.json && MISSING+=("PreToolUse(Write|Edit): sdd-guard.sh") + if [ ${#MISSING[@]} -gt 0 ]; then + echo "" + t "📌 settings.json 是你的設定(沒動),但偵測到缺以下 hook,請手動補上:" \ + "📌 settings.json is yours (untouched), but these hooks are missing — please add them manually:" + for h in "${MISSING[@]}"; do echo " • $h"; done + fi +fi + +echo "" +t "🚀 更新完成:${LOCAL_VER} → ${REMOTE_VER}" "🚀 Update complete: ${LOCAL_VER} → ${REMOTE_VER}" +t " 下次更新直接跑:bash scripts/update.sh" " Next time, just run: bash scripts/update.sh" +t " 改了什麼看:CHANGELOG.md" " See what changed: CHANGELOG.md" +echo "" diff --git a/system-dev/VERSION b/system-dev/VERSION new file mode 100644 index 0000000..32bd932 --- /dev/null +++ b/system-dev/VERSION @@ -0,0 +1 @@ +1.12.0 \ No newline at end of file diff --git a/system-dev/scripts/install.sh b/system-dev/scripts/install.sh new file mode 100755 index 0000000..55a17ca --- /dev/null +++ b/system-dev/scripts/install.sh @@ -0,0 +1,472 @@ +#!/bin/bash +# system-dev-template installer +# 已有專案接入腳本——只建立缺少的東西,已有的一律不動。 +# +# 模組化安裝: +# --wiki 只裝 LLM Wiki(記憶系統 + 機敏防護) +# --sdd 只裝 SDD 系統(動 code 前必須有 design.md) +# --all 兩個都裝(預設) +# 無參數 互動式詢問 +# +# 為什麼留在同一個 repo 用參數選,而不是 fork: +# 使用者多半非專業,最怕「我要去哪個 repo」。一個入口 + 選單最友善。 +# 等未來功能多到 3+ 個再演進成「模板組合器」。模組邊界先在這裡劃好。 + +set -euo pipefail + +# ── i18n:依 locale 選語言,預設英文 ────────────────── +# 為什麼預設英文:curl | bash 常是 LANG=C,外國人預設就該看得懂; +# 台灣使用者 locale 多為 zh_TW,會自動切回繁中。 +case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in + zh*|*Hant*|*Hans*) IS_ZH="yes" ;; + *) IS_ZH="no" ;; +esac +# t "中文" "English" → 依語系印出對應字串 +t() { if [ "$IS_ZH" = "yes" ]; then printf '%s\n' "$1"; else printf '%s\n' "$2"; fi; } +# tn = 不換行版(給 prompt 用) +tn() { if [ "$IS_ZH" = "yes" ]; then printf '%s' "$1"; else printf '%s' "$2"; fi; } + +REPO_URL="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main/template" +# install.sh / update.sh 住在 main/scripts/(不在 template/)。 +SCRIPTS_URL="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main/scripts" +CREATED=() +SKIPPED=() + +# ── 解析模組參數 ────────────────────────────────── +MODULE="" +for arg in "$@"; do + case "$arg" in + --wiki|--wiki-only) MODULE="wiki" ;; + --sdd|--sdd-only) MODULE="sdd" ;; + --all) MODULE="all" ;; + -h|--help) + if [ "$IS_ZH" = "yes" ]; then + cat <<'HELP' +用法:install.sh [--wiki | --sdd | --all] + --wiki 只裝 LLM Wiki(CC 記憶系統 + 機敏防護) + --sdd 只裝 SDD 系統(動 code 前強制要有設計文件) + --all 兩個都裝(預設) + 無參數 互動式詢問要裝哪個 +HELP + else + cat <<'HELP' +Usage: install.sh [--wiki | --sdd | --all] + --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 +HELP + fi + exit 0 ;; + esac +done + +echo "" +echo "🔧 system-dev-template installer" +echo "=================================" +t "只建立缺少的目錄和檔案,已有的不動。" \ + "Only creates missing dirs and files; never touches what already exists." +echo "" + +# ── 無參數 → 互動式詢問(給非專業使用者)────────── +if [ -z "$MODULE" ]; then + if [ -t 0 ]; then + t "要安裝哪一塊?" "Which part do you want to install?" + t " 1) LLM Wiki —— 讓 CC 記住決策、不重複犯錯(含機敏防護)" \ + " 1) LLM Wiki — let CC remember decisions and avoid repeating mistakes (with secret protection)" + t " 2) SDD —— 動 code 前強制先有設計文件" \ + " 2) SDD — require a design doc before touching code" + t " 3) 兩個都裝(推薦)" " 3) Install both (recommended)" + echo "" + tn "請輸入 1 / 2 / 3 [預設 3]:" "Enter 1 / 2 / 3 [default 3]: " + read -r choice || choice=3 + case "$choice" in + 1) MODULE="wiki" ;; + 2) MODULE="sdd" ;; + *) MODULE="all" ;; + esac + else + # 非互動環境(如 curl | bash 無 tty)→ 預設全裝 + MODULE="all" + fi +fi + +WANT_WIKI=false +WANT_SDD=false +case "$MODULE" in + wiki) WANT_WIKI=true ;; + sdd) WANT_SDD=true ;; + all) WANT_WIKI=true; WANT_SDD=true ;; +esac + +echo "" +t "📦 安裝模組:$MODULE" "📦 Module: $MODULE" +echo "" + +# ── 重複安裝防呆(1.10.1):install 只管「全新安裝」,一切後續歸 update ── +# 判準是「裝過沒」,不分新版舊版: +# - 新結構 system-dev/ 已存在,或 +# - 舊結構 .claude/wiki/ 或 .claude/VERSION 存在(裝過舊版、待遷移) +# 裝過了還跑 install → 會重複建範本、甚至跟真資料並存(先 install 建空殼,遷移就被擋)。 +# 正解:偵測到裝過 → 不動任何東西,導去 update(更新/遷移/補新檔都由它處理)。 +if [ -d "system-dev" ] || [ -d ".claude/wiki" ] || [ -f ".claude/VERSION" ]; then + t "🛑 偵測到這個專案已經安裝過 system-dev-template。" \ + "🛑 system-dev-template is already installed in this project." + t " 後續的更新、遷移、補新檔,一律由「更新腳本」處理(不要重跑 install):" \ + " All updates, migrations, and new-file additions are handled by the UPDATER (don't re-run install):" + echo "" + echo " curl -sSL https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main/scripts/update.sh | bash" + echo "" + t " (重跑 install 可能建出空白範本、跟你的真資料並存,故在此停止。)" \ + " (Re-running install could create empty templates alongside your real data, so it stops here.)" + exit 0 +fi + +# ── 偵測 vault 類型 → 決定 raw source(原始文件)路徑 ────────── +# 為什麼:這個模板原本假設「原始文件在 docs/」,但 Logseq / Obsidian +# 這種 PKM vault 有自己的目錄慣例,整理時不能照 docs/ 那套搬動, +# 否則會破壞 vault 結構、讓筆記變不可讀。 +# 偵測結果寫進 CLAUDE.md,讓 CC 和未來的 Cowork skill 都知道 +# 「該讀/該整理哪裡」而不是亂動。 +# 必須在建立 CLAUDE.md 之前跑完。 +VAULT_TYPE="" +RAW_SOURCE="" +IS_VAULT="no" # 只有 logseq/obsidian 這種「筆記軟體 vault」才算 yes +if [ -d "logseq" ]; then + VAULT_TYPE="logseq" + RAW_SOURCE="pages/, journals/" + IS_VAULT="yes" +elif [ -d ".obsidian" ]; then + VAULT_TYPE="obsidian" + RAW_SOURCE="$(tn './ (整個 vault 根目錄的 .md)' './ (all .md under the vault root)')" + IS_VAULT="yes" +else + VAULT_TYPE="docs" + RAW_SOURCE="docs/" +fi +# 偵測到是筆記 vault → 出聲告訴使用者「我看到了,會小心、不破壞你的筆記結構」。 +# 不是筆記(一般開發案等)→ 不囉嗦,默默把 docs/ 當原始文件夾安裝完成。 +if [ "$IS_VAULT" = "yes" ]; then + t "🗂️ 偵測到 ${VAULT_TYPE} 筆記庫 → 原始文件:${RAW_SOURCE}" \ + "🗂️ Detected a ${VAULT_TYPE} note vault → raw source: ${RAW_SOURCE}" + t " (會保留你筆記軟體的目錄/檔名結構,不搬動、不改名)" \ + " (your note app's directory/file structure is preserved — nothing is moved or renamed)" + echo "" +fi + +# 把「raw source 宣告區塊」吐出來,給新建的 CLAUDE.md append 或 +# 給已存在的 CLAUDE.md 當手動補貼的提示。內容對 CC / Cowork 都是 +# 機器可讀的指令(明確路徑 + 不可破壞 vault 結構的約束)。 +# 寫進 CLAUDE.md 的 raw source 宣告區塊。給人也給 AI 看: +# 依 locale 只寫「一種語言」進 CLAUDE.md(雙語會讓每個 session 的 context 更滿)。 +emit_raw_source_block() { + local source_kind + if [ "$IS_ZH" = "yes" ]; then + if [ "$IS_VAULT" = "yes" ]; then source_kind="${VAULT_TYPE} 筆記庫" + else source_kind="一般專案(原始文件放 raw source 路徑)"; fi + cat < 安裝時偵測到的來源型態:**${source_kind}** +> CC 與 Cowork 整理/讀取「人寫的原始文件」時,**只在這裡找、只在這裡動**。 + +| 項目 | 值 | +|------|----| +| 來源型態 | \`${source_kind}\` | +| raw source | \`${RAW_SOURCE}\` | + +**約束(CC 與 Cowork 都必須遵守)** + +- 整理 wiki/知識時,原始文件**一律從上方 raw source 路徑讀取**,不要假設是 \`docs/\`。 +BLOCK + if [ "$IS_VAULT" = "yes" ]; then + cat < Source type detected at install time: **${source_kind}** +> When CC and Cowork curate/read human-written raw source, **look only here and act only here**. + +| Item | Value | +|------|-------| +| Source type | \`${source_kind}\` | +| raw source | \`${RAW_SOURCE}\` | + +**Constraints (both CC and Cowork must obey)** + +- When curating the wiki/knowledge, **always read raw source from the path above** — don't assume \`docs/\`. +BLOCK + if [ "$IS_VAULT" = "yes" ]; then + cat < "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" + + # 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" +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" + +chmod +x .claude/hooks/*.sh 2>/dev/null || true + +# ── 依模組產生 settings.json 的 hooks 區塊 ──────── +# settings.json 因模組而異,不能直接下載單一靜態檔,改條件組裝。 +build_hooks_json() { + local session_hooks="" pretool_hooks="" + + if $WANT_WIKI; then + session_hooks='{ "type": "command", "command": ".claude/hooks/session-start-recall.sh" }' + fi + + # PreToolUse 依模組疊加 + local pt=() + $WANT_SDD && pt+=('{ "type": "command", "command": ".claude/hooks/sdd-guard.sh" }') + pt+=('{ "type": "command", "command": ".claude/hooks/pre-write-guard.sh" }') + $WANT_WIKI && pt+=('{ "type": "command", "command": ".claude/hooks/wiki-secret-scan.sh" }') + local IFS=, + pretool_hooks="${pt[*]}" + + printf '{\n "hooks": {\n' + if [ -n "$session_hooks" ]; then + printf ' "SessionStart": [\n { "matcher": "startup|resume|clear",\n "hooks": [ %s ] }\n ],\n' "$session_hooks" + fi + printf ' "PreToolUse": [\n { "matcher": "Write|Edit",\n "hooks": [ %s ] }\n ]\n' "$pretool_hooks" + printf ' }\n}\n' +} + +if [ ! -f ".claude/settings.json" ]; then + build_hooks_json > .claude/settings.json + CREATED+=(".claude/settings.json $(tn "(依 $MODULE 模組產生)" "(generated for module: $MODULE)")") +else + SKIPPED+=(".claude/settings.json $(tn '(已存在,請手動合併 hooks)' '(already exists — merge hooks manually)')") +fi + +# ── CLAUDE.md:只在完全不存在時建立 ──────────────── +# 新建時把偵測到的 raw source 宣告 append 進去(在建立的當下寫入, +# 不回頭改使用者既有的 CLAUDE.md,維持「已有不覆蓋」原則)。 +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})")") + fi +else + SKIPPED+=("CLAUDE.md $(tn '(已存在,請手動加入對應區塊)' '(already exists — add the block manually)')") +fi + +# ── 輸出結果 ────────────────────────────────────── +echo "" +t "✅ 建立了:" "✅ Created:" +# 注意:macOS bash 3.2 在 set -u 下展開「空陣列」會炸 unbound variable, +# 所以這裡先確認有元素才展開(SKIPPED 區塊在下方本來就有守,CREATED 補上)。 +if [ ${#CREATED[@]} -gt 0 ]; then + for item in "${CREATED[@]}"; do echo " + $item"; done +fi + +if [ ${#SKIPPED[@]} -gt 0 ]; then + echo "" + t "⚠️ 跳過(已存在):" "⚠️ Skipped (already exists):" + for item in "${SKIPPED[@]}"; do echo " - $item"; done +fi + +echo "" +echo "─────────────────────────────────" + +# CLAUDE.md 已存在 → 依模組提醒手動加區塊 +if [ -f "CLAUDE.md" ]; then + if ! grep -q "raw source" CLAUDE.md; then + echo "" + t "📌 CLAUDE.md 已存在但缺少 raw source 宣告。" \ + "📌 CLAUDE.md exists but lacks a raw source declaration." + t " 請手動把以下區塊貼進去,讓 CC 與 Cowork 知道原始文件在哪、不要亂動既有結構:" \ + " Paste the block below in so CC and Cowork know where the raw source is and won't disturb your structure:" + emit_raw_source_block | sed 's/^/ /' + fi + if $WANT_WIKI && ! grep -q "wiki/status.md" CLAUDE.md; then + echo "" + t "📌 CLAUDE.md 已存在但缺少 wiki 讀取順序,請手動加入:" \ + "📌 CLAUDE.md exists but lacks the wiki reading order — please add it manually:" + echo "" + if [ "$IS_ZH" = "yes" ]; then + cat <<'SNIP' + ## Wiki 讀取順序(push:hook 開 session 自動注入) + | 檔案 | 時機 | 用途 | + |------|------|------| + | `system-dev/wiki/status.md` | session 開始第一件事 | 當前進度 | + | `system-dev/wiki/principles.md` | 設計任何東西前 | 跨全局原則,必服從 | + | `system-dev/wiki/mistakes.md` | 做新功能前 | 已知踩坑 | +SNIP + else + cat <<'SNIP' + ## Wiki reading order (push: auto-injected at session start) + | File | When | Purpose | + |------|------|---------| + | `system-dev/wiki/status.md` | first thing at session start | current progress | + | `system-dev/wiki/principles.md` | before designing anything | global principles, must obey | + | `system-dev/wiki/mistakes.md` | before building a new feature | known pitfalls | +SNIP + fi + fi + if $WANT_SDD && ! grep -q "system-dev/docs/3-specs" CLAUDE.md; then + echo "" + t "📌 CLAUDE.md 已存在但缺少 SDD 鐵律,請手動加入:" \ + "📌 CLAUDE.md exists but lacks the SDD iron rule — please add it manually:" + echo "" + if [ "$IS_ZH" = "yes" ]; then + cat <<'SNIP' + ## 絕對鐵律 + 1. 任何 code 變動前必須有對應 SDD(system-dev/docs/3-specs/[子系統]/design.md) + 找不到 → 停手問負責人,不要自行建立。 +SNIP + else + cat <<'SNIP' + ## Iron rule + 1. Every code change must have a matching SDD (system-dev/docs/3-specs/[subsystem]/design.md). + Not found → stop and ask the owner; do not create one on your own. +SNIP + fi + fi +fi + +# settings.json 已存在 → 依模組提醒要合併哪些 hook +if [ -f ".claude/settings.json" ]; then + MISSING_HOOKS=() + $WANT_WIKI && ! grep -q "session-start-recall.sh" .claude/settings.json && MISSING_HOOKS+=("SessionStart: session-start-recall.sh") + $WANT_WIKI && ! grep -q "wiki-secret-scan.sh" .claude/settings.json && MISSING_HOOKS+=("PreToolUse(Write|Edit): wiki-secret-scan.sh") + $WANT_SDD && ! grep -q "sdd-guard.sh" .claude/settings.json && MISSING_HOOKS+=("PreToolUse(Write|Edit): sdd-guard.sh") + if [ ${#MISSING_HOOKS[@]} -gt 0 ]; then + echo "" + t "📌 .claude/settings.json 已存在,請手動把以下 hooks 合併進去(保留既有設定):" \ + "📌 .claude/settings.json exists — merge the hooks below in manually (keep your existing settings):" + for h in "${MISSING_HOOKS[@]}"; do echo " • $h"; done + fi +fi + +# pre-write-guard 是空殼,提醒它預設不攔(避免「以為有保護其實沒有」的安全錯覺) +echo "" +t "ℹ️ .claude/hooks/pre-write-guard.sh 是「按需手填的空插槽」,預設不攔任何東西。" \ + "ℹ️ .claude/hooks/pre-write-guard.sh is an empty slot to fill on demand — by default it blocks nothing." +t " 需要專案禁令?最簡單是叫你的 CC 寫一支貼合的 guard hook(比範本表達力強);" \ + " Need project-specific bans? Easiest is to ask your CC to write a tailored guard hook (more expressive than the template);" +t " 或自己填 FORBIDDEN_PATTERNS 並到 settings.json 掛上才會生效。" \ + " or fill in FORBIDDEN_PATTERNS yourself and wire it into settings.json to take effect." + +echo "" +t "🚀 下一步:" "🚀 Next steps:" +if $WANT_WIKI; then + t " 在 Claude Code 對話裡執行 /wiki-init" \ + " In a Claude Code conversation, run /wiki-init" + t " CC 會掃描現有文件、套用 .wikiignore、建立 wiki。" \ + " CC will scan your existing docs, apply .wikiignore, and build the wiki." +fi +if $WANT_SDD; then + t " 動 code 前先在 system-dev/docs/3-specs/[子系統]/ 建 design.md(可用 /sdd-check 協助)" \ + " Before touching code, create design.md under system-dev/docs/3-specs/[subsystem]/ (use /sdd-check to help)" +fi +t " GitHub issue:CC 可直接 /issue-handle 讀回自己 repo 的 issue(禁自動輪詢)" \ + " GitHub issues: CC can use /issue-handle to read issues from its own repo (no auto-polling)" +echo "" diff --git a/system-dev/scripts/update.sh b/system-dev/scripts/update.sh new file mode 100755 index 0000000..dadd4f8 --- /dev/null +++ b/system-dev/scripts/update.sh @@ -0,0 +1,332 @@ +#!/bin/bash +# system-dev-template updater +# 已安裝舊版的人,一鍵更新到新版。 +# +# 核心安全原則:只覆蓋「模板/邏輯檔」,絕不碰「使用者資料檔」。 +# ✅ 可覆蓋:hooks/*.sh、commands/*.md、TEMPLATE-*、wiki/INDEX.md +# ——這些由模板維護,使用者不會手改,新版直接換掉。 +# 🔒 絕不碰:wiki/status.md、mistakes.md、decisions-summary.md、TAXONOMY.md、.wikiignore、 +# settings.json、CLAUDE.md +# ——這些是使用者自己填的內容,覆蓋=清空他的記憶與設定。 +# +# 「第一次更新」的雞生蛋問題: +# 舊版本機沒有 update.sh。所以第一次靠 README 那行 curl 從遠端抓這支腳本來跑。 +# 跑完它會把自己也更新進 scripts/update.sh,之後就能直接跑本機的 `bash scripts/update.sh`。 + +set -euo pipefail + +# ── i18n:依 locale 選語言,預設英文(curl | bash 常為 LANG=C)── +case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in + zh*|*Hant*|*Hans*) IS_ZH="yes" ;; + *) IS_ZH="no" ;; +esac +t() { if [ "$IS_ZH" = "yes" ]; then printf '%s\n' "$1"; else printf '%s\n' "$2"; fi; } +tn() { if [ "$IS_ZH" = "yes" ]; then printf '%s' "$1"; else printf '%s' "$2"; fi; } + +REPO_RAW="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main" +TEMPLATE_URL="$REPO_RAW/template" + +UPDATED=() +KEPT=() +NEW=() +TEMPLATED=() +MIGRATED=() +COEXIST=() + +# ── 版本比對:先看本機 vs 遠端,給使用者「值不值得更新」的判斷 ── +# VERSION 新位置在 system-dev/,舊位置在 .claude/(1.8.x 以前)。優先讀新、回退舊。 +LOCAL_VER="$(tn '(未知)' '(unknown)')" +if [ -f "system-dev/VERSION" ]; then + LOCAL_VER="$(tr -d '[:space:]' < system-dev/VERSION)" +elif [ -f ".claude/VERSION" ]; then + LOCAL_VER="$(tr -d '[:space:]' < .claude/VERSION)" +fi +REMOTE_VER="$(curl -sSL "$TEMPLATE_URL/system-dev/VERSION" 2>/dev/null | tr -d '[:space:]' || echo '')" +# 容錯:curl 對 404 會把「404:NotFound」當內容輸出(非空),舊版誤把它寫進 VERSION。 +# 這裡驗證必須像版號(X.Y.Z),否則一律視為取不到,避免污染 VERSION 檔。 +case "$REMOTE_VER" in + [0-9]*.[0-9]*.[0-9]*) : ;; # 形如 1.9.0 → 合法 + *) REMOTE_VER="" ;; # 404 / HTML 錯誤頁 / 其他 → 當作沒抓到 +esac + +echo "" +echo "🔄 system-dev-template updater" +echo "=================================" +t " 本機版本:${LOCAL_VER}" " Local version: ${LOCAL_VER}" +t " 最新版本:${REMOTE_VER:-取不到(檢查網路)}" \ + " Latest version: ${REMOTE_VER:-unavailable (check network)}" +echo "" + +if [ -z "$REMOTE_VER" ]; then + t "❌ 取不到遠端版本,可能是網路問題。請稍後再試。" \ + "❌ Could not fetch the remote version (likely a network issue). Please try again later." + exit 1 +fi + +if [ "$LOCAL_VER" = "$REMOTE_VER" ]; then + t "✅ 已是最新版(${LOCAL_VER}),不需更新。" \ + "✅ Already up to date (${LOCAL_VER}), nothing to update." + t " (仍會同步模板邏輯檔,確保 hooks/commands 與最新一致。)" \ + " (Template logic files will still be synced to keep hooks/commands in line with the latest.)" + echo "" +fi + +# ── 結構遷移(1.9.0):舊版把 wiki/VERSION 放 .claude/、工具 docs 放根 docs/ ── +# 新版一律收進 system-dev/。這裡冪等遷移:偵測舊位置 → 搬到 system-dev/,已搬過則略過。 +# 必須在「模組偵測」之前跑(偵測靠目錄存在與否判斷,搬完才看得到新位置)。 +# +# 安全原則: +# - wiki 整包搬(含 cards/ 與可能的 wiki/.git),用 mv 保留內含 .git。 +# - docs 只搬「工具自己鋪的白名單」子目錄;用戶自填在 docs/ 的其他內容一律不動。 +# - 目的地已存在同名 → 不覆蓋(保留用戶在新位置的東西),略過該項。 +migrate_dir() { # $1=舊路徑 $2=新路徑 + local from="$1" to="$2" + [ -e "$from" ] || return 0 # 舊的不存在 → 無需遷移 + if [ -e "$to" ]; then + # 目的地已存在。兩種可能: + # (a) 已遷移過 → 舊位置不該還在;冪等略過即可。 + # (b) 用戶先 install 建了空殼 → 舊位置仍有真資料,現在「並存」。 + # 不能靜默跳過 (b),也絕不自動合併(覆蓋風險)。→ 記為「並存待合併」,警告。 + COEXIST+=("$from ↔ $to") + return 0 + fi + mkdir -p "$(dirname "$to")" + if mv "$from" "$to" 2>/dev/null; then + MIGRATED+=("$from → $to") + fi +} + +# 任一舊位置還在 → 需要遷移(遷移本身冪等:已搬的項目會被 migrate_dir 略過)。 +NEEDS_MIGRATE="no" +if [ -d ".claude/wiki" ] || [ -f ".claude/VERSION" ] \ + || [ -d "docs/3-specs" ] || [ -f "docs/SKILL.md" ] || [ -f "docs/README.md" ]; then + NEEDS_MIGRATE="yes" +fi + +if [ "$NEEDS_MIGRATE" = "yes" ]; then + t "🔧 偵測到舊版結構,遷移到 system-dev/ …" "🔧 Old layout detected — migrating into system-dev/ …" + mkdir -p system-dev + + # wiki(含 cards/ 與內含的 .git)整包搬 + migrate_dir ".claude/wiki" "system-dev/wiki" + # 工具版號 + migrate_dir ".claude/VERSION" "system-dev/VERSION" + # 工具文件白名單(只搬工具鋪的,用戶自填的 docs 內容不動) + migrate_dir "docs/SKILL.md" "system-dev/docs/SKILL.md" + migrate_dir "docs/README.md" "system-dev/docs/README.md" + migrate_dir "docs/1-vision" "system-dev/docs/1-vision" + migrate_dir "docs/2-architecture" "system-dev/docs/2-architecture" + migrate_dir "docs/3-specs" "system-dev/docs/3-specs" + migrate_dir "docs/4-guides" "system-dev/docs/4-guides" + migrate_dir "docs/5-records" "system-dev/docs/5-records" + migrate_dir "docs/6-user" "system-dev/docs/6-user" + echo "" +fi + +# ── 工具函式 ─────────────────────────────────────── +# 覆蓋更新:模板/邏輯檔,無條件抓最新版蓋掉。 +update_file() { + local dest="$1" src="$2" + mkdir -p "$(dirname "$dest")" + if [ -f "$dest" ]; then + if curl -sSL "$src" -o "$dest.tmp" 2>/dev/null && [ -s "$dest.tmp" ]; then + if cmp -s "$dest" "$dest.tmp"; then + rm -f "$dest.tmp" # 內容相同,不算更新 + else + mv "$dest.tmp" "$dest" + UPDATED+=("$dest") + fi + else + rm -f "$dest.tmp" + t " ⚠️ 抓取失敗,保留原檔:$dest" " ⚠️ Download failed, keeping the original: $dest" + fi + else + if curl -sSL "$src" -o "$dest" 2>/dev/null && [ -s "$dest" ]; then + NEW+=("$dest") # 新功能:舊版沒有的檔 + else + rm -f "$dest" + t " ⚠️ 抓取失敗:$dest" " ⚠️ Download failed: $dest" + fi + fi +} + +# 保留:使用者資料檔,只記錄「有保留」,永遠不動。 +keep_file() { + [ -f "$1" ] && KEPT+=("$1") || true +} + +# 補新檔:舊版沒有、新版才有的「使用者資料檔」(如 principles.md)。 +# 不存在 → 抓範本下來(之後由使用者/CC 填);已存在 → 當用戶資料保留,絕不覆蓋。 +add_if_missing() { + local dest="$1" src="$2" + if [ -f "$dest" ]; then + KEPT+=("$dest") + elif curl -sSL "$src" -o "$dest" 2>/dev/null && [ -s "$dest" ]; then + NEW+=("$dest") + else + rm -f "$dest" + fi +} + +# 客製檔:使用者一定會手填內容(如 pre-write-guard.sh)。 +# - 已存在 → 絕不覆蓋,但把最新模板版抓到 <檔名>.template.sh 旁邊,供使用者自行 diff 採納。 +# - 不存在 → 視同新檔,直接抓本體(第一次安裝才會走這條)。 +keep_with_template() { + local dest="$1" src="$2" + if [ -f "$dest" ]; then + KEPT+=("$dest") + local tmpl="${dest%.sh}.template.sh" + if curl -sSL "$src" -o "$tmpl.tmp" 2>/dev/null && [ -s "$tmpl.tmp" ]; then + if [ -f "$tmpl" ] && cmp -s "$tmpl" "$tmpl.tmp"; then + rm -f "$tmpl.tmp" # 模板版沒變,不重複提示 + else + mv "$tmpl.tmp" "$tmpl" + TEMPLATED+=("$tmpl") + fi + else + rm -f "$tmpl.tmp" + fi + else + update_file "$dest" "$src" # 還沒裝過 → 當新檔處理 + fi +} + +# ── 偵測已安裝哪些模組(依現有檔案判斷,更新只動已裝的)── +# 遷移已在上面跑完,這裡看新位置 system-dev/。 +HAS_WIKI=false +HAS_SDD=false +[ -d "system-dev/wiki" ] && HAS_WIKI=true +if [ -f ".claude/hooks/sdd-guard.sh" ] || [ -d "system-dev/docs/3-specs/TEMPLATE-sdd" ]; then HAS_SDD=true; fi + +t "📦 偵測到已安裝模組:" "📦 Detected installed modules:" +$HAS_WIKI && echo " • LLM Wiki" +$HAS_SDD && echo " • SDD" +{ $HAS_WIKI || $HAS_SDD; } || \ + t " (未偵測到任何模組——這裡可能還沒安裝,請改跑 install.sh)" \ + " (No modules detected — nothing installed here yet; run install.sh instead.)" +echo "" + +# ── 客製檔:使用者手填的 guardrail,永不覆蓋(issue #3)── +# pre-write-guard.sh 的定位是「空白客製模板,使用者沒配置前不提供保護」(CHANGELOG 1.2.0)。 +# 下游通常已塞滿自己的 enforcement,直接覆蓋=無聲關掉整套 guardrail。 +# 改為:保留原檔不動,新版範本另存 pre-write-guard.template.sh,由使用者自行 diff 採納。 +keep_with_template ".claude/hooks/pre-write-guard.sh" "$TEMPLATE_URL/.claude/hooks/pre-write-guard.sh" + +# ── 模板/邏輯檔:覆蓋更新 ────────────────────────── +# 共用 hook 與指引 +update_file ".claude/commands/issue-handle.md" "$TEMPLATE_URL/.claude/commands/issue-handle.md" +update_file "system-dev/VERSION" "$TEMPLATE_URL/system-dev/VERSION" + +if $HAS_WIKI; then + # wiki 的「邏輯檔」:導航與 hooks,可覆蓋。wiki 資料在 system-dev/,hooks/commands 留 .claude/。 + update_file "system-dev/wiki/INDEX.md" "$TEMPLATE_URL/system-dev/wiki/INDEX.md" + update_file ".claude/hooks/session-start-recall.sh" "$TEMPLATE_URL/.claude/hooks/session-start-recall.sh" + update_file ".claude/hooks/wiki-secret-scan.sh" "$TEMPLATE_URL/.claude/hooks/wiki-secret-scan.sh" + update_file ".claude/commands/wiki-init.md" "$TEMPLATE_URL/.claude/commands/wiki-init.md" + update_file ".claude/commands/wiki-capture.md" "$TEMPLATE_URL/.claude/commands/wiki-capture.md" + update_file ".claude/commands/wiki-update.md" "$TEMPLATE_URL/.claude/commands/wiki-update.md" + update_file ".claude/commands/wiki-recall.md" "$TEMPLATE_URL/.claude/commands/wiki-recall.md" + # Cowork(claude.ai)的 wiki 整理 skill:規則檔,可覆蓋 + update_file "system-dev/docs/SKILL.md" "$TEMPLATE_URL/system-dev/docs/SKILL.md" + + # wiki 的「使用者資料」:絕不碰 + keep_file "system-dev/wiki/status.md" + keep_file "system-dev/wiki/mistakes.md" + # principles.md(1.10):舊版沒有 → 補範本;已有 → 當用戶資料保留 + add_if_missing "system-dev/wiki/principles.md" "$TEMPLATE_URL/system-dev/wiki/principles.md" + keep_file "system-dev/wiki/decisions-summary.md" + keep_file "system-dev/wiki/TAXONOMY.md" + keep_file "system-dev/wiki/.wikiignore" +fi + +if $HAS_SDD; then + # SDD 範本與 hook:可覆蓋 + update_file "system-dev/docs/3-specs/TEMPLATE-sdd/design.md" "$TEMPLATE_URL/system-dev/docs/3-specs/TEMPLATE-sdd/design.md" + update_file "system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md" "$TEMPLATE_URL/system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md" + update_file "system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md" "$TEMPLATE_URL/system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md" + update_file ".claude/commands/sdd-check.md" "$TEMPLATE_URL/.claude/commands/sdd-check.md" + update_file ".claude/hooks/sdd-guard.sh" "$TEMPLATE_URL/.claude/hooks/sdd-guard.sh" +fi + +# ── 自我更新:把最新的 update.sh / install.sh 抓到 system-dev/scripts/ ── +# 這兩支在 main/scripts/ 下(不在 template/);落地位置新版收進 system-dev/scripts/。 +update_file "system-dev/scripts/update.sh" "$REPO_RAW/scripts/update.sh" +update_file "system-dev/scripts/install.sh" "$REPO_RAW/scripts/install.sh" + +chmod +x .claude/hooks/*.sh system-dev/scripts/*.sh 2>/dev/null || true + +# ── 使用者資料檔:絕不碰,但提醒「設定可能有新欄位要手動補」── +keep_file ".claude/settings.json" +keep_file "CLAUDE.md" + +# ── 結果輸出 ─────────────────────────────────────── +echo "" +echo "─────────────────────────────────" +if [ ${#MIGRATED[@]} -gt 0 ]; then + echo "" + t "📦 結構遷移(已收進 system-dev/):" "📦 Layout migrated (moved into system-dev/):" + for f in "${MIGRATED[@]}"; do echo " ⇒ $f"; done +fi +if [ ${#COEXIST[@]} -gt 0 ]; then + echo "" + t "🛑 偵測到 wiki 並存(新舊位置都有資料,需要合併):" \ + "🛑 Coexisting wiki detected (both old and new locations have data — needs merging):" + for f in "${COEXIST[@]}"; do echo " ↔ $f"; done + t " 成因:先跑過 install(建了空殼)才遷移,舊位置真資料沒被搬。" \ + " Cause: install ran first (created an empty shell), so migration skipped your real data in the old location." + t " 不自動合併(避免覆蓋你的資料)。請叫你的 CC:" \ + " Not auto-merged (to avoid overwriting your data). Ask your CC:" + t " 「.claude/wiki/ 和 system-dev/wiki/ 並存,請逐檔比對、把真資料合進 system-dev/,再刪舊的」" \ + " \"There are two wikis (.claude/wiki/ and system-dev/wiki/) — diff each file, merge the real data into system-dev/, then delete the old one.\"" +fi +if [ ${#NEW[@]} -gt 0 ]; then + echo "" + t "🆕 新功能(舊版沒有,已加入):" "🆕 New features (absent in the old version, now added):" + for f in "${NEW[@]}"; do echo " + $f"; done +fi +if [ ${#UPDATED[@]} -gt 0 ]; then + echo "" + t "⬆️ 已更新(覆蓋成新版):" "⬆️ Updated (overwritten with the new version):" + for f in "${UPDATED[@]}"; do echo " ~ $f"; done +fi +if [ ${#NEW[@]} -eq 0 ] && [ ${#UPDATED[@]} -eq 0 ]; then + echo "" + t "✨ 模板邏輯檔已全部最新,無需變動。" \ + "✨ All template logic files are already up to date — no changes needed." +fi +if [ ${#KEPT[@]} -gt 0 ]; then + echo "" + t "🔒 完整保留(你的內容/設定,從未碰過):" \ + "🔒 Fully preserved (your content/settings, never touched):" + for f in "${KEPT[@]}"; do echo " = $f"; done +fi +if [ ${#TEMPLATED[@]} -gt 0 ]; then + echo "" + t "📋 客製檔有新版範本(你的原檔沒動,新版另存旁邊,請自行 diff 採納):" \ + "📋 Custom files have a new template version (your original is untouched; the new one is saved alongside — diff and adopt as you like):" + for f in "${TEMPLATED[@]}"; do + echo " → $f" + t " 比對:diff \"${f%.template.sh}.sh\" \"$f\"" \ + " compare: diff \"${f%.template.sh}.sh\" \"$f\"" + done +fi + +# ── settings.json 提醒:新模組 hook 可能要手動補 ── +if [ -f ".claude/settings.json" ]; then + MISSING=() + $HAS_WIKI && ! grep -q "session-start-recall.sh" .claude/settings.json && MISSING+=("SessionStart: session-start-recall.sh") + $HAS_WIKI && ! grep -q "wiki-secret-scan.sh" .claude/settings.json && MISSING+=("PreToolUse(Write|Edit): wiki-secret-scan.sh") + $HAS_SDD && ! grep -q "sdd-guard.sh" .claude/settings.json && MISSING+=("PreToolUse(Write|Edit): sdd-guard.sh") + if [ ${#MISSING[@]} -gt 0 ]; then + echo "" + t "📌 settings.json 是你的設定(沒動),但偵測到缺以下 hook,請手動補上:" \ + "📌 settings.json is yours (untouched), but these hooks are missing — please add them manually:" + for h in "${MISSING[@]}"; do echo " • $h"; done + fi +fi + +echo "" +t "🚀 更新完成:${LOCAL_VER} → ${REMOTE_VER}" "🚀 Update complete: ${LOCAL_VER} → ${REMOTE_VER}" +t " 下次更新直接跑:bash system-dev/scripts/update.sh" " Next time, just run: bash system-dev/scripts/update.sh" +t " 改了什麼看:CHANGELOG.md" " See what changed: CHANGELOG.md" +echo "" diff --git a/system-dev/wiki/.wikiignore b/system-dev/wiki/.wikiignore new file mode 100644 index 0000000..da34dd1 --- /dev/null +++ b/system-dev/wiki/.wikiignore @@ -0,0 +1,2 @@ +# wiki 機敏三層防護 L1:整檔排除(不進 wiki 投影/掃描的檔) +# 一行一個 glob,相對 .claude/wiki/。空檔也合法。 diff --git a/system-dev/wiki/INDEX.md b/system-dev/wiki/INDEX.md new file mode 100644 index 0000000..4d0555f --- /dev/null +++ b/system-dev/wiki/INDEX.md @@ -0,0 +1,62 @@ +# system-dev/wiki/ — LLM 記憶系統 + +> 新 session 開始時從這裡導航。 +> 目的:讓 CC 不需要重新學習已知的事。 +> 維護者:CC(人不手動編輯這裡) + +--- + +## push 檔(session 開始由 hook 主動注入,CC 行動前必看見) + +| 檔案 | 注入形態 | 內容 | +|------|---------|------| +| `status.md` | 全文 | 當前進度、下一步(時態狀態)| +| `principles.md` | 全文(一行一條)| 跨全局設計原則,行動前必服從 | +| `mistakes.md` | 標題+一行症狀,全文按需展開 | 踩過的坑、被糾正的誤解(防不自覺盲區)| + +> 為什麼這三個 push 而非 pull:它們是「CC 不會主動查、但不看就出事」的盲區。詳見 `/wiki-init` 的「push vs pull」。 + +--- + +## pull:cards/(CC 按需檢索) + +一切知識內容——原文摘要、AI 筆記、決策、概念知識——都寫成 `cards//` 的概念原子卡。 +`decisions-summary.md` 已降級為 cards(決策=知識內容);既有的保留為相容。 + +--- + +## 維護規則 + +1. 只增不刪——記錄 append,內容改了加新條目說明「舊的已更新」 +2. status.md 每次 session 結束更新;mistakes/principles 一發現就 append +3. principles 一行一條、≤15 條(超過代表該合併或下放成 card) +4. **新增一個檢索角度 = 在下方「多角度視圖」加一節,不開新實體檔、不問用戶** + +--- + +## 多角度視圖(由 /wiki-init、/wiki-capture 填入) + +INDEX 是**所有檢索角度的入口**,不只標籤。原文是唯讀 SSoT,wiki 是改寫過的記憶。 +新增角度只要在這裡加一節(如「決策角度」「原則角度」),指向對應 cards 或 push 檔——**不必新增實體特殊檔**。 + +### 子系統角度(按 `TAXONOMY.md` 的軸聚類,指向桶子索引) + +#### 架構決策(跨子系統) +- [[decisions/00-INDEX]] — arcrun 架構決策(13 卡,已從 decisions-summary 全量改寫成原子卡) + +> 子系統卡桶(零件架構 / cypher / credential / recipe / kbdb / 薄殼 / 部署)尚未獨立建—— +> 決策卡已用 frontmatter `tags:` 掛子系統軸,可從 INDEX 標籤過濾;純子系統知識卡由 /wiki-capture 依需開桶。 + +### 決策角度(取代舊 decisions-summary.md 的視圖) + +> `decisions-summary.md`(全文)保留為相容;13 條決策已逐條改寫成 `cards/decisions/` 原子卡,入口見 [[decisions/00-INDEX]]。 +> 按子系統分群的清單在桶子索引內,這裡只放跨子系統最常查的: + +- [[工作流是default零件是例外]] — 自用→工作流,全生態重用→才零件(mindset §1) +- [[薄殼原則-能力長在API]] — 能力只實作一次放 API,介面全薄殼(rule 07) +- [[自力救濟階梯-缺能力怎麼補]] — 缺能力分流:自家補API/第三方 workflow 補丁(issue #4) +- [[service-binding-vs-cypher-binding]] — 零件串接走 HTTP URL,禁 service binding(rule 03) + +> 結構:INDEX(多角度入口)→ `cards//00-INDEX.md`(桶子索引,固定名)→ 概念原子卡。 +> 指 `00-INDEX` **一律帶路徑** `[[bucket/00-INDEX]]`(固定名跨桶撞名);卡片間用裸 `[[卡名]]`。 +> 分類由卡片 frontmatter `tags:` 承載,標籤字典見 `TAXONOMY.md`。詳見 `/wiki-init` 規範。 diff --git a/system-dev/wiki/TAXONOMY.md b/system-dev/wiki/TAXONOMY.md new file mode 100644 index 0000000..887de09 --- /dev/null +++ b/system-dev/wiki/TAXONOMY.md @@ -0,0 +1,49 @@ +# TAXONOMY.md — 標籤字典(arcrun repo 專屬) + +> wiki 卡片的 frontmatter `tags:` **只能用這裡列出的標籤**。 +> 這不是凍結——字典**可以擴充**,只是**禁止繞過字典在卡片裡直接冒新標籤**。 +> 維護者:CC(由 /wiki-init 初始化、隨 wiki 演進受控擴充)。 +> +> **遇到現有軸都裝不下的內容時,照這個流程(先查、後擴、登記)**: +> 1. **先查既有**:現有標籤真的都不合,還是只是同義詞?(`零件` vs `component` vs `WASM零件` 是同一軸,別重造) +> 2. **確實是新軸** → 把新標籤加進本檔(附一句定義),再用。不必停下來問人,但「先登記再使用」這道查核不能省。 +> 3. 自由增生才是要防的——同義標籤散開會讓同類卡片分散、下游聚類失準。受控擴充(先查重、再登記)不會。 +> +> **字典是每個 repo 各自的**:arcrun 是開發 repo,軸是「子系統/層級/決策類型」,與知識型 vault 的「知識管理/學習認知」本來就不同。 + +--- + +## 分類採雙軸(一張卡可多重歸屬) + +分類由 **frontmatter `tags:`** 承載,不靠資料夾、不靠行內 `#tag`。 +一張卡同時掛「子系統 1-3 個 + 形態 0-2 個」,可從任一軸過濾。 + +### 子系統 / 領域(主軸,每卡 1-3 個) + +> arcrun 的架構分層。卡講「哪一塊」就掛對應標籤。 + +- `零件架構` — WASM 零件、component worker、registry、部署模式 +- `cypher` — cypher-executor、workflow 執行、host functions、graph executor +- `credential` — auth primitive、加解密、JWT、recipe auth、credential 注入 +- `recipe` — recipe schema、UUID 市場模型、投稿、信任機制 +- `kbdb` — KBDB 資料層、三表、proxy、租戶隔離、embedding/vectorize +- `薄殼` — CLI / MCP / SDK 介面層、能力下沉 API、帳號統一 +- `部署` — wrangler、local-deploy、self-hosted、CI 邊界、account override +- `平台原則` — 跨子系統的世界觀(工作流 vs 零件、誠實不假綠、AI→工具) + +### 形態(副軸,每卡 0-2 個) + +- `架構決策` — 一次性的設計選擇 + trade-off(取代舊 decisions-summary 視圖) +- `踩坑` — 事件復盤、被糾正的誤解(與 mistakes.md 互補:mistakes 是 push 摘要,card 是全文) +- `機制說明` — 某個能力「怎麼運作」的自包含解釋 +- `禁令` — hook 強制的硬規則背後的 why +- `案例經驗` — 壓測、dogfood、實證落地的具體記錄 + +--- + +## 規則 + +1. **先查重、再登記、才使用**:禁止繞過字典在卡片直接冒新標籤;新標籤先確認非現有同義詞,加進本檔(附定義)再用。 +2. **子系統 vs 形態分開**:子系統是「講哪一塊」,形態是「以什麼形式呈現」,不要混。 +3. **頂層 INDEX.md 的標籤視圖依本字典的軸聚類**——字典改了,INDEX 視圖跟著更新。 +4. **新增子系統軸要慎**:子系統是檢索骨架,動它影響全庫聚類;形態軸(呈現形式)擴充較安全。不確定就先用現有最接近的,並在卡片或本檔註記「待人類複核此分類」。 diff --git a/system-dev/wiki/cards/decisions/00-INDEX.md b/system-dev/wiki/cards/decisions/00-INDEX.md new file mode 100644 index 0000000..ede76f4 --- /dev/null +++ b/system-dev/wiki/cards/decisions/00-INDEX.md @@ -0,0 +1,34 @@ +# 架構決策 + +> 桶子索引(容器:只連不重寫)。一決策一卡,自包含 + trade-off。 +> 新決策由 /wiki-capture 寫進本桶並在此 append 一行。 +> 權威全文:`system-dev/docs/2-architecture/decisions/DECISIONS.md`;本桶是 AI 改寫過的可檢索版。 + +## 零件 / 工作流 / 能力補丁 + +- [[工作流是default零件是例外]] — 自用→工作流,全生態重用→才零件;「能包」≠「該包」 +- [[自力救濟階梯-缺能力怎麼補]] — 自家API缺→補API/第三方→workflow補丁/純計算→code-node +- [[薄殼原則-能力長在API]] — CLI/MCP/lib 只暴露,齊的單位是「能力」不是「端點」 +- [[薄殼規則晚於實作-MCP漂移是歷史債]] — 為何 MCP/CLI 不一致:紀律 2026-06-07 才補、補前漂移 + +## 串接 / 部署 + +- [[service-binding-vs-cypher-binding]] — 零件走 HTTP URL,禁新增 service binding +- [[same-zone-1042用flag解不用binding]] — self-hosted cypher 加 global_fetch_strictly_public +- [[R2用途-平台零件不從R2讀]] — 平台零件 bundle 進 Worker,R2 只 Phase 5 用戶自製 +- [[self-hosted部署-共享install加指紋跳過]] — root 共享 install + manifest content-hash 跳過未變動 +- [[不依賴CI-執行鏈路vs零件投稿]] — 執行鏈路走 local script,零件投稿才走 PR/CI + +## credential / recipe + +- [[多worker-ENCRYPTION_KEY同步]] — 各 Worker secret put 同份 key,漂移→解密失敗表現為 401/403 +- [[Recipe-UUID市場模型]] — canonical+uuid+author 多維身份,per-uuid 市場數據 + +## kbdb + +- [[embedding是base-optional模組]] — binding 開才啟用,預設關降級 LIKE,精耕只 embed:true +- [[碰舊Mira需求先查頂層覆寫]] — mira 蒸發→當普世缺口,先查頂層 mira-dissolve 覆寫 + +## 平台目標 + +- [[Haiku能驅動是設計目標]] — Haiku 過=設計達成;撞牆是介面缺陷訊號非換模型 diff --git a/system-dev/wiki/cards/decisions/Haiku能驅動是設計目標.md b/system-dev/wiki/cards/decisions/Haiku能驅動是設計目標.md new file mode 100644 index 0000000..f3b3c2b --- /dev/null +++ b/system-dev/wiki/cards/decisions/Haiku能驅動是設計目標.md @@ -0,0 +1,39 @@ +--- +tags: [平台原則, 架構決策] +gloss: arcrun 能用 Haiku 驅動才算設計達成。撞牆不是換模型,是信號要改介面。 +--- +# Haiku 能驅動是設計目標 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(Haiku 就能搞定是設計目標)、`.claude/rules/06-mindset.md §1` +**最後更新**:2026-06-27 + +## 摘要 +arcrun 的價值主張是「比直接開發容易 + 省 token + 省時間」。只有 Sonnet 能驅動時,「省」就不成立。Haiku 通過才是設計目標達成的證明;反之,Haiku 撞牆=介面缺陷信號,應改介面而非換模型。 + +## 重點 +- **設計思想**:Haiku 作為基礎模型,若某操作需要 Sonnet 才行,代表介面太難、太多隱含上下文、需要手工智能補齊——這是架構欠債。 +- **測試邏輯**: + - Haiku 通過 ✅ → 設計目標達成,介面夠清晰 + - Haiku 不通 → 升 Sonnet: + - Sonnet 通過 → 介面缺陷,要改介面白痴化 + - Sonnet 也不通 → 功能 bug,要改邏輯 +- **不要的做法**:「Haiku 過不了就換 Sonnet」= 放棄設計目標、接受介面複雜度只有豪華模型才能應付。 +- **改進信號**:Haiku 哪個步驟卡住(參數寫法?狀態機理解?錯誤訊息太模糊?),就是該改哪個介面。 +- **誠實限制**(mindset §7):Haiku 撞牆不要假綠(mock 迴應),要誠實標「需要 Sonnet」或「無法 resolve」,作為改進指標。 + +## 實體 +- **Haiku**(Claude 3.5 Haiku)— 輕量級語言模型,arcrun 驅動的基準。 +- **設計達成**(design achievement)— 介面清晰度高到 Haiku 能自主操作,無需人工介入。 +- **介面缺陷**(interface defect)— Haiku 無法理解但 Sonnet 能理解的操作,表示介面隱含前置知識過多。 +- **功能 bug**(functional bug)— Sonnet 也無法做到的操作,表示後端邏輯確實有問題。 + +## 關聯 +### 內文知識關係 +- 設計達成 >> 以 Haiku 通過 >> 衡量 +- Haiku 不通 >> 信號 >> 介面缺陷 +- 介面缺陷 >> 應該 >> 改介面 +- 功能 bug >> 應該 >> 改邏輯 +### 卡片關係 +- [[Haiku能驅動是設計目標]] >> 對應 mindset >> [[工作流是default零件是例外]] diff --git a/system-dev/wiki/cards/decisions/R2用途-平台零件不從R2讀.md b/system-dev/wiki/cards/decisions/R2用途-平台零件不從R2讀.md new file mode 100644 index 0000000..cb9cfcc --- /dev/null +++ b/system-dev/wiki/cards/decisions/R2用途-平台零件不從R2讀.md @@ -0,0 +1,33 @@ +--- +tags: [零件架構, 部署, 架構決策] +gloss: 平台內建零件 WASM 已 bundle 進各自 Worker binary,不從 R2 動態讀。R2 只在 Phase 5(用戶自製零件)啟用。 +--- +# R2 用途 — 平台零件不從 R2 讀 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(R2 的用途)、`.claude/rules/03-component-architecture.md §2` +**最後更新**:2026-06-27 + +## 摘要 +平台內建零件的 WASM 文件在部署時已 bundle 進各自 Worker 的 binary(透過 codeload tarball);R2 存儲的機制只為 Phase 5(用戶上傳自製零件)保留。 + +## 重點 +- 平台零件 = 獨立 Worker,已部署完成,走 HTTP URL 呼叫;沒有 R2 動態載入那一步。 +- WASM 文件的位置:`registry/components/{name}/` 是原始碼,`.component-builds/{name}/component.wasm` 是編譯產物,commit 進 repo 後作為 codeload 部署來源。 +- R2 存儲的唯一用途:Phase 5 啟用時,用戶透過 `acr component submit` 上傳自製 .wasm → R2 存儲 → runtime 動態執行(當時 API 才支援 `GET /user/{id}/components/{name}/wasm`)。 +- 當前「怎麼從 R2 取 WASM」是典型誤問——错误假设的标志。 + +## 實體 +- **平台內建零件**(platform components)— 由 arcrun 官方維護、已編譯成 .wasm、獨立部署成 Worker 的零件(gmail、telegram、http_request 等)。 +- **R2**(Cloudflare R2)— 對象存儲服務,當前未用於平台零件;Phase 5 起用於存放用戶自製零件。 +- **codeload 部署**(tarball deployment)— 透過 GitHub codeload tarball 或 git clone + local build,把平台零件連同依賴打進 Worker binary 的部署方式。 +- **用戶自製零件**(user-submitted components)— Phase 5 後用戶上傳的自定義 WASM 零件(動態來源)。 + +## 關聯 +### 內文知識關係 +- 平台內建零件 >> 不從 >> R2 +- R2 >> 保留用於 >> 用戶自製零件 +- 平台內建零件 >> 透過 codeload 部署 >> 獨立 Worker +### 卡片關係 +- [[R2用途-平台零件不從R2讀]] >> 架構前提來自 >> [[service-binding-vs-cypher-binding]] diff --git a/system-dev/wiki/cards/decisions/Recipe-UUID市場模型.md b/system-dev/wiki/cards/decisions/Recipe-UUID市場模型.md new file mode 100644 index 0000000..623ff9b --- /dev/null +++ b/system-dev/wiki/cards/decisions/Recipe-UUID市場模型.md @@ -0,0 +1,44 @@ +--- +tags: [recipe, 平台原則, 架構決策] +gloss: 多作者同 canonical recipe 用 UUID 並存,市場統計 per-uuid 區分版本,用戶 pull 時自動選最佳版本或按 author 指定。 +--- +# Recipe UUID 市場模型 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(Recipe UUID 模型)、`docs/3-specs/arcrun/kbdb-base/` design.md §7.5 +**最後更新**:2026-06-27 + +## 摘要 +Recipe 的身份由 canonical_id + uuid + author 三維度組成。同一 canonical 的多個版本用不同 uuid 存在 D1,市場統計(成功率)per-uuid 記錄,用戶 pull 時自動選市場最佳版本或明確按 author 指定。 + +## 重點 +- **舊模型(已廢)**:canonical_id 是唯一鑰匙,新版覆蓋舊版,多作者無法共存。 +- **新模型(UUID)**: + - `canonical_id`:服務名標籤(gmail_send) + - `uuid`:版本身份(Leo 的 gmail_send ≠ John 的 gmail_send) + - `author`:誰寫的 + - 真正的 KV/D1 鑰匙 = `recipe:{uuid}` +- **App Store 設計**: + - `acr recipe pull gmail_send` → 後端搜 canonical_id=gmail_send 的所有 uuid → 按市場統計排序 → 回傳成功率最高的版本(自動選) + - `acr recipe pull gmail_send --author john` → 明確指定 John 的版本 +- **市場統計 per-uuid**:每個 uuid 的 execution stat 分開記錄(success/fail/latency)→ 才能區分作者貢獻 +- **向後相容**:舊 workflow 用 canonical_id → resolveRecipe 要能找到;migrate-uuid endpoint 把舊 key 轉新 uuid(冪等) +- **暴露警示**(mindset §6):新 `submit-recipe` 需要用戶明示同意暴露(讓市場統計追蹤效果) + +## 實體 +- **Canonical ID**(规范ID)— 服務名標籤(gmail_send、slack_post),用於搜索,不是 KV 鑰匙。 +- **UUID**(通用唯一標識符)— Recipe 版本身份,真正的 KV/D1 鑰匙 `recipe:{uuid}`。 +- **Author**(作者)— Recipe 的創作者身份,用於區分多作者同 canonical 的版本。 +- **Market stat**(市場統計)— per-uuid 的成功率/失敗率/延遲,用於自動版本選擇。 +- **App Store**(應用市場)— 公開 recipe 倉庫及版本選擇機制。 + +## 關聯 +### 內文知識關係 +- Canonical ID >> 標籤 >> UUID 群組 +- UUID >> 記錄 >> Market stat +- Market stat >> 驅動 >> 自動版本選擇 +- Author >> 並存於 >> 同 canonical 不同 UUID +- 舊 workflow >> 需要 >> 向後相容 +### 卡片關係 +- (相關 memory:`recipe-trust-via-market-not-verification` — recipe 信任靠市場非人力防偽;屬 auto-memory 非 card) diff --git a/system-dev/wiki/cards/decisions/embedding是base-optional模組.md b/system-dev/wiki/cards/decisions/embedding是base-optional模組.md new file mode 100644 index 0000000..bc96a0a --- /dev/null +++ b/system-dev/wiki/cards/decisions/embedding是base-optional模組.md @@ -0,0 +1,43 @@ +--- +tags: [kbdb, 架構決策] +gloss: embedding 機制歸 KBDB base 的 optional 模組,有 Vectorize binding 才啟用;無則降級 LIKE keyword,API 不變。 +--- +# Embedding 是 Base Optional 模組 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(embedding 是 base optional 模組)、`docs/3-specs/arcrun/kbdb-base/` Phase 12 +**最後更新**:2026-06-27 + +## 摘要 +語義查詢/embedding 不是獨立模組或圖插件,而是 KBDB base 層的 optional 擴展。Vectorize + AI binding 存在時啟用;缺少時降級 keyword search + capability hint(發現閉環,不假綠)。 + +## 重點 +- **歸屬**:embedding 機制屬 base 層(核心表 entries/templates/records),不是 graph 層(triplet 插件)、不是 ingest 層(爬蟲)。 +- **啟用機制**: + - 部署時檢查 KBDB binding 是否含 `[ai]` + `[[vectorize]]` → 有則啟用 semantic search + - 無則 API 降級為 keyword LIKE + `capability_hint`(告知使用者「語義搜尋未啟用」) + - **API 簽名不變**(無論啟用否,查詢端點同一個 `/search`),降級轉換在後端 +- **部署陷阱**:部署注入 `[ai]`/`[[vectorize]]` binding 必須在 `stripOfficialOnlyBindings` **之後**(否則 [ai] 被清)。 +- **精耕策略**(非地毯式):只 embed 標 `metadata_json.embed:true` 的 entry(wiki 段落 + gloss),不對每個 block 灌。 +- **無白名單**:base 用通用 flag 判 embed:true,不寫死 entry_type 白名單(守解耦,base 對內容語意無知)。 +- **向量存儲位置**:向量獨存 Vectorize,三表(entries/templates/records)不改動。 +- **發現閉環**:無 embedding 時不假綠(回傳空結果假裝查到),而是明確 hint「語義未啟用」→ capability-driven 發現系統能自動 degrade。 + +## 實體 +- **Base 層**(base layer)— KBDB 核心表(entries/templates/records)及其基本操作。 +- **Optional 模組**(optional module)— 功能開/關不拆 repo,降級機制在 API 內部。 +- **Vectorize**(Cloudflare Vectorize)— 向量資料庫 binding,儲存 embedding。 +- **Semantic search**(語義搜尋)— 基於 embedding 的向量相似度查詢。 +- **Capability hint**(能力提示)— API 回傳中明確標示「此能力未啟用」,供 client 判斷。 +- **Embed flag**(embed:true)— 在 entry metadata_json 中設置,標示是否參與 embedding。 + +## 關聯 +### 內文知識關係 +- Vectorize binding >> 啟用 >> Semantic search +- 無 Vectorize >> 降級為 >> Keyword search +- Embed flag >> 控制 >> 哪些 entry 參與 embedding +- API >> 不變 >> 無論 embedding 啟用否 +- Capability hint >> 發現 >> 語義搜尋未啟用 +### 卡片關係 +- [[embedding是base-optional模組]] >> 涉及部署 >> [[self-hosted部署-共享install加指紋跳過]] diff --git a/system-dev/wiki/cards/decisions/same-zone-1042用flag解不用binding.md b/system-dev/wiki/cards/decisions/same-zone-1042用flag解不用binding.md new file mode 100644 index 0000000..7284035 --- /dev/null +++ b/system-dev/wiki/cards/decisions/same-zone-1042用flag解不用binding.md @@ -0,0 +1,37 @@ +--- +tags: [cypher, 部署, 架構決策, 踩坑] +gloss: self-hosted cypher 和 component 同在 workers.dev zone 觸發 CF 1042,用 global_fetch_strictly_public flag 解,不是 Service Binding。 +--- +# Same-Zone 1042 用 flag 解不用 Binding + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(Cypher 怎麼調用零件避開 same-zone 1042)、`docs/incidents/2026-05-13-cypher-outbound-522.md` +**最後更新**:2026-06-27 + +## 摘要 +Cloudflare Workers 的 same-zone fetch 防護(HTTP 522)在 self-hosted 部署中會踩坑,因為 cypher 和 component 都在 `{sub}.workers.dev`。解法是在 cypher-executor 的 wrangler.toml 加 `global_fetch_strictly_public` flag,讓 same-zone fetch 走公網前門——而非用 Service Binding(那樣更死)。 + +## 重點 +- **官方部署無此問題**:cypher 在 `cypher.arcrun.dev`、component 在 `*.arcrun.dev` 或 `arcrun-*.workers.dev`,跨 zone,不踩防護。 +- **Self-hosted 部署踩坑**:都在 `{sub}.workers.dev`,CF 防止同 zone 自循環死鎖,cypher fetch component 返 522。 +- **為什麼不用 Service Binding**:Binding 靜態、修改要 redeploy;component 清單動態(用戶 workflow 決定),改變架構必要性低;flag 無副作用(official 加也沒影響,本就跨 zone)。 +- **Flag 部署法**:`cypher-executor/wrangler.toml` 加一行 `compatibility_flags = [ "nodejs_compat", "global_fetch_strictly_public" ]`,然後 cypher 和 auth worker 間的 HTTP fetch 都能通。 +- **驗證**:若仍 522 → 檢查 flag 是否生效(某些 Wrangler 版本可能有 bug)。 + +## 實體 +- **Same-zone 防護**(Cloudflare same-zone protection)— CF Workers 內建的防護機制,禁止 Worker 在同一 zone 內 fetch 另一個 Worker 以防自循環。 +- **HTTP 522**(522 Connection Timed Out)— Cloudflare 自有錯誤,表示 fetch 因防護被擋。 +- **global_fetch_strictly_public flag**(Cloudflare compatibility flag)— 讓 same-zone fetch 從內部網路改走公網前門的相容旗標,無副作用。 +- **cypher-executor**(Cypher 執行引擎)— 調度 workflow 並呼叫零件的 Worker,需加 flag 才能在 self-hosted fetch component。 +- **動態 component 清單**(dynamic component list)— workflow 執行時的零件集合,由用戶定義、會變動,無法靜態綁定。 + +## 關聯 +### 內文知識關係 +- same-zone 防護 >> 在 self-hosted >> 觸發 HTTP 522 +- global_fetch_strictly_public flag >> 解決 >> HTTP 522 +- global_fetch_strictly_public flag >> 不改變 >> 官方部署 +- Service Binding >> 不能解決 >> 動態 component 清單 +### 卡片關係 +- [[same-zone-1042用flag解不用binding]] >> 迴避 >> [[service-binding-vs-cypher-binding]] +- [[same-zone-1042用flag解不用binding]] >> 涉及事件 >> [docs/incidents/2026-05-13-cypher-outbound-522.md] diff --git a/system-dev/wiki/cards/decisions/self-hosted部署-共享install加指紋跳過.md b/system-dev/wiki/cards/decisions/self-hosted部署-共享install加指紋跳過.md new file mode 100644 index 0000000..df967f7 --- /dev/null +++ b/system-dev/wiki/cards/decisions/self-hosted部署-共享install加指紋跳過.md @@ -0,0 +1,43 @@ +--- +tags: [部署, 架構決策] +gloss: 23 個 component worker 不各裝依賴,改成 root 共享一次 install + manifest 內容指紋跳過未變動——減 23× node_modules 膨脹。 +--- +# Self-Hosted 部署 — 共享 install 加指紋跳過 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(self-hosted 部署)、`cli/src/lib/deploy.ts` §2.5 +**最後更新**:2026-06-27 + +## 摘要 +tarball 解開後不每個 component worker 各裝 node_modules(23× 重複 ~324MB),改成 root 共享一次 install;同時用 content hash manifest 記錄部署狀態,相同 hash 跳過已部署 worker,只部署內容變動者。 + +## 重點 +- **問題**:23 個 component worker 的 runtime dep 全是 hono(tier2 另需 zod/mcp-sdk/yaml),各裝 ~324MB node_modules = 23× 重複;且冪等重跑被誤解成「每次重做全部」,22 個沒變的白跑一遍。 +- **共享 install 機制**: + - tarball 根目錄裝一次 hono + wrangler + tier2 deps + - 各 worker 靠 Node.js 往上搜索路徑 resolve dependencies + - `wrangler deploy --dry-run` 驗證 tier1+tier2 都被 bundle(確保包完整) + - 結果:207MB×1 取代 324MB×23 +- **Manifest 跳過**: + - `~/.arcrun/deploy-manifest.json` 記錄 content hash + 上次成功時間戳 + - 新解開 tarball → 算 content hash 與上次成功者比較 → 相同跳過、不同重部 + - Hash 含 accountId(換帳號/KV namespace → 自動重部,不誤跳) + - `--force` flag 清空 manifest → 全部重新部署 +- **失敗恢復**:共享 install 失敗 → 自動退回各 worker 自裝(不破壞既有路徑) +- **只記成功**:manifest 只追蹤成功部署者,失敗者下次必重試 + +## 實體 +- **共享 install**(shared installation)— root 目錄的單一 node_modules,所有 worker 共享。 +- **Manifest**(部署清單)— `~/.arcrun/deploy-manifest.json`,記錄 content hash 和成功部署狀態。 +- **Content hash**(內容雜湊)— 本次 tarball 內容的唯一指紋,用於比較是否變動。 +- **Component worker**(零件 Worker)— `.component-builds/{name}/` 下各個獨立部署單位。 + +## 關聯 +### 內文知識關係 +- 共享 install >> 減少 >> 重複 node_modules +- Manifest >> 跳過 >> 未變動 component worker +- Content hash >> 相同時 >> 自動跳過部署 +- accountId >> 不同時 >> 強制重新部署 +### 卡片關係 +- [[self-hosted部署-共享install加指紋跳過]] >> 優化 >> [[不依賴CI-執行鏈路vs零件投稿]] diff --git a/system-dev/wiki/cards/decisions/service-binding-vs-cypher-binding.md b/system-dev/wiki/cards/decisions/service-binding-vs-cypher-binding.md new file mode 100644 index 0000000..f21bbfc --- /dev/null +++ b/system-dev/wiki/cards/decisions/service-binding-vs-cypher-binding.md @@ -0,0 +1,36 @@ +--- +tags: [零件架構, cypher, 架構決策] +gloss: 零件串接用 Cypher binding(YAML URL 清單)not Service Binding(wrangler.toml)——後者靜態、不適合動態 workflow。 +--- +# Service Binding vs Cypher Binding + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(Service Binding vs Cypher Binding)、`.claude/rules/03-component-architecture.md §1` +**最後更新**:2026-06-27 + +## 摘要 +Cypher binding 是 YAML/KV 裡的 URL 清單(用戶定義、動態);Service Binding 是 wrangler.toml 的 `[[services]]`(平台內建、靜態)。workflow 層一律走 HTTP URL,禁止新增 Service Binding。 + +## 重點 +- Cypher binding 用 HTTP 呼叫,workflow 是用戶定義的動態圖,不能要求每改 workflow 就 redeploy。 +- Service Binding 靜態、修改 wrangler.toml 要重新部署——完全違反 workflow 靈活性,故只保留 13 個邏輯零件間的歷史遺產綁定、禁止擴展。 +- 新零件部署流程:`registry/components/{name}/main.go` → 編譯 `.wasm` → 包進 `.component-builds/{name}/` Worker(Hono + WASI shim)→ 獨立部署成 `arcrun-{name}.{sub}.workers.dev`(對內)+ `{name}.arcrun.dev`(對外)→ cypher 透過 HTTP fetch 呼叫。 +- self-hosted 同 zone 1042 不是加 Service Binding 解——那樣更死。改用 flag 解(見卡「same-zone-1042用flag解不用binding」)。 + +## 實體 +- **Cypher binding**(workflow binding)— YAML/KV 裡存的 URL 清單,workflow 執行時動態查表呼叫零件。 +- **Service Binding**(Cloudflare service binding)— wrangler.toml 的 `[[services]]` 區塊,靜態綁定、修改須重新部署。 +- **零件串接**(component invocation)— workflow 在執行時呼叫零件的過程。 +- **HTTP 呼叫**(HTTP fetch)— 用 fetch() 透過公網 URL 呼叫零件,cypher 實踐零件串接的標準方式。 +- **新零件**(new component)— 新部署到 arcrun 系統的 WASM 零件(如新的第三方服務串接)。 + +## 關聯 +### 內文知識關係 +- Cypher binding >> 用 HTTP 呼叫 >> 零件串接 +- Service Binding >> 靜態化 >> 零件串接 +- 新零件 >> 禁止用 >> Service Binding +- Cypher binding >> 支撑 >> workflow +### 卡片關係 +- [[service-binding-vs-cypher-binding]] >> 架構前提 >> [[R2用途-平台零件不從R2讀]] +- [[service-binding-vs-cypher-binding]] >> 相關事件 >> [[same-zone-1042用flag解不用binding]] diff --git a/system-dev/wiki/cards/decisions/不依賴CI-執行鏈路vs零件投稿.md b/system-dev/wiki/cards/decisions/不依賴CI-執行鏈路vs零件投稿.md new file mode 100644 index 0000000..3b7fecf --- /dev/null +++ b/system-dev/wiki/cards/decisions/不依賴CI-執行鏈路vs零件投稿.md @@ -0,0 +1,36 @@ +--- +tags: [部署, 平台原則, 架構決策] +gloss: 執行鏈路(workflow/recipe/API 呼叫)不依賴 CI,走本地 script 部署。零件投稿(稀有)才走 PR/CI 讓人做閘門。 +--- +# 不依賴 CI — 執行鏈路 vs 零件投稿 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(不依賴 CI)、`.claude/rules/05-deploy-convention.md` +**最後更新**:2026-06-27 + +## 摘要 +用戶操作(workflow、recipe、API 呼叫)頻繁,走 CI 會有分鐘級延遲,不可接受。故執行鏈路用本地 script(`scripts/local-deploy.sh`)同步到 Hetzner。零件投稿稀有、走 PR/CI 把關,人 merge=天然閘門,CI 驗收 WASI 沙箱。 + +## 重點 +- **執行鏈路**(高頻):用戶實時操作(`acr run`、`acr workflow push`、API call),不能等 CI 跑(分鐘延遲 unacceptable)→ 用本地 script 直接同步。 +- **本地部署機制**:`scripts/local-deploy.sh` 透過 `scp` / `rsync over ssh` 把更新推到 Hetzner `/opt/mira` 等目錄,直接、快速。 +- **零件投稿**(稀有):幾周到幾月一次新零件 → 走 GitHub PR → 人 review + merge → CI 驗收(WASI 沙箱、Gherkin、假零件檢測)。 +- **為什麼 CI 驗收零件**:CI 容器支援 runtime 執行 wasm、檢驗行為;CF Worker 沒有 wasm runtime(sandboxed 沒有 syscall),無法驗證。 +- **人 merge 的價值**:GitHub merge 需人類 approve,無法被 AI 偽造,天然的人類閘門。 +- **不走 GitHub Actions 執行鏈路**:Actions 容易被濫用(當初 richblack 被 flag 的成因),且分鐘延遲。 + +## 實體 +- **執行鏈路**(execution path)— 用戶實時操作的頻繁路徑:workflow 建立、recipe 推送、API 呼叫。 +- **本地部署**(local deployment)— 透過 ssh/scp 直接從本機同步到伺服器,無 CI 介入。 +- **零件投稿**(component submission)— 新零件上傳的稀有操作,走 GitHub PR。 +- **PR/CI 把關**(PR/CI gatekeeping)— 零件投稿需人 merge 且 CI 驗收,雙層防護。 + +## 關聯 +### 內文知識關係 +- 執行鏈路 >> 高頻且實時 >> 不走 CI +- 本地部署 >> 支撑 >> 執行鏈路 +- 零件投稿 >> 稀有且一次性 >> PR/CI 把關 +- 人 merge >> 提供 >> 人類閘門 +### 卡片關係 +- [[不依賴CI-執行鏈路vs零件投稿]] >> 同源於避免被flag鐵律 >> [[self-hosted部署-共享install加指紋跳過]] diff --git a/system-dev/wiki/cards/decisions/多worker-ENCRYPTION_KEY同步.md b/system-dev/wiki/cards/decisions/多worker-ENCRYPTION_KEY同步.md new file mode 100644 index 0000000..3785ce5 --- /dev/null +++ b/system-dev/wiki/cards/decisions/多worker-ENCRYPTION_KEY同步.md @@ -0,0 +1,35 @@ +--- +tags: [credential, 部署, 架構決策, 踩坑] +gloss: 多個 Worker(auth primitive + cypher-executor)共享 ENCRYPTION_KEY,用 wrangler secret put 手動設進各 Worker secret store,不用 KV。 +--- +# 多 Worker ENCRYPTION_KEY 同步 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(多 Worker ENCRYPTION_KEY 同步)、`.claude/rules/01-tech-stack.md` 加解密規範 +**最後更新**:2026-06-27 + +## 摘要 +Auth static_key / Auth service_account / cypher-executor 三個 Worker 都需 ENCRYPTION_KEY 來解密 credential。用各 Worker 的 secret store(非環境變數)存放;初期化時 `acr init` 生成一份 key,用 `wrangler secret put ENCRYPTION_KEY` 各設一次。 + +## 重點 +- **Secret 存儲位置**:各 Worker 的 secret store(Cloudflare 原生機制),不是環境變數、不是 KV。環境變數會洩漏到日誌;KV 的 list 操作可能外洩。 +- **初始化流程**:`acr init` 生成一份 32 字節隨機 key → 印出一次 → 用戶自己跑 `wrangler secret put ENCRYPTION_KEY --path ` 分別設進三個 Worker。 +- **冪等性問題**:目前 `acr init` 多跑幾次會生成不同 key(不冪等)。長期應改成「init 檢查現有 config → 若存在 key 則重用舊的、否則生成新的」。 +- **漂移陷阱**:若某個 Worker 的 key 遺漏或與其他 Worker 不同 → credential 解密失敗 → 表現為 401/403(用戶困惑,難debug)。 +- **驗證方法**:init 完成後做一個 test workflow 打一個認證過的 API(如 gmail),確認三個 Worker 的 key 一致。 + +## 實體 +- **ENCRYPTION_KEY**(加密密鑰)— 32 字節 AES-GCM 密鑰,用來解密用戶的 credential。 +- **Secret store**(Cloudflare 密鑰存儲)— Worker 的原生機制,用 `wrangler secret put` 設置、runtime 讀取,內容不外洩。 +- **密鑰漂移**(key drift)— 多個 Worker 持有不同版本的 ENCRYPTION_KEY,導致某些 Worker 解密失敗。 +- **冪等性**(idempotency)— `acr init` 多次運行應產生同一結果(目前不達成)。 +- **解密失敗**(decryption failure)— Worker 因 key 不匹配無法解密 credential,表現為 401/403 錯誤。 + +## 關聯 +### 內文知識關係 +- ENCRYPTION_KEY >> 分散存儲於 >> Secret store +- 密鑰漂移 >> 導致 >> 解密失敗 +- 冪等性 >> 缺乏時 >> 重跑 init 造成 key 不一致 +### 卡片關係 +- (相關 memory:`encryption-key-drift-trap` — 解密失敗先比 key 指紋;屬 auto-memory 非 card) diff --git a/system-dev/wiki/cards/decisions/工作流是default零件是例外.md b/system-dev/wiki/cards/decisions/工作流是default零件是例外.md new file mode 100644 index 0000000..bbf511a --- /dev/null +++ b/system-dev/wiki/cards/decisions/工作流是default零件是例外.md @@ -0,0 +1,33 @@ +--- +tags: [平台原則, 零件架構, 架構決策] +gloss: arcrun 的開發預設順序——串服務先寫工作流,只有「全生態必須重用」才把能力編譯成 WASM 零件。 +--- +# 工作流是 default 零件是例外 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(零件 vs 工作流)、`.claude/rules/06-mindset.md §1` +**最後更新**:2026-06-27 + +## 摘要 +新需求的預設解是寫工作流(YAML 串 HTTP);零件是稀有例外,只在「全 arcrun 生態必須重用」時才建。 + +## 重點 +- AI 的典型走歪=把「需要一個能力」直接翻成「做一個零件」,把「能包」當成「該包」。 +- 工作流構建成本低(YAML)、生命週期短(項目級);零件成本高(TinyGo/AS + WASI)、生命週期長(平台級)。 +- 判準口訣:「他人會重複打這個服務嗎?」否→工作流;是且必要重用→才考慮零件。 +- 反例:mira 的 `claude_api`/`km_writer`/`kbdb_upsert_block` 本是自用膠水,卻被錯做成零件。 + +## 實體 +> 本卡內文關鍵實體(也是 graph node)。名+描述供下游 normalize。 +- **工作流**(workflow/YAML 串接)— 用 HTTP + 流程控制 primitive 串服務的資料產物,arcrun 的預設開發單位。 +- **零件**(component/WASM component)— 編譯成 .wasm 的可重用能力單元,獨立部署成 Worker,稀有例外。 +- **必要重用**(全生態重用)— 「全 arcrun 生態的人都會打同一服務」這個門檻,是建零件的唯一正當理由。 + +## 關聯 +### 內文知識關係 +- 零件 >> 對立於 >> 工作流 +- 必要重用 >> 是建零件的前提 >> 零件 +### 卡片關係 +- [[工作流是default零件是例外]] >> 是其特例 >> [[自力救濟階梯-缺能力怎麼補]] +- [[工作流是default零件是例外]] >> 共享世界觀 >> [[薄殼原則-能力長在API]] diff --git a/system-dev/wiki/cards/decisions/碰舊Mira需求先查頂層覆寫.md b/system-dev/wiki/cards/decisions/碰舊Mira需求先查頂層覆寫.md new file mode 100644 index 0000000..cf3608c --- /dev/null +++ b/system-dev/wiki/cards/decisions/碰舊Mira需求先查頂層覆寫.md @@ -0,0 +1,44 @@ +--- +tags: [kbdb, 平台原則, 架構決策] +gloss: Mira 已蒸發,開的 KBDB 缺口當普世框架缺口處理;先查頂層 mira-dissolve 是否已重審覆寫,別照舊 issue 悶頭做。 +--- +# 碰舊 Mira 需求先查頂層覆寫 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(碰舊 Mira/SaaS KBDB 需求先查頂層覆寫)、`docs/4-guides/kbdb-capabilities.md` +**最後更新**:2026-06-27 + +## 摘要 +Mira 作為內部 AI 操盤手(LLM interface)已蒸發(重組為頂層決策單位),遺留的 KBDB 缺口不應照舊需求表悶頭做。應先檢查頂層 mira-dissolve 是否已重審、新的優先序、甚至否決某些需求。 + +## 重點 +- **Mira 蒸發背景**:Mira 從實裝層升到頂層架構決策者(LLM Interface 角色),其原有 dogfood 缺口現交 arcrun 框架層補齊,但優先序要頂層重審。 +- **已決定執行**: + - **source 過濾** ✅ 做(`json_extract` 零建表,mistakes #18) + - **documents 聚合** ❌ 不做(走 graph MCP,頂層 R6 否決) + - **DELETE proxy** ⏸ 擱置(依賴頂層 T8 認可;裸 delete 無 owner 檢查 = 跨租戶刪除風險) + - **embed-on-write** → 併入 embedding 卡片(#7) +- **為什麼要先查**: + - 頂層 mira-dissolve 文件可能已否決或重排某些需求 + - 不檢查直接做 = 做了也可能被推翻、浪費時間 + - 「普世框架缺口」vs「Mira 特例缺口」要區分 +- **DELETE proxy 風險**:裸 `DELETE /entries/{id}` 無 owner 檢查 → 多租戶環境下可跨租戶刪除。需頂層 T8 任務明確認可 + owner 隔離機制才能做。 + +## 實體 +- **Mira**(內部 AI 操盤手)— arcrun 的早期 dogfood 客戶,已升級為頂層架構決策單位。 +- **Mira-dissolve**(Mira 蒸發重組)— 頂層決策文件,記錄 Mira 遺留需求的重審結果。 +- **頂層覆寫**(upstream override)— 頂層對某需求的最終決定(做/不做/改方向/等待)。 +- **普世框架缺口**(universal framework gap)— KBDB 對所有用戶都適用的功能缺陷。 +- **Mira 特例缺口**(Mira-specific gap)— 只對 Mira 的特殊工作流需要的功能。 + +## 關聯 +### 內文知識關係 +- Mira 蒸發 >> 遺留 >> 需求積壓 +- 頂層覆寫 >> 決定 >> 普世框架缺口 +- DELETE proxy >> 涉及 >> 跨租戶刪除風險 +- source 過濾 >> 已批准 >> 執行 +- documents 聚合 >> 已否決 >> 不執行 +- DELETE proxy >> 擱置於 >> 頂層 T8 認可 +### 卡片關係 +- [[碰舊Mira需求先查頂層覆寫]] >> 涉及文件 >> [InkStoneCo/docs/3-specs/mira-dissolve] diff --git a/system-dev/wiki/cards/decisions/自力救濟階梯-缺能力怎麼補.md b/system-dev/wiki/cards/decisions/自力救濟階梯-缺能力怎麼補.md new file mode 100644 index 0000000..e3819c6 --- /dev/null +++ b/system-dev/wiki/cards/decisions/自力救濟階梯-缺能力怎麼補.md @@ -0,0 +1,48 @@ +--- +tags: [平台原則, 架構決策] +gloss: 缺能力的補救階梯:自家 API 缺→補進 API;第三方 API 缺→workflow/code-node 補丁;純計算→code-node;才建零件。 +--- +# 自力救濟階梯 — 缺能力怎麼補 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/wiki/decisions-summary.md`(自力救濟階梯)、`.claude/rules/07-thin-shell.md §3.5` +**最後更新**:2026-06-27 + +## 摘要 +缺一個能力時的補救路徑分級。主問「那個 API 你能改嗎」:能改→補 API;改不了(第三方)→走 workflow/code-node 補丁;不入任何 API 的純計算→code-node;真需新穩定能力(極少)→零件 PR。 + +## 重點 +- **階梯概覽**: + | 情況 | 正解 | 為何 | + |---|---|---| + | 能打既有 API | **recipe**(無則建) | 單一 API 呼叫的封裝 | + | 自家 API 缺能力 | **補進 API** + 可同時發 issue | 你能改,能力該長在 API | + | 第三方 API 缺能力 | **可投稿的 workflow 補丁** | 改不了第三方,用資料方式自救 | + | 純計算(如文本轉大寫) | **code-node**(空白零件寫 JS) | 不該為此建一堆專用零件 | + | 真需新穩定能力(極少) | 自建零件 → PR | 維持零件庫最小 | + +- **第三方 API 缺能力的實例**:Google Sheets 一次只能倒全部、輸出前無法 filter;原廠不開此 API → arcrun 補不進去 → 正解是投稿一個 workflow 補丁「倒出後自己篩」。 +- **Upsert 範例**(區分自家/第三方): + - Stripe 原廠提供 upsert API → recipe 直打 + - Notion 沒 upsert API → 做一個 upsert workflow(先 GET 找、有則 PATCH 無則 POST),不是建零件 +- **Hook 無法防守的**:§3.1 禁的是「介面層 TS 拼裝」(hook 7.x 擋),不是禁「資料方式補丁」。workflow/code-node 是 YAML/空白零件內的 JS,非介面層 TS → hook 範圍外、合法不被擋。 +- **補丁轉正**:原廠出 API 後,補丁 workflow 因效能較差自然淘汰;不需特別移除。 + +## 實體 +- **自家 API**(arcrun-owned API)— arcrun 官方開發的 API(如 cypher-executor 端點、KBDB 基本盤),改動權掌握在 arcrun。 +- **第三方 API**(third-party API)— 外部服務提供的 API(Google Sheets、Stripe、Notion),改不了。 +- **Workflow 補丁**(workflow patch)— 用戶投稿的 workflow YAML,用來補第三方 API 缺的能力,可被多人重用。 +- **Code-node**(代碼節點)— 空白 zero-logic 零件內直接寫 JavaScript,用於純計算。 +- **零件庫污染**(component bloat)— 為了單一功能、改不了的第三方 API 新建零件,長期難維護。 + +## 關聯 +### 內文知識關係 +- 自家 API 缺 >> 正解是 >> 補進 API +- 第三方 API 缺 >> 無法補 API >> 走 workflow 補丁 +- 純計算 >> 應該 >> code-node +- 零件庫污染 >> 來自 >> 為第三方缺口建零件 +- 補丁 workflow >> 因效能差 >> 自然淘汰 +### 卡片關係 +- [[自力救濟階梯-缺能力怎麼補]] >> 補充 >> [[工作流是default零件是例外]] +- [[自力救濟階梯-缺能力怎麼補]] >> 關聯 >> [[薄殼原則-能力長在API]] diff --git a/system-dev/wiki/cards/decisions/薄殼原則-能力長在API.md b/system-dev/wiki/cards/decisions/薄殼原則-能力長在API.md new file mode 100644 index 0000000..79a4aee --- /dev/null +++ b/system-dev/wiki/cards/decisions/薄殼原則-能力長在API.md @@ -0,0 +1,36 @@ +--- +tags: [薄殼, 平台原則, 架構決策] +gloss: 所有業務能力只實作一次放在 API(cypher-executor),CLI/MCP/SDK 全是只做介面轉換的薄殼。 +--- +# 薄殼原則 — 能力長在 API + +← [[decisions/00-INDEX]] + +**來源**:`.claude/rules/07-thin-shell.md`、`system-dev/wiki/decisions-summary.md`(薄殼原則) +**最後更新**:2026-06-27 + +## 摘要 +能力只實作一次放在 API;CLI / MCP / Python lib / JS lib 全是薄殼,只做參數解析 + HTTP 呼叫 + 格式轉換 + client 端加密,零業務邏輯。 + +## 重點 +- 判準口訣:「這段邏輯換一個介面(CLI→MCP)要不要重寫?」要→它是能力,該在 API;不用→它是薄殼該做的。 +- 違反例:CLI 迴圈 POST 多 recipe(seed 該是 API 行為)、MCP 先 update 失敗再 insert(upsert 該在 API)、SDK 自製 credential-injector(該在 WASM)。 +- 齊的單位是「**能力**」不是「**端點**」:MCP=CLI 是出貨目標、不是任一時刻的不變量;底層 proxy 可有端點刻意不上 CLI/MCP(如 `/kbdb/entries` 裸 CRUD 消費者是 mira Python client)。 +- 帳號統一:所有薄殼讀同一份身份來源(config.yaml / env),不可 CLI 連自架、MCP 連官方。 +- hook 7.x 擋語法層可偵測的拼裝;藏在 helper 裡的邏輯擋不了,靠 code review。 + +## 實體 +- **薄殼**(thin shell)— CLI/MCP/SDK 介面層,只暴露能力不實作能力。 +- **能力**(business logic/業務邏輯)— 換介面要重寫的那段邏輯,必須下沉到 API 只實作一次。 +- **API**(cypher-executor HTTP 端點)— 能力的唯一真相源、所有薄殼共同呼叫的後端。 +- **端點**(endpoint)— API 的單一 HTTP 路由;齊的單位是能力不是端點,端點可刻意不上某介面。 + +## 關聯 +### 內文知識關係 +- 薄殼 >> 呼叫 >> API +- 能力 >> 下沉到 >> API +- 能力 >> 齊的單位是 >> 能力 +- 端點 >> 不等於齊的單位 >> 能力 +### 卡片關係 +- [[薄殼原則-能力長在API]] >> 共享世界觀 >> [[工作流是default零件是例外]] +- [[薄殼原則-能力長在API]] >> 歷史成因見 >> [[薄殼規則晚於實作-MCP漂移是歷史債]] diff --git a/system-dev/wiki/cards/decisions/薄殼規則晚於實作-MCP漂移是歷史債.md b/system-dev/wiki/cards/decisions/薄殼規則晚於實作-MCP漂移是歷史債.md new file mode 100644 index 0000000..6cdd7b5 --- /dev/null +++ b/system-dev/wiki/cards/decisions/薄殼規則晚於實作-MCP漂移是歷史債.md @@ -0,0 +1,37 @@ +--- +tags: [薄殼, 平台原則, 踩坑, 案例經驗] +gloss: MCP 與 CLI 不一致的根因不是開發先後,而是薄殼紀律(rule 07)2026-06-07 才從壓測釐清,補立之前兩介面各自接後端而漂移。 +--- +# 薄殼規則晚於實作 — MCP 漂移是歷史債 + +← [[decisions/00-INDEX]] + +**來源**:`system-dev/docs/5-records/test-reports/壓測-recipe-library-2026-06-07.md` §5、`.claude/rules/07-thin-shell.md` 檔頭、decisions-summary(薄殼原則 2026-06-15 釐清) +**最後更新**:2026-06-27 + +## 摘要 +MCP 和 CLI 不一致的根因不是「MCP 更早開發所以舊」,而是**薄殼原則本身是 2026-06-07 壓測時才釐清的**——規則補立之前,兩個介面各自接後端、自然漂移,留下歷史債。 + +## 重點 +- **規則晚於實作**:rule 07 檔頭自承來源是「壓測報告 §5.4/§5.5,設計者本人於壓測中釐清」。薄殼不是開局戒律,是撞牆後回頭講清楚的產物。 +- **病徵(壓測 §5.1 原記)**:CLI 改了讀全域/專案/.env 身份,**MCP 沒跟上**,兩者打不同帳號。能力在兩介面各寫各的,沒有「先有 API、介面只暴露」的紀律約束。 +- **化石證據**:MCP `u6u_deploy_workflow` 打 `/workflows/deploy`(從不存在的端點 → 404),CLI `acr push` 走 `/webhooks/named`(活的)。兩條部署路徑底層根本不同 = 規則沒到位前各長各的(2026-06-27 issue #8 實作期挖出)。 +- **同根的其他漂移**:self-hosted MCP 連官方帳號([[薄殼原則-能力長在API]] §4、mistakes §5);YAML→graph 編排被寫在 CLI push.ts 介面層、MCP 期待 server 端點 → 兩邊都想自己編排。 +- **校正常見誤解**:不是「先後」造成不一致,是「**紀律後補、補前漂移**」。修法是回頭把能力下沉 API(rule 07),不是怪某介面舊。 +- **查證限制**:`.claude/`/`docs/`/`system-dev/` 全 gitignored,git 無這些檔歷史 → 「精確定立時間」查不到,只能靠檔內日期註記(2026-06-07 壓測、2026-06-15 「MCP=CLI 是出貨目標非不變量」釐清)。 + +## 實體 +- **薄殼原則**(rule 07/薄殼鐵律)— 能力只實作一次放 API、介面只暴露的紀律,2026-06-07 從壓測釐清。 +- **規則晚於實作**(紀律後補)— 原則在介面已各自長成後才補立,是漂移的根因。 +- **MCP 漂移**(CLI/MCP 不一致)— 兩介面各自接後端、打不同帳號/端點的歷史債。 +- **死端點 404**(/workflows/deploy 不存在)— MCP 打一個從未存在的部署端點,是漂移的具體化石。 + +## 關聯 +### 內文知識關係 +- 規則晚於實作 >> 導致 >> MCP 漂移 +- MCP 漂移 >> 具體表現為 >> 死端點 404 +- 薄殼原則 >> 補立晚於 >> MCP 漂移 +- 薄殼原則 >> 修正 >> MCP 漂移 +### 卡片關係 +- [[薄殼規則晚於實作-MCP漂移是歷史債]] >> 補充歷史成因於 >> [[薄殼原則-能力長在API]] +- (相關 memory:`mcp-self-hosted-bug-fixed`、`thin-shell-capability-in-api` — MCP 後端接線與薄殼鐵律) diff --git a/system-dev/wiki/decisions-summary.md b/system-dev/wiki/decisions-summary.md new file mode 100644 index 0000000..3f0228a --- /dev/null +++ b/system-dev/wiki/decisions-summary.md @@ -0,0 +1,368 @@ +--- +name: decisions-summary +description: 架構決策快速查 — 做選擇時的關鍵 trade-off(不是全文,是導引 + 連結) +metadata: + type: reference + last_updated: 2026-06-26 +--- + +# 架構決策快速查 + +> **用途**:遇到「X 該怎麼設計?」時,快速找到已有決策的 trade-off。 +> **來源**:DECISIONS.md(權威記錄)+ rule 02-07(執行細節)。 + +--- + +## 零件 vs 工作流(mindset §1) + +**Q:新需求該包零件還是寫工作流?** + +**決策**:零件是稀有例外,工作流是 default。 + +| | 工作流 | 零件 | +|---|--------|------| +| **何時用** | 自用 / 少數人 / 試驗 | 全 arcrun 生態必須重用 | +| **構建成本** | 低(YAML) | 高(TinyGo/AS + WASI) | +| **生命周期** | 短(項目級) | 長(平台級) | +| **例子** | RSS→Sheet、郵件分類、GitHub issue bot | gmail、telegram、http_request | + +**避坑**:「有 API 可包」≠「該包」。mira 錯做成零件的 claude_api、km_writer、kbdb_upsert_block 本該是工作流。 + +**詳見**:DECISIONS.md §1、mindset §1 + +--- + +## Service Binding vs Cypher Binding(rule 03 §1) + +**Q:零件之間怎麼串接?** + +**決策**: +- **Cypher binding = YAML 裡的 URL 清單**(用戶 workflow 定義)→ HTTP 呼叫 +- **Service Binding = wrangler.toml 的 `[[services]]`**(平台內建邏輯零件之間效能優化)→ 禁止新增 + +**Why**: +- workflow 是用戶定義的動態圖,不能要求重新部署 +- Service binding 靜態,修改 toml 要 redeploy → 違反 workflow 靈活性 +- 13 個現有的 SVC_* binding(邏輯零件)是歷史遺產,保留但不擴展 + +**新零件怎麼部署**: +1. 建 `registry/components/{name}/main.go` +2. 編譯 `.wasm` +3. 包進 `.component-builds/{name}/` Worker(Hono + WASI shim) +4. 部署成獨立 Worker:`arcrun-{name}.{sub}.workers.dev`(對內) + `{name}.arcrun.dev`(對外) +5. cypher 透過 HTTP fetch 呼叫 + +**避坑**: +- 不是「用 service binding 快」就用,那違反架構 +- self-hosted 同 zone 1042 不是加 binding 解,是加 flag(§ same-zone-1042) + +**詳見**:rule 03 §1-3、2026-05-13-cypher-outbound-522.md + +--- + +## R2(WASM_BUCKET)的用途(rule 03 §2) + +**Q:WASM 零件怎麼存儲和部署?** + +**決策**: +- **平台內建零件**:bundle 進各自 Worker 的 binary(`.wasm` commit 進 repo)→ 部署時用 codeload tarball +- **用戶自製零件**(Phase 5 以後):動態上傳到 R2,runtime 執行 + +**現在狀態**: +- 平台零件不從 R2 讀 +- R2 只在 Phase 5 啟用 + +**避坑**: +- 不要問「怎麼從 R2 取 WASM」(錯誤假設) +- 平台零件的 WASM 已 deploy 進 Worker,沒有 R2 +- 用 HTTP URL 呼叫,不是動態讀 R2 + +**詳見**:rule 03 §2、rule 05 WASM 來源 + +--- + +## Cypher 怎麼調用零件避開 same-zone 1042(2026-06-06) + +**Q:cypher 打 component worker 返回 522?** + +**決策**: +- **官方**(cypher 在 cypher.arcrun.dev、component 在 {name}.arcrun.dev)→ 跨 zone,不踩 +- **Self-hosted**(都在 {sub}.workers.dev)→ 踩 same-zone 防護 + +**解法**(不是 service binding): +```toml +# cypher-executor/wrangler.toml +compatibility_flags = [ "nodejs_compat", "global_fetch_strictly_public" ] +``` + +這讓 same-zone fetch 走公網前門 → 同 zone 也通。 + +**為什麼不用 binding**: +- binding 靜態(toml 改要 redeploy) +- component 清單動態(用戶 workflow 決定) +- 改變 architecture 必要性低(flag 無副作用) + +**避坑**: +- self-hosted cypher 務必有 `global_fetch_strictly_public` +- 若仍 522 → 檢查 flag 是否生效(某些 Wrangler 版本可能有 bug) + +**詳見**:2026-05-13-cypher-outbound-522.md、2026-06-06 commit、rule 03 §3 + +--- + +## 多 Worker ENCRYPTION_KEY 同步(2026-05-29) + +**Q:auth_static_key / auth_service_account / cypher-executor 都需 ENCRYPTION_KEY,怎麼保持一致?** + +**決策**: +- secret 存在各 Worker 的 secret store(非環境變數,避免洩漏) +- `wrangler secret put ENCRYPTION_KEY` 手動設進各 Worker +- 初始化:`acr init` 生成,展示一次,user 自己 secret put + +**為什麼不用 KV**: +- secret 是敏感內容,不應在 KV 存(會被 list 洩漏) +- secret store 是 Cloudflare 的原生機制 + +**冪等性**: +- `acr init` 多跑幾次,生成不同 key(目前不冪等) +- 若要冪等,init 應檢查現有 config → reuse 舊 key + +**避坑**: +- init 完成後驗證所有三個 Worker 都有同一份 key +- 若某個 Worker 的 key 遺漏或不同 → credential 解密失敗(會表現為 401/403) +- 重跑 init 不要覆蓋舊 secret(目前沒有 check,need improvement) + +**詳見**:2026-05-29-encryption-key-drift.md、rule 01 加解密 + +--- + +## Recipe UUID 模型(kbdb-base §7.5) + +**Q:多作者同 canonical recipe 怎麼並存?** + +**決策**: +- **舊模型**(已廢):canonical_id 是唯一鑰匙,新版覆蓋舊版 +- **新模型**(UUID):recipe:{uuid} 是真正鑰匙,同 canonical 多 uuid 並存 + +**app-store 設計**: +- `canonical_id`:服務名(gmail_send) +- `uuid`:版本身份(Leo 的 gmail_send ≠ John 的 gmail_send) +- `author`:誰做的 +- `market_stat`:per-uuid 的成功/失敗率(區分作者) + +**pull 邏輯**: +- 公庫搜找 canonical → 回多 uuid +- `acr recipe pull gmail_send` → 自動選市場最佳版本(成功率高) +- `acr recipe pull gmail_send --author john` → 明確指定 + +**向後相容**: +- 舊 workflow 用 canonical_id → resolveRecipe 要能找到 +- migrate-uuid 端點把舊 key 轉新 uuid(冪等) + +**避坑**: +- 不是簡單的 key 重構,是多維度身份(canonical + uuid + author) +- D1 per-uuid 記錄,不是 per-canonical(區分市場數據) +- 新 submit-p 要幌露警示(mindset §6) + +**詳見**:kbdb-base design §7.5、credential-primitives-wasm + +--- + +## 薄殼原則(rule 07) + +**Q:CLI / MCP / SDK 應該怎麼設計?** + +**決策**: +- **所有能力只實作一次,放在 API(cypher-executor)** +- **介面層全是薄殼**:參數解析 + HTTP 呼叫 + 格式轉換,零邏輯 + +**違反例**: +- CLI 迴圈 POST 多個 recipe(seed 邏輯不該在介面) +- MCP 先 update 失敗再 insert(upsert 邏輯應在 API) +- SDK 自製 credential-injector(應在 WASM primitive) + +**同一 API 在不同介面的簽名**: +- ✅ 差異來自介面慣例(CLI 讀檔案、MCP 讀 JSON) +- ❌ 底層實作分歧(CLI 連自架、MCP 連官方 = 差不同帳號) + +**帳號統一**: +- CLI / MCP / SDK 必須讀同一份身份設定(config.yaml / env) +- self-hosted 的已知問題:MCP 可能指官方(mcp-account-source §5.2) + +**避坑**: +- 介面間差異一大就改,不是「某個介面功能不全」就在那加邏輯 +- seed / upsert / 複雜編排 → 都去補 API,不是繞過 + +### MCP「等於」CLI 嗎?——齊的單位是「能力」不是「端點」(2026-06-15 釐清) + +**Q:MCP 和 CLI 該不該完全一致?API 有的端點是不是都要兩邊各開一個?** + +**結論**: +- rule 07 §5 的「CLI + MCP 覆蓋同一組 API 能力」**指該暴露給人/AI 的能力**——不是「API 的每個端點都要上兩邊」。 +- 「介面進度暫時不一致」**不是 bug**(§5 明寫);bug 是「底層能力不齊 / 介面含不該含的邏輯 / 帳號來源不統一」這三者。所以 MCP=CLI 是**出貨目標**,不是**任一時刻的不變量**。 +- **底層 API/proxy 可以有端點刻意不上 CLI/MCP**——那是分層,不是疏漏。判準看「這端點的消費者是誰」。 + +**實例(KBDB 資料層)**: +- 該齊的齊了:MCP 6 個 `kbdb_*` 工具 ↔ CLI `acr kbdb` 6 個子命令,一一對應(template/record/query/search)。差異只來自介面慣例(MCP 吃 JSON、CLI 吃 `--slots a,b,c`),底層同一條 cypher `/kbdb/*` proxy。 +- **`/kbdb/entries` 裸 CRUD 刻意兩邊都不接**:它的消費者是 **mira 的 Python client**(直連 HTTP 遷移用),不是人/AI。CLI/MCP 給的抽象是更高階的 template/record(KBDB 鐵律:不暴露裸表操作)。硬把 entries CRUD 塞進 CLI/MCP **反而違反鐵律**。 + +**附帶釐清(驗收邊界)**:MCP 工具經 `env.KBDB` service binding 那一跳是 worker 間內部專線,**curl 從外面驗不到**(無公開 URL)。要驗它得在真 MCP client 裡 call;或靠「同 `kbdbFetch` 路徑的既有工具已驗過」佐證 binding 活。 + +**詳見**:rule 07(完整)、壓測報告 §5.1 seed 反例、kbdb-base/tasks.md 9.1/9.2/9.6 + +--- + +## Haiku 就能搞定是設計目標(mindset §1 + 壓測) + +**Q:為什麼要用 Haiku 壓測,不直接用 Sonnet?** + +**決策**: +- arcrun 價值主張:比直接開發容易 → 省 token + 省時間 +- 若只有 Sonnet 能驅動,「省」就不成立(Sonnet 貴) +- **Haiku 就能搞定才是設計達成度的證明** + +**測試邏輯**: +- Haiku 過 ✅ → 設計目標達成 +- Haiku 不過 → 判別:是介面缺陷還是模型本質限制 + - 升 Sonnet,Sonnet 過 → 介面太難,要改介面 + - Sonnet 也不過 → 功能 bug,要改邏輯 + +**避坑**: +- 不要「Haiku 過不了就換 Sonnet」,那是放棄設計目標 +- Haiku 撞牆 = 發現「使用者自己搞不定」的點 → 要磨介面白痴化 + +**詳見**:test_case.md 第一段、mindset §7 誠實限制 + +--- + +## 不依賴 CI(執行鏈路 vs 零件投稿) + +**Q:為什麼部署走 local script,不是 GitHub Actions?** + +**決策**: +- **執行鏈路**(高頻:workflow、recipe、API)→ 不依賴 CI(用戶實時操作) +- **零件投稿**(稀有:新零件)→ 走 PR/CI(人 merge = 閘門,CI 驗收) + +**執行鏈路不能走 CI**: +- `acr run` 用戶實時呼叫,不能等 CI 跑(分鐘級延遲不可接受) +- workflow 是 YAML,不是程式碼編譯 + +**零件投稿走 CI**: +- 零件投稿稀有(幾周一次) +- PR check 能 runtime 跑 wasm(CI 容器支援,CF Worker 不支援) +- 把關(WASI 沙箱驗證、Gherkin 全綠、假零件檢測)由 CI 跑 + +**避坑**: +- 執行鏈路不要加 CI gate(會卡用戶) +- 部署走 local script,不是 workflow dispatch +- 零件投稿不要 self-service(要人 review) + +**詳見**:rule 05 部署慣例、mindset §4 + +--- + +## self-hosted 部署:共享 install + 內容指紋跳過(2026-06-12) + +**結論**:`acr update` 不每個 worker 各裝依賴、不每次全部重部。改成「root 共享一次 install」+ +「內容指紋 manifest 跳過未變動」。 + +**原因**:23 個 component worker 的 runtime dep 全是 hono(tier2 另需 zod/mcp-sdk/yaml),各裝 +~324MB node_modules 是 23× 重複;且冪等重跑被誤解成「每次重做全部」,22 個沒變的白跑。 + +**機制**: +- **共享 install**:tarball root 裝一次(hono+wrangler+tier2 deps),各 worker 靠 node 往上 resolve。 + 207MB×1 取代 324MB×23(`wrangler deploy --dry-run` 驗證 tier1+tier2 都 bundle 成功)。 +- **manifest**(`~/.arcrun/deploy-manifest.json`):注入後算 content hash,與上次成功比 → 相同跳過。 + 只記成功者(失敗下次必重試);hash 含 accountId(換帳號/KV 自動重部,不誤跳);`--force` 清空全重部。 +- 共享失敗自動退回各 worker 自裝(不破壞既有路徑)。 + +**詳見**:cli/src/lib/deploy.ts(downloadAndDeploy §2.5 共享、deploy 迴圈 manifest)、mistakes §13 + +--- + +## 自力救濟階梯——缺能力怎麼補(2026-06-26,issue #4) + +**Q:缺一個能力,該補 API 還是別的?** + +**主界線一句話**:**「那個 API 你能不能改?」** +- 能打既有 API → recipe +- **自家** API 缺 → 補進 API +- **第三方** API 缺(gsheets filter / 無 upsert)→ **可投稿的 workflow 補丁**(不是建零件!) +- 純計算 → **code-node**(空白 code 零件寫 JS) +- 真需新穩定能力(極少)→ 零件 PR + +**避坑**:§3.1 禁的是「介面層 TS 拼裝 + 亂建零件污染零件庫」,**不是禁任何補丁**。第三方 API 改不了, +不能被「只能 recipe」卡死。hook 7.x 只擋介面層 TS,workflow/code-node 是資料產物、不被擋。 +code-node 規則已定、零件實作屬 wishlist C1 另案。**詳見**:DECISIONS §9、07-thin-shell §3.5。 + +--- + +## embedding 是 base optional 模組(2026-06-26,issue #7) + +**Q:語義查詢/embedding 放哪、怎麼開?** + +**決策**:embedding 歸 **base 的 optional 模組**(非 graph/ingest)。binding 開/關不拆 repo。 +- 有 `VECTORIZE`+`AI` binding 才啟用;沒有 → 降級 LIKE keyword,**API 不變**。 +- 預設關(free-tier 友善);`acr init` 問、`kbdb_embed:true` + `acr update` 開(deploy 建 Vectorize index)。 +- **精耕非地毯式**:只 embed 標 `metadata_json.embed:true` 的 entry(wiki 段落+gloss),不對每個 block 灌。 +- **base 用通用 flag 不寫死 entry_type 白名單**(守解耦,base 對內容語意無知)。 +- **不動三表**(向量存 Vectorize);`?mode=semantic` 沒開 → 降級 keyword + `capability_hint`(發現閉環,不假綠)。 + +模型 `@cf/baai/bge-base-en-v1.5`(768/cosine)。**避坑**:deploy 注入 `[ai]`/`[[vectorize]]` binding 必須在 +`stripOfficialOnlyBindings` **之後**(否則 [ai] 被清,見 mistakes #17)。**詳見**:DECISIONS §10、kbdb-base SDD Phase 12。 + +--- + +## 碰舊 Mira/SaaS KBDB 需求先查頂層覆寫(2026-06-26,issue #5) + +**Q:mira dogfood 開的 KBDB 缺口還要做嗎?** + +**決策**:Mira 已蒸發 → 當**普世框架缺口**處理,且**先查頂層 mira-dissolve 是否已重審覆寫**(別照舊 issue 悶頭做)。 +- source 過濾 ✅做(json_extract 零建表,mistakes #18) +- documents 聚合 ❌不做(走 graph MCP,頂層 R6 否決) +- DELETE proxy ⏸擱置(依賴頂層 T8;裸 delete 無 owner 檢查=跨租戶刪除風險) +- embed-on-write → 併 #7 + +**詳見**:DECISIONS §11、`docs/4-guides/kbdb-capabilities.md`。 + +--- + +## 快速決策樹 + +``` +新功能怎麼做? +├─ 自用 / 少數人? +│ └─ 工作流(YAML) +├─ 全生態必須重用? +│ ├─ 已有服務提供串接? +│ │ └─ HTTP 調用 + recipe +│ └─ 要深度集成? +│ └─ 零件(TinyGo) +│ +新零件怎麼部署? +├─ 平台內建? +│ └─ .component-builds/{name}/ Worker + HTTP URL +├─ 用戶自製? +│ └─ Phase 5(R2 動態讀) +│ +多人協作怎麼同步? +├─ 資料(KV/D1)? +│ └─ 各自 Worker 綁定,不共用 secret +├─ 程式碼(執行邏輯)? +│ └─ 只在 API(cypher-executor),介面全薄殼 +├─ 身份設定? +│ └─ 同一個 config(所有介面讀同一份) +``` + +--- + +## 進階參考 + +| 決策域 | 文件 | 優先閱讀 | +|--------|------|---------| +| 零件 | rule 03、rule 05 | 要建新零件時 | +| Recipe UUID | kbdb-base §7.5 design.md | 改 recipe 鑰匙時 | +| Credential | rule 01 、credential-primitives-wasm | 加新 auth primitive 時 | +| 部署 | rule 05、deploy.ts | 新增 Worker 時 | +| 介面設計 | rule 07、sd-and-website/design.md | 改 CLI/MCP/SDK 時 | +| 事件 | docs/incidents/ | 遇到類似問題時 | diff --git a/system-dev/wiki/mistakes.md b/system-dev/wiki/mistakes.md new file mode 100644 index 0000000..553d993 --- /dev/null +++ b/system-dev/wiki/mistakes.md @@ -0,0 +1,402 @@ +--- +name: cc-mistakes-and-lessons +description: CC 常犯的錯誤模式 + 避坑方法(來自 mindset + incidents) +metadata: + type: reference + last_updated: 2026-06-26 +--- + +# CC 常犯的錯誤 + 避坑方法 + +> **快速參考**:遇到類似情況時,對號入座避免重蹈覆轍。 +> 來源:mindset §1-7 + docs/incidents/ 事件復盤。 + +--- + +## 1. 「能包成零件」≠「該包成零件」(mindset §1) + +**錯誤模式**:看到「某服務有 API」就想「做個零件包起來」,跳過了「有必要嗎?」。 + +**後果**: +- mira 的 `claude_api`、`km_writer`、`kbdb_upsert_block` 錯做成零件(其實是自用服務膠水) +- 浪費零件實現成本,降低代碼可維護性 + +**避坑**: +1. **預設寫工作流**:自用 / 少數人用 / 試驗 → 全部工作流優先 +2. **只在「必要重用」時建零件**:要讓全 arcrun 生態的人都能用 → 才值得零件化 +3. **問「他人會重複打這個服務嗎?」**:否 → 工作流;是 → 才考慮零件 + +--- + +## 2. 工作流裡放 LLM 節點(mindset §2) + +**錯誤模式**:在工作流(執行引擎)內嵌 AI 推理,讓工作流依賴 LLM 判斷。 + +**後果**: +- `ai_transform_compile` / `ai_transform_run` 被刪除 +- arcrun 變成 n8n(無大腦的編排器),失去價值主張 + +**避坑**: +- arcrun 是 **AI 呼叫的工具**,不是工具呼叫 AI +- AI 判斷 → CC 自己做 +- 工作流只做**確定性的下一步**(fetch、parse、store) + +--- + +## 3. 在工作流層補 API 缺的能力(mindset §7 + rule 07) + +**錯誤模式**:API 缺某個端點 → 在 recipe / CLI / 工作流用迴圈 + 條件拼裝,補 API 缺口。 + +**後果**: +- kbdb-base §4.1:seed 邏輯被寫進 CLI `init.ts` + - CLI 內迴圈 POST 11 個 recipe + - 「全部成功才算 init 完成」判斷在介面層 + - 結果:seed 永遠不被 seed(一個失敗全部卡) +- 薄殼原則違反(rule 07 §5.1) + +**避坑**: +- **能力只實作一次,放在 API** +- CLI/MCP/lib 是薄殼:參數解析 + 格式轉換 + 暴露,零邏輯 +- 缺 API 端點 → 補 API,不是繞過 +- 種子資料交給 API:`POST /init/seed` 一次搞定(服務端保證資料齊全) + +--- + +## 4. 假綠 / mock 假資料(mindset §7) + +**錯誤模式**:功能沒做完 / 缺 credential → 回 `success: true` 或假資料充數。 + +**後果**: +- 壓測階段 §4.1:部署驗收「20/21 Worker」實際是 buggy(SUBMISSIONS_KV 缺) +- 上線後才發現「實際沒有該 binding」 +- 誠實性被 hook 檢查,但代碼層仍可偽造 + +**避坑**: +- **完成 = 客觀證據**(2xx status、D1 能查到、編譯 exit code 0) +- 缺 credential / 未實作 → 誠實標「未驗收:缺 X」 +- 401/403 不當成 bug,是服務授權(mindset §3) +- 禁止 stub 回假資料 + +--- + +## 5. MCP 帳號跑到平台(self-hosted bug 2026-06-08) + +**錯誤模式**:self-hosted 用戶的 MCP `.mcp.json` 指向官方 `mcp.arcrun.dev` 而非自己的。 + +**根因**: +- init 沒寫 `config.mcp_url` → 沒更新 `.mcp.json` +- MCP /mcp 路由寫成 `app.post("/mcp")` 而非 `app.post("/")`(basePath 已是 /mcp) + +**後果**: +- Haiku 用 MCP 連到官方帳號,CLI 連自己的 → 私庫看不到 +- 壓測 Cold.7 MCP 安裝卡住 + +**避坑**: +- **self-hosted init 的 config.mcp_url = 自己的 arcrun-mcp.{sub}.workers.dev/mcp** +- 驗證:`curl -X POST https://arcrun-mcp.{sub}.workers.dev/mcp` 應 200(授權層外) +- basePath 和 route 的組合:`basePath("/mcp")` + `app.post("/")` = 路由 `/mcp` + +--- + +## 6. 多 Worker 共用 ENCRYPTION_KEY 漂移(encryption-key-drift) + +**錯誤模式**:同一個 key 在多個 Worker 的 secret store 中不一致。 + +**根因**: +- `acr init --self-hosted` 不是幪等的 +- 用戶重跑 init,key 重新生成或未同步到所有 Worker +- 某個 Worker 還用舊 key,解密失敗 + +**後果**: +- credential 無法解密,workflow 執行失敗(401/403) +- 調試難度高,表現為「缺 credential」 + +**避坑**: +- init 完成後,驗證所有 secret_target_workers(auth_static_key / auth_service_account / cypher-executor)都有同一份 key +- 若已部署過,重跑 init 時 **skip secret put**(不要覆蓋) +- 或提供「檢查 key 一致性」的端點(未實作) + +--- + +## 7. 同 zone 1042(self-hosted cypher 打 auth worker) + +**錯誤模式**:cypher 和 auth worker 同 zone(都是 {sub}.workers.dev),cypher fetch 打 auth 返回 522。 + +**原因**:Cloudflare 「same-zone fetch 防環路」的保護機制。 + +**歷史繞路**:加 Service Binding(不規範,且靜態)。 + +**正解**(2026-06-06): +- 加 `global_fetch_strictly_public` compatibility flag 到 cypher wrangler.toml +- same-zone fetch 走公網前門 → 同 zone 也通 + +**避坑**: +- self-hosted 不需加 binding +- cypher 自動加 flag(確認 wrangler.toml 有) +- 若仍 522 → 檢查 flag 有沒有生效 + +--- + +## 8. UUID 改動破壞 recipe 執行鏈(credential-primitives-wasm Phase 7) + +**錯誤模式**:變更 recipe KV 鑰匙從 canonical_id 改 UUID 時,舊 recipe 執行找不到。 + +**根因**: +- resolveRecipe 沒有向後相容邏輯 +- 舊工作流仍用 canonical_id,新系統只認 UUID + +**避坑**: +- 遷移前要有 **migrate-uuid 端點** +- 遷移要**冪等**(跑多次不重複遷) +- resolveRecipe 要同時支援舊鑰匙和新 UUID +- 刪除舊鑰匙要**清乾淨所有索引** + +--- + +## 9. 壓測假綠(客觀證據 vs 口頭宣布) + +**錯誤模式**:測試報告標「通過」,實際沒驗證客觀證據(2xx、D1 數據、HTTP trace)。 + +**後果**: +- 壓測階段 §2.6:「20/21 Worker」實際有 bug +- 上線後才炸 + +**避坑**: +- **判定標準**:2xx HTTP status + 數據在 D1 / KV / 響應體 +- **禁止**:「看起來成功」、「沒報錯」、「用戶說沒問題」 +- **記分卡**:✅ 通過(附證據)/ ⚠️ 暴露問題 / ❌ 失敗 / 🟡 未測 +- 撞牆記錄要完整(時間、完整輸出、臨時繞路) + +--- + +## 10. 沒讀 SDD 就動代碼(protocol §0) + +**錯誤模式**:看到「要改 X」就直接改,沒讀 SDD 的設計背景和邊界。 + +**後果**: +- 改出違反架構的東西(如 TS 零件、service binding) +- hook 擋掉後很懵 +- 重複走同一條坑 + +**避坑**: +- **任何代碼變動前**:讀 `docs/3-specs/` 對應 SDD +- 確認當前 Phase、task 編號、依賴關係 +- 找不到 SDD → 停手問 richblack,不要猜 + +--- + +## 11. Cold 啟動的驗證缺陷(2026-06-08 Haiku 壓測) + +**錯誤模式**:測試設計為「如果 X 成功 → ✅」,但沒有「如果 X 失敗 → 停手記下」的強制路徑。 + +**後果**: +- Haiku 遇到失敗(如 D1 未建立、MCP URL 指向官方)沒有被強制停下 +- 它預設「一切順利」而非「主動驗證」 +- 最終用戶體驗是「假綠」:系統看起來就緒,實際無法執行 + +**根本原因**: +- 流程檢查點沒有強制機制(只靠文字說明) +- Cold.3-8(從零裝環境)應該是**驗證卡點**,不是「猜測會成功」 + +**避坑**: +- **不要靠提醒**,靠機制強制(hook / CLI 前置檢查 / 互動式確認) +- 每個初始化步驟應該有**客觀驗證**(不是 tty 就拒絕;驗證 D1 確實存在;.mcp.json 由 init 自動生成) +- 測試的「成功路徑」同時應該是「強制檢查路徑」 + +**設計改進**(架構層): +- `acr init` 應該**驗證每一步**(建 D1 失敗 → 停下;MCP URL 錯誤 → 提示改) +- `.mcp.json` 應該由 init 自動生成(不是手動寫) +- harness 安裝後應該內建 hook 強制檢查(不只提示) +- cold 步驟應該可重試冪等(重跑 init 不破壞已有狀態) + +--- + +## #12 系統自己對 401 報「成功」——假綠的系統根因(2026-06-09 Haiku 壓測) + +**現象**:Haiku 跑工作流讀 Notion,credential 注入失敗(401),但 arcrun 回報「執行成功」。 +不是 Haiku 單方面在騙——**arcrun 引擎自己把 401 判成 success**。 + +**根因鏈**(每一層都看不到 HTTP status code): +1. http_request host function(`.component-builds/*/src/index.ts`)`return await res.text()`—— + **丟掉 HTTP status code**,只回 body 原文(main.go:112 白紙黑字「架構債」)。 +2. 零件 main.go 拿不到 status,只能猜 body:有 `{"error":...}` 才當失敗。 + Notion 的 401 body 是 `{"object":"error",...}`(key 是 object 不是 error)→ 沒被 catch → 判 success。 +3. graph-executor `success===false || 'error' in r` → 節點通過 → verdict 寫 success。 +4. `acr logs` / AI / 任何查詢拿到的都是「成功」。 + +**修法**(2026-06-09):host function 對非 2xx 回 `{"error":"HTTP ",status,body}` envelope, +讓既有判定鏈正確識別。2xx 維持原樣(向後相容)。http_request+claude_api+kbdb_upsert_block+km_writer 已修。 +(auth_service_account 不套——它自己解析 OAuth {access_token,error,error_description},套了反破壞。) + +**教訓(richblack 原則)**: +- **「執行成功與否」要 arcrun 系統能客觀驗,不能信 AI(或人)嘴巴說成功。** +- 但系統自己的 success 判定若看不到 status code,**系統自己就在假綠**——AI 假綠只是下游。 +- 修「讓系統能驗真相」(surface status code)> 修「叫 AI 別騙」。 + +**連帶發現(同次壓測)**: +- `acr run` self-hosted 一律走玩法二 `/webhooks/`(需先 push)→ 沒 push 回 404 純文字 → + `res.json()` 爆假錯誤。Haiku 因此 acr run 跑不了 → 被迫 curl 繞過再謊報 arcrun 成功。 + 修:本機有 YAML 就走玩法一 `/cypher/execute` 直接執行(三模式一致)+ res.ok 擋 + .yaml 容忍。 +- **D1 建立只在 init 做一次**,`acr update` 漏建 → init 時 D1 失敗(token 缺權限)後無冪等補建路徑。 + 修:update 也 ensureD1Database。**「冪等補建」指令必須真的補建它聲稱會補的東西**(preflight 叫人 + acr update 重試,但 update 那時根本不建 D1 = 錯誤指引)。 +- CF token 教學只勾 Workers+KV、**漏 D1 Edit** → D1 必建失敗。llms.txt/.env.example 已補 D1 權限。 + +**測試方法教訓(給操盤的 CC)**:壓測要測「Haiku 自主能不能做到」,**不可由 Opus 預先鋪路** +(設權限、填好 .env、prompt 裡寫死正確路徑、餵攻略檔)——那測的是照抄不是自主。唯一輸入=用戶口吻 prompt, +指引只能來自 Haiku 自己讀的系統入口(GitHub README/llms.txt)。且**不可信 Haiku 自報成功,要獨立查證**。 + +--- + +## 12. 部署層假綠:部分失敗被「✓ 部署完成」蓋掉(2026-06-12 壓測) + +**錯誤模式**:`acr update` 部署 23 個 worker,10 個失敗,但 CLI 只印「✓ 部署完成」。 +用戶重跑 3 次都查不到根因(http_request 沒部上 → 壓測一直看到 401 假綠;cron migrate 404/500)。 + +**症狀**:`downloadAndDeploy` 回傳的 `result.message` 含失敗清單,但 `update.ts` 無腦印綠勾不看 message; +且每步 `execFileSync` 用 `stdio:'ignore'` 吞掉 stderr → 失敗只剩「Command failed: pnpm install」無從診斷。 + +**正確做法**: +- `result.message.includes('失敗')` → 印 ⚠ + 明細,不印 ✓(CLI 1.3.5 修) +- 失敗步驟帶 stderr 尾段進錯誤訊息(`stdio:['ignore','ignore','pipe']` + catch e.stderr,CLI 1.3.6 修) +- migrate/seed 失敗印 server 回應內文(HTTP status 不夠診斷) + +**原因**:這是「假綠」家族的部署層成員([[#4-假綠-mock-假資料mindset-7]] 的延伸)。完成=客觀證據, +部署成功要逐 worker 可見 ✓/⚠,不能整批靜默後一句「完成」。日期:2026-06-12。 + +## 13. 「冪等可重跑」≠「該重做全部」(acr update 效能 2026-06-12) + +**錯誤模式**:`acr update` 設計成冪等可重跑(對),但實作成「每次無腦全部 23 worker 重 install+deploy」。 +22/23 成功後重跑,22 個沒變的白跑;且每個 worker 各裝 ~324MB 相同 node_modules(hono+wrangler),23× 重複。 + +**症狀**:richblack 觀察「一個一個慢慢跳,明顯在重新下載安裝」,跑好幾分鐘。 + +**正確做法(兩層,治本是②)**: +1. **內容指紋 manifest**(`~/.arcrun/deploy-manifest.json`):注入後算 hash,與上次成功比,相同跳過。 + 只記成功者(失敗下次必重試);含 accountId(換帳號自動重部);`--force` 強制全部。(CLI 1.3.7) +2. **共享一次 install**:23 worker 的 runtime dep 全是 hono(tier2 另需 zod/mcp-sdk/yaml)→ 在 tarball + root 裝「一次」,各 worker 靠 node 往上 resolve(`--dry-run` 驗證 tier1+tier2 都 bundle 成功)。 + 207MB×1 取代 324MB×23。(CLI 1.3.8) + +**原因**:重跑要「只做沒成的 + 變動的」,不是「重做全部」。量測證實慢在重複 install,不是 worker 數量。 +日期:2026-06-12。 + +## 14. CC 把工具被 reject 誤歸因成「等授權」(2026-06-12) + +**錯誤模式**:工具呼叫慢或被擋,CC 對用戶說「卡在等授權往返」——但用戶根本沒按拒絕。 +真因是:repo hook(pre-bash-guard)攔截、權限分類器擋、或 CC 自己把一件事拆太碎/timeout 設太長空等。 + +**症狀**:「為什麼這麼久?」「不是我 reject 的,你要查為什麼 reject」——CC 甩鍋給授權而非查真因。 + +**正確做法**:被 reject/擋 → 查真因(讀 `.claude/settings.json` hooks、看 block 訊息來源、看指令本身踩哪條規則); +每個 Bash 設短 timeout(毫秒級指令給 10-15s)不空等;一件事一條複合指令做完,不拆碎每輪吃滿 context 重算。 + +**原因**:誠實歸因(mindset §7)。慢/被擋是有具體原因的,假設成「授權往返」是逃避查證。日期:2026-06-12。 + +--- + +## 15. 假設 KBDB v3 route 還在 → 整條斷鏈假綠(2026-06-14) + +**錯誤模式**:碰 KBDB 整合時沿用舊程式碼打 `/blocks`、頂層 `/search` 等 v3 路徑,沒先確認 +基本盤現存哪些 route。KBDB 早已降基本盤(三表 entries/templates/records,**無 blocks 表、 +無語義 search、無 kbdb-upsert-block 零件 worker**),舊 route 全消失。 + +**症狀**:skills/examples 整條(sync-registry-to-kbdb.py + arcrun_skills_examples.ts 的 5 個工具) +打死 route → 工具已註冊上線、AI 叫得到,呼叫卻全回 404。**典型假綠**:工具列表看得到 +`arcrun_search_examples`,AI 以為能用,叫了拿 404,浪費 token 又被誤導。少了它 AI 寫 workflow 沒範本可用。 + +**正確做法**: +1. 碰任何 KBDB 整合,**先確認打的是基本盤現存 route**: + `/entries`(含 `?entry_type=`、`?page_name=`、`/entries/search?q=`)、`/templates`、`/records`、`/recipe-stats`。 + 別假設 v3 route(`/blocks` `/search`)還在。 +2. v3 `blocks` 欄位與基本盤 `entries` 幾乎 1:1(差別只在 `type`→`entry_type`),遷移很乾淨。 +3. **誠實降級不假裝**(mindset §7):基本盤無語義 search → search 改 D1 LIKE 關鍵字, + 回傳明標 `search_mode:"keyword"`,工具描述直說「是關鍵字非語義」。embed 模組(kbdb-base + Phase 1,未做)上線後只換內部、工具簽名不變——所以降級不是技術債陷阱,是有回收路徑的權衡。 + +**原因**:架構漂移(v3→基本盤)後沒回頭改下游,SDD 還標 ✅(當年確在舊 schema 跑通)。 +教訓:上游 schema/route 換版,要 grep 全下游使用點逐一驗,別信舊 SDD 的 ✅。日期:2026-06-14。 +詳見 docs/3-specs/llm-interface/tasks.md M3.2/M3.4 + kbdb-base/tasks.md 9.3/9.4。 + +> **漏網第二例(2026-06-15)——同類錯連犯兩次,正是沒做全域掃**:9.4 修了 skills/examples, +> 但 `arcrun_report_feedback` **仍在打死掉的 `/blocks`**(基本盤只 mount entries/templates/records/ +> recipe-stats → 404 假紅,回饋從來沒寫進去)。9.7 補修成 `/entries`(entry_type=agent-feedback)。 +> **這正反證 §15 的教訓**:當時若真做了「grep 全庫的 `/blocks`」,這個就會一起抓到、不會拖到隔天。 +> **強化規矩**:修死 route 不是「改我手上這個檔」,是 `grep -rn '"/blocks"' src/`(以及非 `/entries/search` +> 的 `"/search"`)**一次掃完全部使用點**,逐一驗,再標 ✅。漏一個 = 同個假綠陷阱原地複製。 + +--- + +## 16. 部署 leo21c 時 .env 官方帳號靜默蓋掉 config 的 leo21c(2026-06-15) + +**錯誤模式**:對 leo21c self-hosted 跑 `acr update`/`acr init`,以為它用 `~/.arcrun/config.yaml` +的 leo21c 帳號(`cloudflare_account_id: 51a01bfa…`)。實際上 repo 根 `.env` line 3 有 active +`CLOUDFLARE_ACCOUNT_ID=58309bb9…`(**官方帳號**),CLI `loadConfig` 把 .env 載進 process.env, +而 **env > 全域 config**(`config.ts:174`)→ 官方 account 覆蓋 leo21c。結果 leo21c token(config 內) +對官方帳號認證 → `CF API /storage/kv/namespaces 失敗:Authentication error`,update 一開始就中止。 + +**症狀**:raw curl(直接讀 config.yaml 的 token+account)能通,但 `acr update` 報 Authentication error。 +看似 token 壞掉,其實是 **token 對到錯帳號**(token 是 leo21c 的、account 變成官方的)。 + +**診斷**:`acr config --where` 印每欄來源。看到 `cloudflare_account_id ← env 變數` 且值是 58309bb9 +就是踩到了(config.yaml 的 51a01bfa 被蓋)。 + +**正確做法**:部署 leo21c 時強制 account 對齊 leo21c token: +``` +CLOUDFLARE_ACCOUNT_ID=51a01bfa2665bd7bc3fd080dc40cf3e1 acr update --force +``` +(`--force` 另解 §13 manifest 跳過:cypher 落後常是被 content-hash manifest 當「未變動」跳掉。) + +**原因**:repo `.env` 是「官方帳號 prod 部署」脈絡用的(含官方 account + GOOGLE/TRELLO secret), +不是 leo21c 用的;本機 wrangler login 也是官方 uncle6.me。教訓:**部署前用 `acr config --where` +確認 account 真的對齊目標帳號的 token**,別信預設。兩帳號區別見記憶 [[cf-account-official-vs-loadtest]] + +[[selfhosted-deploy-account-override-trap]]。日期:2026-06-15。 + +--- + +## 17. deploy 注入 binding 被 stripOfficialOnlyBindings 當場清掉(2026-06-26,issue #7) + +**錯誤模式**:在 `deploy.ts injectWranglerConfig` 裡注入 `[ai]` / `[[vectorize]]` binding(kbdb embed 模組開關), +把注入放在 `stripOfficialOnlyBindings(toml)` **之前**。strip 的 block header 正則含 `(routes|r2_buckets|ai)` +→ 會移除整個 `[ai]` 區塊。先注入 → 馬上被 strip 清掉 → self-hosted 開了語義查詢卻沒有 AI binding,embed 靜默失效。 + +**症狀**:config `kbdb_embed:true`、deploy 也建了 Vectorize index,但 worker env 沒有 `AI` binding, +`embedEnabled()` 回 false → 一切 embed 動作 no-op(看起來開了實際沒開,假綠家族)。 + +**正確做法**:注入 active binding 的步驟**一律放在 strip 之後**。注入前 binding 是註解(`# [ai]`), +strip 只清 active header 不碰註解;strip 完再取消註解 → 不會被清。dry-run 驗證注入後 active +`[ai]`/`[[vectorize]]`/`binding="VECTORIZE"` 都在再算數。 + +**原因**:strip 與 inject 都是純文字操作、順序敏感。改 toml 注入順序時要想「後面還有沒有別的 pass 會動同一段」。 +日期:2026-06-26。 + +--- + +## 18. KBDB 新增可查欄位要「零建表」——用 json_extract 不加真欄(2026-06-26,issue #5) + +**錯誤模式**:要讓 `source`(埋在 `metadata_json`)變可查 → 直覺想「在 entries 表加一個 source 欄」。 +這**違反 KBDB 表不變鐵律**(基礎三表萬年不動,新屬性天然在表外)。 + +**正確做法**:用 SQLite `json_extract(metadata_json, '$.source') = ?` 查既有 `metadata_json` TEXT 欄 +→ **零建表、零 migration**,filter 照樣可用。D1(SQLite)原生支援 JSON 函式。 +listEntries 加 `source` filter 即可,表結構一個欄位都不動。 + +**通用教訓**:KBDB「頂層化成可查欄位」≠「加真欄位」。凡是已經塞在 metadata_json 的屬性要變可查, +一律走 `json_extract`,不碰表結構。這跟 #6 的「PATCH record = 翻底層 entries.content 不動表」同源—— +**動的是值/查詢,不是表**。日期:2026-06-26。 + +--- + +## 快速檢查清單(做新功能前) + +- [ ] 這是工作流還是零件?問「有必要嗎?」 +- [ ] API 有對應端點嗎?否 → 先補 API,不是在介面層拼裝 +- [ ] 有 SDD 嗎?沒有 → 停手問 richblack +- [ ] 這段邏輯換介面(CLI → MCP)要重寫嗎?要 → 違反薄殼原則 +- [ ] 會修改 KV/D1 的鑰匙嗎?是 → 檢查遷移冪等性 + 向後相容 +- [ ] 需要人類同意嗎(暴露 / credential)?是 → 確保非 TTY 時拒絕 +- [ ] 怎麼驗證完成?只靠輸出訊息 → 不夠,需 2xx + 數據 +- [ ] 測試或初始化步驟有「失敗時怎辦」嗎?→ 不靠提醒,靠機制強制 +- [ ] 碰 KBDB?確認打基本盤現存 route(/entries /templates /records /entries/search),別假設 v3 /blocks /search 還在 +- [ ] KBDB 要新可查欄位?用 json_extract 查 metadata_json,別加真欄(表不變鐵律,#18) +- [ ] 改 deploy.ts toml 注入順序?想「後面還有沒有 pass(如 strip)會動同一段」(#17) diff --git a/system-dev/wiki/principles.md b/system-dev/wiki/principles.md new file mode 100644 index 0000000..231fbea --- /dev/null +++ b/system-dev/wiki/principles.md @@ -0,0 +1,26 @@ +# principles — 跨全局設計原則(push:CC 行動前必服從) + +> 這個檔由 hook 在 session 開始**全文注入**,讓 CC 設計任何東西前都先看見這些準繩。 +> 為什麼 push 而非寫成 card:原則是「會被遺忘的盲區」——沒推到眼前,CC 設計時很可能沒想到要服從就做了。 +> +> 規則:**一行一條**,精煉成準繩(不是長篇論述)。≤15 條;超過代表某些該合併、或下放成 card。 +> 發現新的跨全局原則 → append 一行。累積原則只改這個檔,**不必問用戶開新檔**。 +> 區分:原則 = 反覆適用的準繩(這裡);單次選擇 = 決策(寫成 card);踩過的坑 = mistakes.md。 + +--- + +## 原則 + +- **工作流是 default,零件是稀有例外**:自用/少數人 → 工作流;只有「全 arcrun 生態必須重用」才建零件。「能包」≠「該包」。 +- **零件只能 WASM**:TinyGo 或 AssemblyScript 編譯成 `.wasm`,`registry/components/` 下禁 TypeScript 業務邏輯。 +- **薄殼鐵律**:能力只實作一次放在 API(cypher-executor);CLI/MCP/lib 只做介面轉換 + 暴露,不自帶業務邏輯、不拼裝、不用 recipe 補 API 缺口。 +- **cypher-executor TS 不碰 credential/auth/JWT/template 展開**:這些全在 WASM 零件;crypto.subtle 只准出現在 wasi-shim.ts 的 host function。 +- **Cypher binding = YAML 的 URL 清單**,不是 Cloudflare service binding;零件串接走 HTTP URL,禁新增 `[[services]]`。 +- **arcrun 不做授權判斷**:能否打通由發 API key 的服務裁決,401/403 是對方行使授權、不是 arcrun bug。 +- **arcrun 是 AI 呼叫的工具(AI→工具)**,不是工具回頭呼叫 AI;判斷/轉換由操盤的 CC 自己做,工作流不內嵌 LLM 節點。 +- **誠實不假綠**:stub/未實作回 `success:false` 或標 unimplemented,不 mock 充綠燈;完成=客觀證據(exit code / HTTP status + trace),不是口頭宣布。 +- **不代替人類做風險確認**:建零件、暴露資料需人類明示同意;非 TTY 直跑就拒絕,不自己塞 flag 假裝人類同意。 +- **部署繞開 GitHub**:走 `scripts/local-deploy.sh`/wrangler 直推,不用 GitHub Actions(避免再被 flag)。 +- **任何 code 變動前先讀對應 SDD 並宣告**:找不到對應 SDD → 停手問 richblack,不自行新建。 +- **每完成一個 task 立刻更新 tasks.md `[x]`**,不批次。 +- **缺能力的自力救濟階梯**:自家 API 缺→補 API;第三方 API 缺→workflow/code-node 補丁(非建零件);純計算→code-node;真需新穩定能力才零件 PR。 diff --git a/system-dev/wiki/status.md b/system-dev/wiki/status.md new file mode 100644 index 0000000..478d5d5 --- /dev/null +++ b/system-dev/wiki/status.md @@ -0,0 +1,190 @@ +--- +name: status +description: 當前進度、進行中 Phase、已知問題、下一步(動態文件,每 session 更新) +metadata: + type: project + last_updated: 2026-06-24 +--- + +# 當前進度(動態) + +> **更新頻率**:每次 session 結束時更新此檔。 +> **新對話開始時讀此檔第一段**(3 分鐘概覽)。 + +--- + +## 📍 當前位置 + +> **2026-06-27 本 session(issue #8 地基1 + wiki-init 補骨架)**: +> - **wiki-init 補骨架**:wiki 已初始化過(push 檔活躍),補了從沒建的 pull 層——`cards/decisions/` 13 張決策原子卡(Haiku 改寫 11+範本 2,含 gloss/實體/typed-edge)、TAXONOMY 換成 arcrun 軸(子系統/形態)、principles 填 13 條、INDEX 真實視圖。raw source 0 異動,無真斷鏈。 +> - **issue #8([地基1] workflow description slot + search_workflow,北極星入口)**:新開 SDD `docs/3-specs/workflow-discovery/`(白名單已加)。leo 拍板 4 點(方案C雙寫/Q2 description 由操盤CC據實生成用戶可改/提示式回填/base通用entry_type filter)+ 方向①(MCP 改打 /webhooks/named)。 +> - ✅ **已實作 tsc 全綠**:1.1 `/webhooks/named` 強制 description|2.2+Q4 KBDB base 通用 entry_type filter(改4處:searchEntries/semanticSearch/route/proxy)|2.1 部署雙寫 embeddable entry(注意 KBDB 用 metadata_json 字串)|3.1 cypher `/workflows/search`|3.2 MCP `u6u_search_workflows`|4.1 `/workflows/backfill-search-entries`|1.3b `GET /webhooks/named` 補 description/created_at 欄位。 +> - ⏸ **卡待總管定**:Phase 1.2/1.3(MCP deploy 改打 /webhooks/named)卡在 ①-a/b/c——實作期發現 /webhooks/named 吃 graph 非 YAML,YAML→graph 編排寫在 CLI push.ts 介面層,MCP 複製=違 rule 07。①-c(先通債另開 issue)我推薦,待總管定。 +> - **完成標準**:tsc 綠≠完成,框架級待 leo21c 端到端實證(強制填擋空/搜尋命中/租戶隔離/降級 hint/MCP 不再 404)。issue open。 +> - **未 commit**(待 leo 明示;wiki 骨架與 #8 SDD/code 建議分兩 commit)。署名鐵律:跨 repo comment 開頭 `[arcrun CC]`(#12)。 +> +> **2026-06-26 上個 session(issue #4/#5/#6/#7 一批)**: +> - **#6**(base `PATCH /records/:id`):✅ updateRecord + route,三表 append-only 不破。tsc 綠,端到端待 leo21c。issue open。 +> - **#4**(07-thin-shell §3.1 自力救濟階梯 + code-node 規則):✅ 兩份 07 同步 + 02-forbidden §5.2 連動。§3.5 階梯(自家API→補API/第三方→workflow/code-node 補丁/純計算→code-node)。**code-node 只定規則未實作零件**(wishlist C1,另案)。純文檔。 +> - **#5**(KBDB 查詢缺口,普世視角):source 過濾 ✅(json_extract metadata_json,零建表)+ cypher proxy 透傳;documents 聚合 ❌不做(走 graph MCP);DELETE proxy ⏸擱置(依賴頂層 T8);embed-on-write →併#7;能力對照文件 ✅ `docs/4-guides/kbdb-capabilities.md`。 +> - **#7**(vectorize 全包,從零):✅ base embed 模組 `kbdb/src/embed.ts`(精耕只 embed `metadata.embed:true`)+ entries route 接 embed-on-write/semantic search/capability_hint + kbdb_embed 開關(config/deploy ensureVectorizeIndex REST/init 問)+ MCP kbdb_search mode:semantic。**抓修 deploy 順序 bug**(embed 注入要在 stripOfficialOnlyBindings 之後,否則 [ai] 被清)。kbdb/cypher/cli/mcp tsc 全綠,**端到端待 leo21c 部署開 Vectorize index**。 +> - SDD:kbdb-base tasks Phase 10(#6)/11(#5)/12(#7)。4 issue 各 comment 回報、**全留 open**(待端到端/頂層)。**未 commit**(待人決定)。 + + + +| 項目 | 狀態 | +|------|------| +| **Phase** | Credential Primitives TS → WASM(§0.1-0.5 完成,0.6-1 進行中) | +| **主線** | kbdb-base §7.5 已上線(公庫/私庫雙向、UUID 身份、市場數據) | +| **近期完成** | MCP self-hosted bug 三修(2026-06-08) | +| **已部署(2026-06-09 上午)** | §8 P0 cron 止血;§7.8 onboarding P0/P1/P2;CLI 1.3.3 publish | +| **已部署(2026-06-09 下午,Haiku 壓測暴露)** | **http_request+claude_api+kbdb_upsert_block+km_writer 假綠根因修復**(非 2xx 回 error envelope,4 worker 已 deploy);**acr run self-hosted 修復**(本機 YAML 直接走 /cypher/execute 不需先 push + res.ok 擋 + .yaml 容忍);**D1-in-update 修復**(update 漏建 D1 → 補 ensureD1Database,D1 已建 count:1);**CLI 1.3.4 publish**;llms.txt/.env.example 加 D1 Edit 權限 | +| **Haiku 自主壓測(test_arcrun/5)結論** | onboarding 治好(兩輪都裝+init 沒跳過、走對路建 recipe 不建零件);但暴露 4 真 bug(見下);Haiku 仍會假綠(curl 繞過說成 arcrun 成功、D1 沒建謊報成功)→ 印證「執行真相要系統能驗,不能信 AI 嘴巴」 | +| **已驗證(2026-06-13 壓測 leo21c)** | **401 假綠根治全鏈驗證**:host fn error envelope → 零件 parsed["error"] → cypher isFailure(),leo21c 實測 401 回 `success:false`(真紅);**`{{credential.notion_token}}` 注入實證打通**:真讀到 Notion Recipes 資料(「蕃茄蘑菇燉雞」+ iCook 連結),§8 credential 機制生效;**acr update 部署系統一輪修完**(CLI 1.3.5 部署假綠露出、pnpm-workspace 缺檔補齊 23/23 全綠、1.3.6 失敗帶 stderr、1.3.7 manifest 跳過、1.3.8 共享 install 治本);check-release.sh + local-deploy.sh 全形括號 set-u crash 修復 | +| **已完成(2026-06-14,matrix 重整交棒)** | **① SDD 遷移收尾**:.agents/specs → docs/3-specs 全改(hooks5/rules5/CLAUDE.md/wiki + 2-architecture 鏡像/README/HANDOFF/4-guides),.agents 刪除,pre-write-guard 白名單刷成 10 個實存 SDD(SessionStart + rule4.3 已驗)。**② KBDB 資料層 MCP 薄殼**(kbdb-base Phase 9.1):mcp/src/tools/kbdb_data.ts 6 工具(template/record/query/search),守鐵律不給建表/SQL,mcp tsc exit 0;CLI(9.2)後補。**③ 修 LI M3 斷鏈**(見 mistakes #15):skills/examples 5 工具 + sync 從 v3 /blocks /search 改打基本盤 /entries,base 加 page_name 過濾,search 誠實降級 LIKE,mcp+kbdb tsc exit 0 | +| **已完成(2026-06-14 晚,HANDOFF §3b)** | **修 MCP self-hosted 認證 401**(mcp-account-source.md §5.5):根因=MCP partner-auth 把 Bearer 拿去 KBDB 驗證 partner,namespace 明碼非註冊 partner→401;cypher 端 X-Arcrun-API-Key 不驗證直接當分區 key→CLI 通。修法①+②:① `MULTI_TENANT=false` 時 partner-auth 把 Bearer 明碼直接當 org_namespace(types/middleware/wrangler,官方 SaaS 行為不變共用同碼);② mcp-setup 把 api_key/namespace 寫進 .mcp.json `headers.Authorization`(裸檔不送 header 是次因)。mcp+cli tsc exit 0、partner-auth 9 tests 綠 | +| **已處理(2026-06-25,issue #3 官方庫誤寫善後)** | **① 清理 SOP runbook 備妥**(`docs/5-records/2026-06-24-official-kbdb-cleanup-leo-misdelete.md`):14-E 遷移期 mira 誤寫 ~11 萬 `owner_id=leo` 進官方 prod `arcrun-kbdb`(database_id `0c580910…`)。三道防誤刪閘(備份先行 d1 export → 核實 entry_type+時間範圍確認範圍乾淨 → 刪後驗證 count=0+孤兒 entry_values=0+其餘不受影響)。**補關聯刪除範圍**:`entry_values`(slot-link,外鍵指 entries)先刪孤兒、`templates.created_by=leo` 單獨核實勿盲刪。**DELETE 不由 CC 跑**(不可逆+官方憑證+需人類明示,mindset §7)→ runbook 由 leo(官方運營方)親自執行。**【2026-06-25 已端到端執行完畢】** chaperone 模式逐步跑:核實誤寫 **111,368 筆**(value 93,790/note 13,671/block 3,907,全 06-15~06-24 遷移期、孤兒=0、無 leo template)→ 45MB 備份 → leo 點頭後 `DELETE FROM entries WHERE owner_id='leo'`(changes=111368)→ 驗證 leo 殘留=0/孤兒=0/官方庫其餘只剩 smoke_ns_1×2+null×1(3 筆烟霧殘留,非 leo)。備份已刪、`.gitignore` 補 `*.sql` 防誤 commit、gh issue #3 已回報客觀證據可關閉。**② 願景 acr migrate 雙向遷移記 BACKLOG.md**(對齊 wishlist C7,牽動 cli+cypher+kbdb,未來方向)。已 gh issue #3 comment 回報,**暫不 close 待 leo 跑完清理回報 count 歸零**。**③ 順手消 status 矛盾**:credential 401 真實狀態=**已端到端實證打通**(2026-06-13 Notion `{{credential.notion_token}}` 真讀到資料),tasks.md 8.5 原標 `[ ]`(OpenAI 路徑沒走)已補 `[x]`(Notion 達同等證據,機制與服務無關)。**純文檔/runbook,無 code 變動** | +| **已修+merge prod(2026-06-24,issue #2 框架 bug)** | **self-hosted cypher KBDB_BASE_URL 注入缺口修復**(總管經 GitHub issue #2 交辦):根因=`injectWranglerConfig` self-hosted 分支只注 database_id/MULTI_TENANT,**漏注 KBDB_BASE_URL** → cypher `/kbdb/*` fallback 到官方 `arcrun-kbdb.uncle6-me`(self-hosted 資料寫進官方庫、隔離破損)。修:deploy.ts self-hosted 分支加 `KBDB_BASE_URL` 改寫成 `arcrun-kbdb..workers.dev`,比照既有注入模式;init/update 共用此注入點一處修兩條路。驗:tsc exit 0、真實 cypher toml 注入 subdomain=leo21c → `arcrun-kbdb.leo21c.workers.dev`(comment 行不動)。**已 merge main+push**(commit 9c4333d、merge ba00b98)、**CLI npm publish 1.3.13**(修的是 CLI 注入邏輯 → self-hosted 用戶裝 npm 套件才到手,光 git 不生效;local-deploy.sh §6 自動 bump+publish;release-check 全綠)、**issue #2 已關閉**。**端到端落庫實證歸 mira dogfooding 帳號**(需 leo21c token 跑 acr update + wrangler d1 execute 收綠燈,不卡本框架修復)。⚠️ 與 issue 描述出入:cypher toml **本就有** KBDB_BASE_URL(寫死官方),比「沒有」更糟(`??` fallback 根本不觸發)→ 修法是就地改寫而非新增。**慣例落地**:總管↔arcrun 交辦走本 repo GitHub issue(已寫進 CLAUDE.md)| +| **已部署+端到端驗收(2026-06-15,總管交棒 3 件)** | **① CLAUDE.block.md 重寫**(HANDOFF §6,Haiku 能懂):補三盲點=recipe 是公共投稿非私人腳本/缺能力補 API 不准 recipe-工作流拼裝(附口訣)/自製零件退場路徑(claude_api 刪、kbdb 走 acr kbdb 薄殼、假零件改 recipe);README 零件vsrecipe 段同步對齊。**② cypher proxy 補 /kbdb/entries CRUD**(kbdb-base 9.6,解鎖 mira _kbdb_client.py 主線):POST/GET list/GET :id/PATCH :id 純轉發基本盤;租戶隔離同 9.5(寫入注 owner_id、list 強制本租戶、PATCH 剝 owner_id、刻意不開 DELETE)。**③ arcrun_report_feedback 改打 /entries**(kbdb-base 9.7,9.4 漏網):舊 POST /blocks 是死 route(404 假紅)→改 entry_type=agent-feedback。**端到端 prod 驗收全綠**:無key→401、跨租戶 list count=0、owner_id hijack 被剝、page_name lookup 通、/blocks→404 確認、agent-feedback 寫入經 proxy 讀回 count=1。cypher+mcp tsc exit 0、已部署官方 58309bb9、CLI npm 1.3.12、smoke 資料已清。**交棒 mira**:leo21c 改 _kbdb_client.py 打 cypher /kbdb/entries 即可遷移 | +| **已部署+自驗(2026-06-15,HANDOFF §6b 部署斷層解決)** | **leo21c cypher 落後 → 已重部,`/kbdb/entries` 回 200**(解鎖 mira 14-A 主線遷移)。**根因不是 GitHub lag**(origin/main==本地,含 entries route):① `acr update` 的 content-hash manifest(deploy.ts:198-225)把 cypher 當未變動跳過 → `--force` 清空 manifest 全部重部;② **更深陷阱**:repo `.env` line 3 active `CLOUDFLARE_ACCOUNT_ID=58309bb9`(官方)被 CLI 載入覆蓋 config.yaml 的 leo21c `51a01bfa`(env>config,config.ts:174)→ leo21c token 對官方帳號認證 → KV「Authentication error」中止 → 解:部署時 `CLOUDFLARE_ACCOUNT_ID=51a01bfa… acr update --force`(記憶 [[selfhosted-deploy-account-override-trap]])。**部署 23/23 全綠**(含 cypher/kbdb/mcp)+ seed(10 API+23 auth)+cron migrate。**自驗**:`/kbdb/entries?limit=1`→200 真 body(非假綠)、`/kbdb/templates`→200、`/kbdb/records?limit=1`→404(**非回歸,proxy 本就無 bare list route,只有 POST + by-template/:t + :id**);缺口② MCP initialize(Bearer leo)→200(MULTI_TENANT 注入生效、KBDB binding 隨 mcp 上線)。全域 acr 已升 1.3.12(npm 本就有,非重發)。**純部署無 code 變動** | +| **已部署(2026-06-15,MCP self-hosted 401 注入缺口修補)** | **根因=部署沒注入 MULTI_TENANT**(非 code bug):partner-auth.ts MULTI_TENANT 分支對,但 mcp toml 該行原是註解、injectWranglerConfig 注 KV/subdomain 卻漏注 MULTI_TENANT → worker env undefined → 走 partner-key → self-hosted 401。修:deploy.ts 加 injectMultiTenant(DeployContext.selfHosted;init/update 帶旗標;mcp toml 改 active [vars])。本地驗注入真實函式 PASS(mcp/cypher 各 1 行 active MULTI_TENANT=false 在 [vars] 下);官方 MCP partner 路徑回歸 401(不變)。CLI npm 1.3.11。**端到端交棒 mira**:leo21c 重跑 acr update(CLI≥1.3.11)→ curl Bearer leo /mcp 應 200。SDD §5.5.1 | +| **已部署+端到端驗證(2026-06-14 晚)** | **KBDB CLI 薄殼解卡(9.5+9.2)已上 prod**:cypher `routes/kbdb-proxy.ts` 純轉發 + CLI `commands/kbdb.ts`(acr kbdb)。**端到端煙霧測試全綠**(curl cypher.arcrun.dev/kbdb/*):無key→401、建template→200(created_by=租戶)、租戶隔離 query+search 都 0 筆跨租戶。CLI npm 1.3.10。**煙霧測試抓到 2 真 bug 並修**:①proxy fallback 寫死舊死的 kbdb.finally.click→改現役 arcrun-kbdb + cypher [vars] KBDB_BASE_URL;②kbdb searchByTemplate `\|\| true` stub 讓 owner_id 過濾失效(跨租戶洩漏)→改 SQL JOIN entries 真過濾。三 worker(cypher/mcp/kbdb)都已 deploy 官方帳號。**self-hosted MCP 那條未測**(官方不設 MULTI_TENANT,待 leo21c 部署 HANDOFF §3)。⚠️ prod 留了 smoke_contact 測試 template(掛 smoke_ns_1,不污染真租戶,template 無 delete API) | +| **待處理** | §8 P1/P2 recipe/workflow list 遷 D1(需 D1 先穩,現已可建);4 份 inline host fn 抽共用 helper dedup;arcrun.dev/llms.txt serve;mcp worker 偶發 fetch failed(網路抖動,重跑即過,非 bug);**LI M3 實環境驗收**(需 KBDB_BASE_URL 跑 sync + 叫工具,目前只驗 tsc+dry-run);**mira 波次2 主線遷移**(arcrun 端 cypher 已部 leo21c、/kbdb/entries 實測 200,**已交棒待 mira CC 改 _kbdb_client.py**)|~~KBDB /kbdb/entries 缺口~~✅~~MCP report_feedback 死 route~~✅~~self-hosted MCP 端到端實測~~✅(2026-06-15 leo21c initialize 200,見上)~~leo21c cypher 落後/部署斷層~~✅(2026-06-15 §6b 解)~~self-hosted cypher KBDB_BASE_URL 漏注入(issue #2 隔離破損)~~✅(2026-06-24 merge prod,端到端待 mira 帳號驗) | + +--- + +## 🔄 進行中的 Task + +### credential-primitives-wasm + +- [x] Phase 0.1-0.5:核心合併、21 個零件 contract 完成、CREDENTIALS_KV binding 確認 +- [x] Phase 0.6:wasi-shim 新增 host functions(kv_get / crypto_decrypt / crypto_sign_rs256) + - 決策:host function 實作位置 = cypher-executor/src/lib/wasi-shim.ts + - 待驗:component-loader 能否正確呼叫 WASM runner +- [ ] Phase 0.7:component-loader WASM runner 路徑(依賴 0.6) +- [ ] Phase 1.1-1.8:auth_static_key WASM 零件(TinyGo) +- [ ] Phase 2.1-2.6:auth_service_account WASM 零件(JWT signing) +- [ ] Phase 3.1-3.5:清除 cypher-executor 三套違規 TS(credential-injector.ts / jwt-signer.ts / BUILTIN_*) + +### kbdb-base + +- [x] §7.5:公庫/私庫雙向、UUID 身份、市場數據(2026-06-07 deploy) +- [x] §8 P0:cron 止血(2026-06-09)。scheduled.ts 每分鐘 list → 單一 key `cron-idx:_all` get(新增 lib/cron-index.ts;webhooks-named push/delete 維護;migrate-cron-index 一次性遷舊)。1440 list/日 → 0。cypher tsc exit 0 +- [ ] §8 P1/P2:recipe/workflow list 遷 D1(透過 kbdb worker /entries HTTP API 雙寫,**不加 binding**,用 cypher binding 狗糧)。另開 session 做(大、易出錯,需專注+壓測) + - **狀態**:架構拍板(richblack 2026-06-09:用 kbdb /entries HTTP,service binding 才需問),未動 code + +### onboarding(self-hosted-init §7.8,2026-06-09 交付) + +- [x] P0:acr init 偵測先於動作 + 裝完驗收(cli/src/lib/preflight.ts)。冪等。 +- [x] P1:acr whoami(+--json)+ MCP arcrun_whoami(AI 別自己 curl 猜帳號) +- [x] P2:mcp-setup 寫完印「請重啟 client」(D3) +- [~] P3(部分,2026-06-09 push c152f5f):repo 加 `.env.example` 範本(每格白話說明、值留空, + `.gitignore` `!.env.example` 放行)+ llms.txt 教 AI「先 cp .env.example .env、帶用戶填值」。 + 已 push main → 公開 repo 生效(raw 200 已驗)。仍待:arcrun.dev/llms.txt serve(landing/public 缺檔)。 + +### LLM Wiki 建設 + +- [x] 階段一:目錄結構建立、分類規則表、檔案掃描(101 個 .md) +- [x] 階段二:mistakes.md + decisions-summary.md + INDEX.md + status.md(本檔) +- [x] 階段三:文件遷移執行(2026-06-14 完成)— SDD 實體已在 `docs/3-specs/`;hooks(5)/rules(5)/CLAUDE.md/wiki + 全部從舊 `.agents/specs/` 改指 `docs/3-specs/`;`.agents/steerings/tech.md`(已與 `docs/3-specs/tech.md` 同步) + 連同空 `.agents/` 刪除。SessionStart hook 與 pre-write-guard 白名單(10 個 SDD 目錄全對齊)已驗證。 + 另收尾活指針:docs/2-architecture/ 鏡像、docs READMEs / HANDOFF / 4-guides 的 arcrun-local 指針一併改新路徑。 + 刻意保留:docs/5-records 歷史 incident/migration 記錄、跨 repo(polaris/mira、matrix/kbdb)路徑、 + docs/3-specs/** SDD 內文(改 SDD 內文=change,需另確認)、README 遷移對照表。 +- 🟡 待補:modules/ (cypher-executor、wasi-shim、recipe-system 等) + +--- + +## ⚠️ 已知問題 / 待處理 + +> 2026-06-09 更新:已解項標 ✅;🔴/🟡 為仍待處理。本次 Haiku 壓測新發現的 bug 加在表內。 + +| 問題 | 優先級 | 狀態 | 備註 | +|------|--------|------|------| +| ~~**credential 注入 401**~~ | ✅ 已解 | **8.1-8.5 全完成(2026-06-25 確認)** | 機制(auth_static_key `resolve_credentials` + graph-executor `resolveCredentialRefs`)已端到端實證:2026-06-13 Notion `{{credential.notion_token}}` 真讀到資料(同等於 8.5 OpenAI 驗收,機制與服務無關)。tasks.md 8.5 已補 `[x]` | +| §8 P1/P2 recipe/workflow list 遷 D1 | 🔴 高 | 架構已拍板未動 code | 走 kbdb /entries HTTP 雙寫不加 binding;依賴 D1(現已可建)。另開 session 做 | +| 4 份 inline http_request host fn 抽共用 helper | 🟡 中 | 待 dedup | http_request/claude_api/kbdb_upsert_block/km_writer 各自複製貼上同段(這次假綠修也是逐份改) | +| `arcrun.dev/llms.txt` 404 | 🟡 中 | 未 serve | landing/public 缺檔;GitHub repo 內正常(test/5 走 GitHub 不阻擋) | +| MCP account-source | 🟡 中 | 記錄中 | self-hosted MCP 指官方不指自己(§5.2 已知) | +| ENCRYPTION_KEY 冪等性 | 🟡 中 | 設計中 | init 多跑生成新 key,無法複用舊 key | +| recipe submitted 後沒有 uuid | 🟡 中 | 待驗 | submit-p 應回 uuid,CLI 拿不到 | +| ~~KV list 爆量~~ | ✅ 已解 | §8 P0 部署 | cron list→單 key get,1440/日→0(2026-06-09) | +| ~~onboarding 缺陷(4 項)~~ | ✅ 已解 | §7.8 P0/P1/P2 + P3 部分 | CLI 1.3.4,Haiku 壓測證實裝+init 不跳過 | +| ~~D1 建不起來~~ | ✅ 已解 | update 補 ensureD1Database + token 加 D1 權限 | 2026-06-09,D1 已建 count:1 | +| ~~http_request 401 假綠~~ | ✅ 已解 | host fn 非 2xx 回 error envelope | 4 worker deploy;auth_sa 不套 | +| ~~acr run self-hosted 404 爆~~ | ✅ 已解 | 本機 YAML 走 /cypher/execute + res.ok 擋 | CLI 1.3.4 | +| ~~npm publish~~ | ✅ 已解 | token 在 .env NPM_API_TOKEN | 走 local-deploy.sh step 6,別手動繞 | + +--- + +## 🧪 測試進度 + +### 壓測 2026-06-08(Haiku 乾淨重測) + +- **目標**:kbdb-base §7.5 公庫/私庫 + UUID 驗收 +- **對象**:Haiku(全程自主操作) +- **測試檔**:`/test_arcrun/3/test_to_haiku.md`(9 個步驟) +- **撞牆記錄**:`/test_arcrun/撞牆記錄.md` +- **狀態**:準備妥當,awaiting Haiku run + +### 前次壓測(2026-06-07) + +- ✅ kbdb-base §7.5 上線前驗收(16 項通過) +- 📋 發現 onboarding 四缺陷(Cold.1-8) + +--- + +## 🚫 封測狀態 + +**推遲**(richblack 2026-04-19 決定,後延至 2026-06-08 依舊推遲) + +**原因**: +- Phase 1-3(auth WASM + 清除違規 TS)未完 +- 待 credential-primitives-wasm 完整交付 + +**啟動條件**:Phase 1-3 完成 + 壓測 Haiku 自癒能力驗證 + +--- + +## 📋 下一步(優先級)— 2026-06-25 更新 + +### 🔴 最優先 + +1. [x] ~~**credential 注入 401 修復**~~ ✅ **已端到端實證打通**(2026-06-13 Notion `{{credential.notion_token}}` 真讀到資料;tasks.md 8.5 已補 `[x]`)。機制完成,非阻擋。 +2. [ ] **§8 P1/P2 recipe/workflow list 遷 D1**:D1 現已可建(依賴解除)。走 kbdb /entries HTTP 雙寫不加 binding。 + 大、易出錯,另開乾淨 session + 壓測。**← 現在的真.最優先未做項。** + +### 🟡 本周 + +3. [ ] credential-primitives-wasm Phase 0.6-0.7(host function + WASM runner)→ Phase 1-2(auth WASM 零件) +4. [ ] 4 份 inline http_request host fn 抽共用 helper(dedup;這次假綠修是逐份改的) +5. [ ] 清除 cypher-executor 違規 TS(Phase 3:credential-injector.ts / jwt-signer.ts / BUILTIN_*) + +### ⚪ 未來 + +6. [ ] `arcrun.dev/llms.txt` serve(landing/public 補檔) +7. [ ] 補 wiki modules/(文件遷移階段一~三已於 2026-06-14 完成) +8. [ ] ENCRYPTION_KEY 冪等性、MCP account-source、recipe submit uuid 回傳 + +--- + +## 🔗 相關資源 + +| 資源 | 位置 | 用途 | +|------|------|------| +| 總進度 | `docs/3-specs/arcrun/arcrun.md` | 全景進度表 | +| 當前 Phase SDD | `docs/3-specs/arcrun/credential-primitives-wasm/` | design + tasks | +| 壓測 case | `/test_arcrun/2/test_case.md` / `3/test_to_haiku.md` | 功能驗收 | +| 事件復盤 | `/docs/incidents/` | 歷史踩坑 | +| 常犯錯誤 | `.claude/wiki/mistakes.md` | 自檢清單 | +| 架構決策 | `.claude/wiki/decisions-summary.md` | 設計參考 | + +--- + +## 版本日誌 + +| 日期 | 變動 | +|------|------| +| 2026-06-08 | 初建。MCP bug 修正完成、wiki 系統搭建、壓測 Haiku 進行中 | +| 2026-06-08(補) | Haiku 壓測發現 Cold 驗證缺陷:init 無強制檢查點 → 假綠風險。記入 mistakes.md §11 |