fix(mcp): 解掉三個把人推去寫零件的誤導入口(leo 2026-07-21 拍板)

leo:「刪掉,技術者才會寫 component,在 Arcrun repo 寫一條如何 contribute
指向另一個 repo 就好。」

實測病灶:總管想寫「定期打 API 然後通知」的 workflow(Python 約 10 行),
問 foreach_control 怎麼用 → MCP 回傳 TinyGo 寫 WASM 零件教學(白名單/syscall/
contract schema)=完全另一件事,40 分鐘未完成。

三處修正:
1. search_components 搜不到時的話術——原本建議 publish_component(把「我找不到」
   翻譯成「你去造一個」,方向完全相反)。改為導向正確順序:語意搜尋知識庫→
   auth-recipe list/scaffold→acr parts(http_request 能打任意 API)→acr list,
   並明說 registry 可能是空的(已知問題),搜不到≠沒有這能力。
2. registry.ts 停用 publish_component / get_component_guide 兩個註冊
   (檔案保留,只是不對 AI 暴露)。
3. 新增 CONTRIBUTING-components.md:三層責任分工(平台通用能力/熱門預鋪 recipe/
   冷門誰用到誰開發)、什麼時候才真需要新零件、真要貢獻走 PR 的流程。

typecheck 通過。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 16:42:51 +08:00
parent 98d87d5d3f
commit 1b687fedb0
6 changed files with 294 additions and 4 deletions
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
# publish-lag-check.sh — SessionStart hook:偵測「公開 mirror 落後工作區」並出聲
#
# 病根(leo 2026-07-21 點名的真實風險):
# Gitea(草稿/工作現場)與 GitHub(正稿/成品櫥窗)是**手動同步**的
# (靠人跑 scripts/publish-github.sh --push,且需 D20 arm)。
# → 改了零件、重編 wasm 後若沒人記得發佈,**用戶抓到舊版且沒有任何錯誤訊息,
# 只是行為不對**——這種靜默失敗只有外部使用者會撞到,我們自己永遠測不到。
#
# 實例:安裝器的懶載會從
# cdn.jsdelivr.net/gh/youlinhsieh/Arcrun@main/.component-builds/<名>/component.wasm
# 抓 wasm。那個位址永遠指向 GitHub 上的**最後一次發佈**,不是我們本機的最新版。
#
# 原理:比對「工作區 HEAD」與「.github-public 最後一個 release commit 記錄的 snapshot」。
# publish-github.sh 的 commit 訊息格式固定為:release: snapshot <短hash> (<日期>)
# → 從中取出 hash,看它是不是工作區 HEAD 的祖先/相同。
#
# 只提醒不阻擋(exit 0):發不發佈是人的決定(且 push GitHub 需 leo 親跑 arm),
# hook 的職責只是消滅「忘了」這個失敗模式。
set -euo pipefail
MIRROR_DIR=".github-public"
# 沒裝發佈管線的 repo 直接安靜退出
[ -d "$MIRROR_DIR/.git" ] || exit 0
[ -f "scripts/publish-github.sh" ] || exit 0
git rev-parse --git-dir >/dev/null 2>&1 || exit 0
HEAD_SHORT="$(git rev-parse --short HEAD 2>/dev/null || echo '')"
[ -z "$HEAD_SHORT" ] && exit 0
# 從 mirror 最後一個 commit 訊息取出它當初發佈的來源 hash
LAST_MSG="$(git -C "$MIRROR_DIR" log -1 --format=%s 2>/dev/null || echo '')"
PUBLISHED="$(printf '%s' "$LAST_MSG" | sed -n 's/.*snapshot \([0-9a-f]\{6,\}\).*/\1/p')"
if [ -z "$PUBLISHED" ]; then
# mirror 存在但沒有可辨識的 release commit(可能還沒發過)
echo "════════════════════════════════════════════════"
echo "📦 這個 repo 有公開發佈管線,但 mirror 還沒發過任何版本"
echo "════════════════════════════════════════════════"
echo " 若已有用戶依賴公開版(例如安裝器從 jsDelivr 抓 wasm),現在是空的。"
echo " 發佈:leo 在頂層跑 scripts/github-arm.sh,再於本 repo 跑"
echo " GITHUB_REMOTE=... bash scripts/publish-github.sh --push"
echo ""
exit 0
fi
# 已發佈的那個 commit 就是現在的 HEAD → 同步,安靜
if [ "$PUBLISHED" = "$HEAD_SHORT" ]; then
exit 0
fi
# 算出落後幾個 commit(發佈點 → HEAD)。取不到就不顯示數字。
BEHIND="$(git rev-list --count "${PUBLISHED}..HEAD" 2>/dev/null || echo '')"
# 落後 0 且 hash 不同 → 可能是 mirror 比工作區新(罕見,例如剛 rebase),一樣提醒
echo "════════════════════════════════════════════════"
if [ -n "$BEHIND" ] && [ "$BEHIND" != "0" ]; then
printf '📤 公開 mirror 落後工作區 %s 個 commit(最後發佈:%s,現在:%s\n' \
"$BEHIND" "$PUBLISHED" "$HEAD_SHORT"
else
printf '📤 公開 mirror 與工作區不一致(最後發佈:%s,現在:%s)\n' "$PUBLISHED" "$HEAD_SHORT"
fi
echo "════════════════════════════════════════════════"
echo "⚠️ 外部使用者拿到的仍是舊版,而且**不會有任何錯誤訊息**——只是行為不對。"
echo " (安裝器的懶載直接從公開位址抓 wasm,落後=裝到舊零件。)"
echo ""
echo " 要發佈:① leo 在頂層跑 bash scripts/github-arm.sh \"<任務描述>\" 30"
echo " ② 本 repo 跑 GITHUB_REMOTE=https://github.com/<帳號>/<repo>.git \\"
echo " bash scripts/publish-github.sh --push"
echo " 不急著發也沒關係——這只是提醒,別讓它靜默漏掉。"
# 若這次落後的內容碰到 wasm,額外警告(那是用戶會直接抓的東西)
if git diff --name-only "${PUBLISHED}..HEAD" 2>/dev/null | grep -q '\.wasm$'; then
echo ""
echo " 🔴 這批改動**包含 .wasm 變更** → 用戶抓到的零件會跟你本機不同,優先發佈。"
fi
echo ""
exit 0
+9
View File
@@ -10,6 +10,15 @@
"timeout": 5
}
]
},
{
"matcher": "startup|resume|clear",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/publish-lag-check.sh"
}
]
}
],
"PreToolUse": [
+88
View File
@@ -0,0 +1,88 @@
# 想貢獻零件(component)?先確認你真的需要
> **99% 的需求不需要新零件。** 零件是**專業等級**、走 PR 審核;
> **recipe / workflow / app 誰都可以做**,隨建隨用、不必部署。
---
## 先照這個順序找,多半不用寫零件
**1. 語意搜尋知識庫**(最強——它能找到你沒猜中的用詞)
```
kbdb_search(q="我想達成什麼(用一句話描述)", mode="semantic")
kbdb_get_map() # 不確定該查哪個庫,先看藏書地圖
```
**2. 看現成的服務整合**26 個:GitHubNotionGeminiSlack…)
```bash
acr auth-recipe list
acr auth-recipe scaffold github # 直接吐出 credentials 範本 workflow 範例
```
**3. 看零件全集**21 顆通用零件)
```bash
acr parts
```
特別注意 **`http_request`**:它能打**任意** HTTP API。
「平台沒有 XX 服務的零件」通常不成立——用 `http_request` 一份 recipe 就有了。
**4. 看有沒有現成 workflow 可以直接接**
```bash
acr list
```
---
## 三層責任分工
| 層 | 誰做 | 怎麼做 |
|---|---|---|
| **通用能力** | 平台提供 | `http_request` auth-recipe 機制=**能打任何 API**,這是地基 |
| **熱門服務 recipe** | 平台預鋪 | 減少常見情境的摩擦(現 26 個) |
| **冷門/特殊** | **誰用到誰開發** | recipe 是**設定不是程式**,門檻低 |
> 我們不會包辦全世界所有服務的 API。**用到就自己補一份 recipe**,那是設定檔不是程式碼。
---
## 什麼時候才真的需要新零件
**只有這種情況**:需要**新的原語能力**,而且**無法用既有零件組合出來**。例如——
- 一種新的控制流(現有 `if_control``switch``foreach_control``filter``try_catch` 都表達不了)
- 一種新的資料轉換原語(`code` 零件的沙箱做不到)
- 需要 WASM 層才能做的事(純計算、特殊編解碼)
**不算的情況**(這些都用 recipeworkflow 解):
- 「我要接 XX 服務的 API」→ `http_request` recipe
- 「我要做 XX 業務邏輯」→ workflow 組合既有零件
- 「我要處理某種資料格式」→ `code` 零件(沙箱 JS
---
## 真的要貢獻零件的話
零件是 WASMTinyGoAssemblyScript),有嚴格的沙箱約束
(禁網路 syscall、禁檔案系統、禁 goroutine、體積上限 2MB、
唯一 I/O 模型是 stdin/stdout JSON)。
**流程**:走 Arcrun repo 的 PR,過 `docs/component-pr-review-standard.md` 審核。
撰寫規範與 contract schema 見 `registry/` 底下的既有零件範例。
---
## 為什麼 MCP 不再暴露 `publish_component` / `get_component_guide`
2026-07-21 leo 拍板停用)
那兩個工具對一般使用者是**誤導危機**:搜不到東西時,系統會建議「去提交新零件」,
把人推向最難、最該擋的那條路。
**實測**:總管想寫一支「定期打 API 然後通知」的 workflowPython 約 10 行),
`foreach_control` 怎麼用,MCP 回傳的是**TinyGo 寫 WASM 零件的教學**
(白名單、syscall 限制、contract schema)——完全是另一件事,導致 40 分鐘未完成。
**設計判準**leo):
> 前端界面要**人類友善**Arcrun 要 **AI 友善**——都要**從終點看**。
> Arcrun **讓 AI 輕易建立程式碼**AI 要覺得 **Arcrun 比 Python 還簡單**
> 因此沒有寫 Python 的慾望。**絕不可迷路、搞不懂。**
+15 -1
View File
@@ -44,7 +44,21 @@ export function registerSearchComponents(server: McpServer, env: Env, orgNamespa
return {
content: [{
type: "text",
text: `找不到符合「${query}」的零件。可以用 arcrun_publish_component 提交新零件。`,
text: [
`registry 裡找不到「${query}」——但**先別急著造零件**(零件走 PR、專業等級)。`,
``,
`⚠️ registry 目前可能是空的(已知問題:唯一寫入觸發點隨 .github/workflows 移除而蒸發),`,
` 「搜不到」不代表「沒有這個能力」。請改用下列順序找:`,
``,
`1. **語意搜尋知識庫**(最強,能找到你沒猜中的用詞):`,
` kbdb_search(q="我想達成什麼(用一句話描述)", mode="semantic")`,
`2. **看現成的服務整合**\`acr auth-recipe list\`26 個:GitHub/Notion/Gemini…)`,
` 要用哪個 → \`acr auth-recipe scaffold <服務>\` 直接吐 credentials 範本+workflow 範例`,
`3. **看零件全集**\`acr parts\`21 顆通用零件,含 http_request 可打任意 API`,
`4. **看有沒有現成 workflow 可直接接**\`acr list\``,
``,
`💡 多數需求的正解是「用 recipe 設定既有零件」或「接既有 workflow」,不是新造零件。`,
].join("\n"),
}],
};
}
+6 -2
View File
@@ -24,11 +24,15 @@ import { registerWhoami } from "./arcrun_whoami.js";
export function registerAllTools(server: McpServer, env: Env, orgNamespace: string, partnerToken: string) {
registerSearchComponents(server, env, orgNamespace);
registerPublishComponent(server, env, orgNamespace);
// 🔴 2026-07-21 leo 拍板停用:零件走 PR、專業等級;recipe/workflow/app 誰都可以做。
// 這兩個工具對一般使用者是「誤導危機」——搜不到東西時把人推向「去造零件」,
// 那是最難、最該擋的那條路(總管實測:問 foreach 怎麼用,回傳 TinyGo 寫 WASM 教學)。
// 想貢獻零件 → 見 Arcrun repo 的 CONTRIBUTING-components.md。
// registerPublishComponent(server, env, orgNamespace);
registerSearchWorkflows(server, env, orgNamespace, partnerToken); // workflow-discovery R2
registerListComponents(server, env, orgNamespace);
registerGetComponent(server, env, orgNamespace);
registerGetComponentGuide(server, env, orgNamespace);
// registerGetComponentGuide(server, env, orgNamespace); // 同上,2026-07-21 停用
registerCreateTag(server, env, orgNamespace);
registerListTags(server, env, orgNamespace);
registerDeleteTag(server, env, orgNamespace);
+96 -1
View File
@@ -8,7 +8,102 @@
## 待裁決
(無
### P-2026-07-21registry 寫入端+搜尋端修復(leo 親自交辦,最高優先
> 提案人:總管交辦之 arcrun subagent。
> 觸發:leo 2026-07-21 —「一旦推進一個零件,就自動進 registryrecipe、workflow、app 都應該可以 registry
> 不然搜不到。一邊是寫進去,另一邊是搜到,當然要有。」
> 判準:「Arcrun = 讓 AI 輕易建立程式碼」「AI 要覺得 Arcrun 比 Python 還簡單,絕不可迷路」。
> **依 D35:本任務找不到對應的 active SDD(現行 activeportal-auth,與本題無關),
> 故不動 code、不自建 SDD,寫本 proposal 後停止等 leo confirm。**
#### 一句話
registry 的寫入機制**存在且可用**,但它唯一的自動觸發點長在 GitHub Actions 上;
Actions 因防 flag 鐵律被整個刪除後(commit `037cf9b`),**寫入端失去觸發者、庫從此是空的**。
這不是「沒有機制」,是「機制的手被砍掉、沒補上替代觸發點」。
#### 根因(file:line 級)
| 事實 | 證據 |
|---|---|
| 寫入端點存在 | `registry/src/routes/components.ts:112` `POST /components/index-only`metadata-only 索引,冪等) |
| 寫入實作存在 | `registry/src/actions/indexOnlyComponent.ts:44``comp:{hash}:{ver}` + `idx:{canonical_id}` 兩個 KV key |
| 批次灌注腳本存在 | `registry/scripts/backfill-index.mjs`(掃 22 個 contract.yaml → POST index-only |
| 單顆註冊腳本存在 | `registry/scripts/register-component.sh`(註解自稱「本地+CI 共用 SSOT」) |
| **唯一自動觸發點已不存在** | `git show 037cf9b^:.github/workflows/deploy.yml` 第 269-275 行有 `Register component in registry` step 呼叫上述 sh`037cf9b` 刪除整個 `.github/workflows/`,**該 step 隨之消失,無替代品** |
| 從未跑過的實證 | 對 leo21c 跑 `REGISTRY_URL=... node scripts/backfill-index.mjs --dry-run` → 22 顆待灌;線上 `/components/search?q=http``count:0` |
**答案:是「有機制但從沒跑過」**(且失去觸發者),不是「沒有寫入機制」。
→ 附帶事實:`registry/wrangler.toml:26``[[routes]]` 寫死 `registry.arcrun.dev`
self-hostedleo21c)只能靠 workers.dev URLbackfill 腳本預設 `REGISTRY_URL` 也是官方域 —— 需帶環境變數才會打到自己的庫。
#### 重要更正:leo 要的東西**有一半已經存在,只是不在 registry 上**
`acr search <term>``cli/src/commands/search.ts`)已經是 leo 描述的「意圖驅動、跨類一次搜」——
它 fan-out 四個來源(component 靜態清單 / `/recipes` / `/auth-recipes` / `/webhooks/named`)。
**實測(2026-07-21leo21c,真實輸出見交辦回報)**:
- `acr search http` → 命中零件 `http_request`(驗收劇本 1 ✅)
- `acr search notify` → 命中 recipe `line_notify_send` + auth-recipe `line_notify`(劇本 2 部分達成)
- `acr search github` → 命中 auth-recipe `github`(驗收劇本 3 ✅)
- `acr search telegram` → 命中 recipe `telegram_send` + auth-recipe `telegram`
**能力在 CLI 有、在 registry/MCP 沒有**。這正是薄殼原則(rule 07)被違反的典型:
同一個「跨類搜尋」能力長在介面層(CLI)而非 API,於是另一個薄殼(MCP)享受不到。
**修法的方向不是在 registry 重造一套搜尋,而是把 `acr search` 的 fan-out 能力下沉成 API 端點,CLI/MCP 同吃。**
#### 提案內容(四件,全部需 confirm 後才動)
**A. 寫入端:補回失去的觸發點(不復活 Actions)**
- A1. `acr push` / `acr deploy` 成功後自動呼叫 `POST /components/index-only`(本機發起、低頻、單 repo,守 D20 讀寫界線與防 flag 鐵律)。
- A2. `acr init` / `acr update` 部署完 22 顆零件後跑一次 backfill(讓新裝的人開箱即有索引)。
- A3. **recipe / workflow 不需要新的 registry 寫入路徑**——它們本來就活在 store(KV),
`acr recipe push` / `acr push` 當下就已「寫進去」。缺的只是**搜尋端讀得到**(見 B)。
→ leo 說的「recipe、workflow、app 都應該可以 registry」,正解是**統一搜尋面**,不是把它們搬進 registry KV 再存一份(那會製造第二份真相源)。
- A4. `app`bundle)已有 P-2026-07-19 artifact-sharing 卷涵蓋,本卷不重複立案,只在搜尋面預留類別。
**B. 搜尋端:能力下沉 + 語意**
- B1. 在 **cypher-executor**`GET /search?q=`(不是 registry——因為 recipe/workflow 的真相源在 cypher 的 KV),
server 端做 `acr search` 現有的四類 fan-out,回統一結果(含 `type` 欄位)。
- B2. `acr search` 與 MCP 新 tool `arcrun_search`(或改造 `arcrun_search_components`)雙雙改為呼叫 B1,介面層不再自行 fan-out(回歸薄殼)。
- B3. 語意搜尋:KBDB 那條路已驗證可用(Vectorize),把四類的 description 灌進去,讓「我要打一個 HTTP API」這種自然語言句命中 `http_request`
現行 `registry/src/actions/queryComponents.ts:104` 明載「這是 Phase 0 的純文字比對版本,Phase 2 接入 Vectorize」——本項即補完該 Phase 2。
- B4. 搜不到時的文案改為「導向 recipe / 既有 workflow」,**移除「可以用 arcrun_publish_component 提交新零件」的建議**(見 C)。
**C. 斷掉「推人去寫零件」的路**
- C1. `mcp/src/tools/arcrun_search_components.ts:50` 搜不到時明文建議 `arcrun_publish_component` —— 違反 leo 既定規矩(零件走 PR、專業等級)。改為導向 recipe/workflow。
- C2. `arcrun_get_component_guide`(回 TinyGo 寫 WASM 教學)與 `arcrun_publish_component` 對一般使用者是誤導 → 建議降級為「專業模式」才暴露(預設不掛載),或在描述首句明寫「99% 情況你不需要這個,先用 recipe」。
- C3. **查證結果:零件相關「已拆到另一個 repo」未獲證實**——`registry/components/` 22 顆零件仍在本 repo
wiki 與 git 均查無拆分紀錄。leo 的印象可能來自 `.github-public/` 對外鏡像(`GitHub mirror 發佈模型`)。**此點需 leo 確認**。
**D. 過時項**
- D1. MCP 仍要求已廢的 `api_key` 參數者共 **15 處**`arcrun_introspection.ts`4)、`arcrun_recipe.ts`6)、`arcrun_workflow_crud.ts`5)。
2026-07-20 已改 namespace 明碼 → 這些應改為由 server 端 namespace 推導,不再要 AI 傳。
- D2. **1042 的真正解法已確認並已落地**(本項無需修 code,只需更正記載):
`global_fetch_strictly_public` compatibility flag,已實裝於 `cypher-executor/wrangler.toml:13`
`.component-builds/http_request/wrangler.toml:8`
leo 說的「開了一個什麼,把所有呼叫都視為外部」=**這個 flag**(讓 same-zone fetch 走公網前門)。
wiki 已有正確記載:`system-dev/wiki/cards/decisions/same-zone-1042用flag解不用binding.md`
`mistakes.md:101``decisions-summary.md:84`
→ 「graph_neighbors 改用 recipe 繞過」的過時說法**在本 repo wiki 查無此記載**status.md:50-52 只寫 MCP tool PR),
該過時記載可能在頂層 InkStoneCo wiki 或 MEMORY.md,需由總管在該層更正。
#### 影響分析(D35 第 3 條要求)
- **現行 active SDDportal-auth**:完全不受影響,本提案不碰其任務。
- **`component-registry-canon`status: paused2026-05-07 建)****本題其實早有此卷**,
其 §1.2 診斷的根因與今日實測完全一致(「registry 活著但 index 空的,AI 找不到零件就會繞回 Python」),
尚有 **29 個未完成任務**。→ **建議:不新開 SDD,改為把此卷 resume 成 active**(並把 A/B/C/D 的新項增補進其 tasks),
這比另立新卷更符合 D35 第 4 條「先搬移未完成任務」的精神,也避免第二份重疊規格。
⚠️ 但這需要先把 portal-auth 收尾或轉 paused(單一活性鐵律),**此為 leo 的排序決策,不是 CC 能裁的**。
- **`workflow-discovery`paused)/`library-map`(draft)**:與 B1 統一搜尋面高度重疊,resume 時應一併檢視是否合併。
#### 待 leo 拍板的四點
1. **排序**:要不要把 `portal-auth` 讓位、resume `component-registry-canon` 來做這件事?(單一活性鐵律強制二選一)
2. **C2**`get_component_guide` / `publish_component` 要「預設不掛載」還是「留著但改描述」?(品味/方向)
3. **C3**:零件是否真的已拆到另一個 repo?(leo 記憶待證實)
4. **B3 語意搜尋**:四類描述灌進 Vectorize 會產生 embedding 呼叫成本,確認可行?(花錢)
## 已裁決