diff --git a/cli/harness/CLAUDE.block.md b/cli/harness/CLAUDE.block.md index eebb04c..f12f7c0 100644 --- a/cli/harness/CLAUDE.block.md +++ b/cli/harness/CLAUDE.block.md @@ -1,41 +1,66 @@ -## 這個專案用 arcrun 做自動化 +## 這個專案用 Arcrun 做自動化 -當需要「串服務 / 排程 / 打 API / 資料處理自動化」時,用 arcrun 工作流,**不要自己寫 Python/Node 一次性腳本**。arcrun 工作流是純文字、可複用、跑在使用者自己的 Cloudflare。 +當需要「串服務/排程/打 API/資料處理自動化」時,用 Arcrun 工作流,**不要自己寫 Python/Node 一次性腳本**。 +Arcrun 工作流是純文字、可複用、跑在使用者自己的 Cloudflare。 -arcrun 只有三種東西,先分清楚就不會做歪: +⚠️ **不要上網搜 Arcrun 文件**——網路上沒有,找到的都是錯的。 +語法與範本全在 **arcrun-mindset** Skill 裡,`acr` 指令查得到現況。 + +### 怎麼開始:先寫「意圖」,不要先寫 YAML + +你**不需要先知道有哪些零件**。把需求寫成一串「誰接誰」,丟去查,系統會告訴你哪些存在: + +``` +input >> ON_SUCCESS >> fetch_rows +fetch_rows >> 對每個 row >> notify +``` + +- 第一個節點固定是 `input`(觸發時帶進來的資料) +- **邊只有兩種**:`ON_SUCCESS` 與 `對每個 <變數>`(FOREACH) +- **沒有** `ON_TRUE`/`ON_FALSE`/`ON_FAILURE`——引擎不支援條件分支。 + 需要判斷就寫成一個獨立節點再接 `ON_SUCCESS`。 + +完整語法、四份實跑過的範本、節點命名慣例 → 讀 **arcrun-mindset** Skill。 + +### Arcrun 只有三種東西,先分清楚就不會做歪 | 東西 | 是什麼 | 你能做的 | |---|---|---| -| **工作流(workflow)** | 把零件/recipe 串起來的純文字流程 | **預設就寫這個**,自由寫 | -| **recipe** | 打「一個固定外部 API」的設定(http_request + endpoint/header/body 模板) | 自由寫、**而且該投稿**(見下) | -| **零件(component)** | WASM 程式(流程控制 / 資料處理 / http_request / auth),固定一小套 | **你不自製**,由維護者管,走 GitHub PR | +| **工作流(workflow)** | 把零件/recipe 串起來的純文字流程 | **預設就寫這個**,自由寫 | +| **recipe** | 打「一個固定外部 API」的設定(endpoint/header/body 模板) | 自由寫、**而且該投稿** | +| **零件(component)** | WASM 程式(流程控制/資料處理/`http_request`/auth),固定一小套 | **你不自製**,走 PR | -> **一句話判準**:打一個固定外部 endpoint → 寫 **recipe**;流程控制 / 資料處理 / 通用 HTTP → 用既有**零件**;其他 → 寫**工作流**串起來。 +> **一句話判準**:打一個固定外部 endpoint → 寫 **recipe**;流程控制/資料處理/通用 HTTP → 用既有**零件**;其他 → 寫**工作流**串起來。 +> +> ⚠️ 常見錯誤:把 `telegram_send`/`gmail_send`/`kbdb_get` 當**零件**寫。它們是 **recipe**。 -### 三個最常踩的坑(讀懂這三條,就不會像前人誤解四輪) +### 查詢回 `not_found` 時:兩條路,都不准改寫成 code -**① recipe 是公共資產,發現缺的就自己做一個投稿,不必問人。** -`acr recipe push` = 把 recipe **投稿到公共庫**,不是存私人腳本。公開/私有只是 recipe 的一個屬性(同一身份可有公私版本並存),不是兩條不同的路。 -→ 所以「想打某個 API 但沒有現成 recipe」時,**正解是自己寫一個 recipe 並 push 上去**(會 inject credential、push 時幫你檢查打不打得通)。這是被鼓勵的,別誤以為要自用、不上傳。 +查詢會誠實回報(兩庫都查過才點名): -**② 缺一個能力 → 去補 API(cypher endpoint),不准用 recipe / 多步工作流拼裝出來。** -判準口訣:**「這段邏輯換一個介面(CLI→MCP)要不要重寫?要重寫 → 它是『能力』,該長在 API。」** -- ❌ 缺 `upsert` → 在工作流裡拼「先查、沒有再建」、或寫個 recipe 假裝補上。 -- ✅ 缺 `upsert` → 去 API 加一個 `upsert` endpoint,CLI/MCP/recipe 都呼叫它。 -recipe 只負責「打一個固定外部 API」這件單純事;它不是用來補 arcrun 自己缺的能力的。缺能力就回報 / 補在 API,不要繞。 +| status | 意思 | 你該做什麼 | +|---|---|---| +| `found` / `resolved` | 有現成的可用 | **只填 payload** | +| `not_found` | 零件庫與 recipe 庫都沒有 | 照回應的 `suggestion` 走(見下兩條路),並看 `similar_components`/`similar_recipes` 有沒有能直接用的 | +| `unknown` | 查不到 registry(未部署/網路失敗) | **不代表不存在**,別據此改寫成 code | -**③ 已經有自製零件(例如 mira 的那幾個)→ 讓它退場,別再加新的。** -你不該自製零件;既有的自製零件要往這三條退場: -- `claude_api` 之類「工作流回頭叫 LLM」→ **刪掉**,需要 AI 判斷時是**你(操盤的 CC)自己做**,再叫工作流做確定性的下一步。arcrun 是 AI 用的工具,不是工具回頭用 AI。 -- `kbdb_*` 之類資料存取 → 改走已備好的 **`acr kbdb` 薄殼 / `kbdb_*` MCP 工具**(template + record 模型),不要當零件。 -- 純粹打某個固定外部 API 的假零件 → **改寫成 recipe** 投稿(見①)。 +- **缺外部 API** → **自己寫一個 recipe** 並 `acr recipe push`(幾行 YAML,不用部署 Worker、不用寫程式)。 + recipe 是公共資產,發現缺的就補一個投稿,不必問人。 +- **缺計算能力**(加解密/壓縮這類純運算) → 投稿**零件 PR**(要人類確認,罕見)。 + +🔴 **查不到就改寫成 `code` 節點 =「腹語術」**(表面用 Arcrun、實際全寫 JS)。 +`code` 只用於**局部整形**(例:剝掉 LLM 回應的雜訊、切段落),不用來取代零件與流程控制。 +> 實錄:每一個寫進 `code` 的 `if` 都是沒被測過的新 bug;零件的價值是「被測過 1000 次」,寫進 code 就歸零。 ### 其餘鐵律 -- **先查能力再動手**:`acr parts`(看可用零件)、`acr auth-recipe list`(看支援的認證服務)、`acr kbdb`(資料存取)。 -- **暴露資料要人類同意**:部署對外 webhook / push recipe 會讓東西可被外部呼叫 → 停下來讓使用者明示同意,不替他決定公開。 -- **誠實**:沒打通就誠實說(缺 credential 標「未驗收:缺 X」),不假裝成功;完成以 HTTP 2xx / trace 為證,不口頭宣布。 +- **先查能力再動手**:`acr search <關鍵字>`(一次掃零件/recipe/auth-recipe/workflow)、 + `acr parts`(零件)、`acr recipe list`(recipe)、`acr auth-recipe list`(支援的認證)。 +- **需要 AI 判斷時你自己做**,不要讓工作流回頭呼叫 LLM。Arcrun 是 AI 用的工具,不是工具回頭用 AI。 +- **金鑰只拿名字**:定義裡只寫 `{{credential.<名字>}}`,真身絕不寫進 workflow/recipe 檔案。 +- **暴露資料要人類同意**:`acr push`/`acr recipe push` 會讓東西可被外部呼叫 → 停下來讓使用者明示同意,不替他決定公開。 +- **誠實**:沒打通就誠實說(缺 credential 標「未驗收:缺 X」),不假裝成功;完成以 HTTP 2xx/trace 為證,不口頭宣布。 -開始前讀 **arcrun-mindset** Skill(世界觀)。使用者技術細節交給你,CLI 操作你來做。 +開始前讀 **arcrun-mindset** Skill(意圖語法+範本+世界觀)。使用者技術細節交給你,CLI 操作你來做。 diff --git a/cli/harness/commands/arcrun.md b/cli/harness/commands/arcrun.md index 1c52b27..7bd39b3 100644 --- a/cli/harness/commands/arcrun.md +++ b/cli/harness/commands/arcrun.md @@ -1,26 +1,60 @@ -# 用 arcrun 完成這個自動化需求 +# 用 Arcrun 完成這個自動化需求 -使用者想做一個自動化。你的任務:用 arcrun 做出來,全程不要讓使用者自己寫程式。 +使用者想做一個自動化。你的任務:用 Arcrun 做出來,全程不要讓使用者自己寫程式。 + +⚠️ **不要上網搜 Arcrun 文件**(網路上沒有)。先讀 **arcrun-mindset** Skill,再用 `acr` 指令查現況。 ## 鐵則 -- **用 arcrun 工作流 / recipe,絕不自己寫 Python/Node 腳本。** 使用者選 arcrun 就是不想要一次性腳本。 -- 打外部 API → 寫 recipe(`acr recipe push`),不自刻 HTTP client。 -- 不自製零件(WASM)—— 零件由 arcrun 維護。你能用的是現有零件 + recipe + 工作流。 -- 需要 AI 判斷時你自己做,不要讓工作流回頭呼叫 LLM。 +- **用 Arcrun 工作流/recipe,絕不自己寫 Python/Node 腳本。** 使用者選 Arcrun 就是不想要一次性腳本。 +- **打外部 API → 寫 recipe**(`acr recipe push`),不自刻 HTTP client。缺 recipe 就自己補一個,不必問人。 +- **不自製零件(WASM)**——零件由 Arcrun 維護。你能用的是現有零件 + recipe + 工作流。 +- **需要 AI 判斷時你自己做**,不要讓工作流回頭呼叫 LLM。 +- 🔴 **查不到零件就改寫成 `code` 節點 = 腹語術**,禁止。缺 API 寫 recipe、缺能力投稿零件。 ## 步驟 -1. 先讀 **arcrun-mindset** Skill(世界觀 + 資源去哪取)。 -2. 跑 `acr parts` 看零件、`acr auth-recipe list` 看支援的認證。**先查再動手。** -3. 把使用者需求拆成工作流(哪些零件、什麼順序、什麼條件),寫成 `.yaml`。 -4. 需要 credential(API key / token)→ 用 `acr auth-recipe scaffold ` 看要哪些, - 明確告訴使用者去哪取得、怎麼 `acr creds push`。 -5. `acr validate` 通過後 `acr push` 部署,告訴使用者 webhook URL / 怎麼 `acr run`。 -6. 完成給客觀證據(HTTP 2xx / trace),不要只說「做好了」。 -## 遇到要暴露資料(對外 webhook) +### 1. 先寫「意圖」,不要先寫 YAML +把使用者的需求寫成一串「誰接誰」(**不必是真實零件名**,用你想得到的名字即可): + +``` +input >> ON_SUCCESS >> fetch_rows +fetch_rows >> 對每個 row >> notify +``` + +- 第一個節點固定是 `input` +- 邊只有 `ON_SUCCESS` 與 `對每個 <變數>`(**沒有** `ON_TRUE`/`ON_FALSE`/`ON_FAILURE`) +- 需要判斷 → 寫成獨立節點(例 `check_amount`)再接 `ON_SUCCESS` + +語法細節、四份實跑過的範本、節點命名慣例 → **arcrun-mindset** Skill。 + +### 2. 丟去查,讓系統告訴你有什麼 +`acr search <關鍵字>` 一次掃零件/recipe/auth-recipe/workflow; +或把意圖串丟 `/cypher/search`,逐節點拿 `found` / `resolved` / `not_found` / `unknown`。 + +- `found`/`resolved` → **只填 payload** +- `not_found` → 照回應的 `suggestion` 走(缺 API 寫 recipe、缺計算能力投稿零件), + 並看 `similar_components`/`similar_recipes` 有沒有現成能用的 +- `unknown` → **不代表不存在**,別據此改寫成 code + +### 3. 把意圖變成 workflow YAML +節點填上查到的真實零件/recipe + payload。 +需要 credential 時:`acr auth-recipe scaffold ` 看要哪些,明確告訴使用者去哪取得、怎麼 `acr creds push`。 +🔑 定義裡只寫 `{{credential.<名字>}}`,**真身絕不寫進檔案**。 + +### 4. 驗證 → 部署 → 給證據 +```bash +acr validate .yaml # 先驗 +acr push .yaml # 部署(暴露動作,見下) +acr run # 觸發一次 +acr logs # 看執行紀錄 +``` +完成要給客觀證據(HTTP 2xx/trace),不要只說「做好了」。 + +## 遇到要暴露資料(對外 webhook/recipe 投稿) 停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。不要替他決定公開。 +非互動環境下把完整指令印給使用者自己貼上跑。 -## 還沒設定好 arcrun? +## 還沒設定好 Arcrun? 若 `acr` 指令不存在或還沒 `acr init`:先帶使用者完成前置設定 (裝 CLI → 拿 Cloudflare 帳號的兩串憑證 → `acr init --self-hosted`)。 拿 Cloudflare 憑證時用白話照抄式引導,不要對使用者講 KV / Worker / R2 等術語。 diff --git a/cli/harness/hooks/arcrun-guard.sh b/cli/harness/hooks/arcrun-guard.sh index cf7388a..5831f90 100644 --- a/cli/harness/hooks/arcrun-guard.sh +++ b/cli/harness/hooks/arcrun-guard.sh @@ -66,7 +66,7 @@ if echo "$CMD" | grep -qE "acr (push|recipe push)\b"; then if echo "$EXEC_PART" | grep -qE "(^|[;&|][[:space:]]*)acr[[:space:]]+(push|recipe[[:space:]]+push)\b"; then if [ ! -t 0 ] && [ "${ARCRUN_HUMAN_CONFIRMED:-}" != "1" ]; then block "在非互動環境自動執行暴露動作(acr push / recipe push 會讓東西可被外部呼叫)" \ - "交人類在終端機執行(真 TTY 會自動放行)。可把指令完整複製給使用者貼上自己跑:\`acr push <你的 workflow.yaml>\`。或使用者先在對話明示同意後親自於終端機執行。不要替使用者決定公開。" + "交人類在終端機執行(真 TTY 會自動放行)。可把指令完整複製給使用者貼上自己跑:\`acr push <你的 workflow.yaml>\`。或使用者先在對話明示同意後親自於終端機執行。不要替使用者決定公開。(部署前的正路見 arcrun-mindset Skill:先 \`acr validate\`)" fi fi fi @@ -76,15 +76,29 @@ fi if echo "$CMD" | grep -qE "(^|[;&| ])(python3?|node)[ ]+[^ ]+\.(py|js|mjs|ts)\b"; then # 排除明顯的測試 / 既有工具呼叫(pytest / npm test / jest 等)降低誤判 if ! echo "$CMD" | grep -qE "(pytest|jest|vitest|npm (run )?test|mocha|\btest_)"; then - remind "偵測到用 python/node 跑腳本。這專案用 arcrun,串服務/自動化不要自刻一次性腳本。" \ - "先跑 \`acr parts\` 看有哪些零件,把需求寫成 workflow.yaml 用 \`acr run\`。若這確實不是自動化(例如跑測試/別的工具),忽略本提醒。" + remind "偵測到用 python/node 跑腳本。這專案用 Arcrun,串服務/自動化不要自刻一次性腳本。" \ + "讀 arcrun-mindset Skill,先把需求寫成「意圖」串(\`input >> ON_SUCCESS >> <下一步>\`,邊只有 ON_SUCCESS 與「對每個 X」),再用 \`acr search <關鍵字>\` 查哪些零件/recipe 存在,最後才寫 workflow.yaml → \`acr validate\` → \`acr run\`。若這確實不是自動化(例如跑測試/別的工具),忽略本提醒。" fi fi # ── 提醒(不硬擋):自寫打固定 API 的 script,而非 recipe ────────────── if echo "$CMD" | grep -qE "(curl|fetch|requests\.(get|post)|axios).*https?://"; then - remind "偵測到自己打外部 API。arcrun 裡「打固定 endpoint」應寫成 recipe,不自刻 HTTP 呼叫。" \ - "用 \`acr recipe push\` 把這個 API 包成 recipe,workflow 裡用 component 引用它。見 arcrun-mindset Skill。" + remind "偵測到自己打外部 API。Arcrun 裡「打固定 endpoint」應寫成 recipe,不自刻 HTTP 呼叫。" \ + "先 \`acr recipe search <服務名>\` 看有沒有現成的;沒有就自己寫幾行 YAML(canonical_id/endpoint/method/auth_service)用 \`acr recipe push\` 投稿,workflow 裡用 \`http_request\` + 該 recipe 引用它。缺 recipe 就自己補,不必問人。寫法見 arcrun-mindset Skill。" +fi + +# ── 提醒(不硬擋):把 code 節點當成缺零件的替代品(「腹語術」)────────────── +# 查詢回 not_found 就改寫成 code = 表面用 Arcrun、實際全寫 JS。這是現世代最常見的走歪。 +if [ "$TOOL" = "Write" ] || [ "$TOOL" = "Edit" ] || [ "$TOOL" = "MultiEdit" ]; then + FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""') + CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""') + if echo "$FILE" | grep -qE '\.(ya?ml)$' && echo "$CONTENT" | grep -qE 'component:[[:space:]]*["'"'"']?code\b'; then + # 只在 code 內容看起來在做流程控制/取代零件時提醒(含 if/for/fetch),單純整形不吵 + if echo "$CONTENT" | grep -qE '\b(if[[:space:]]*\(|for[[:space:]]*\(|fetch\(|await[[:space:]]+fetch)'; then + remind "workflow 裡的 \`code\` 節點含流程控制/HTTP 呼叫——這可能是「腹語術」(表面用 Arcrun、實際全寫 JS)。" \ + "\`code\` 只用於局部整形(例:剝掉 LLM 回應的雜訊、切段落)。缺外部 API → 寫 recipe(\`acr recipe push\`);缺計算能力 → 投稿零件 PR;要判斷 → 寫成獨立節點接 \`ON_SUCCESS\`(引擎沒有條件邊)。每個寫進 code 的 if 都是沒被測過的新 bug。見 arcrun-mindset Skill。" + fi + fi fi exit 0 diff --git a/cli/harness/settings.fragment.json b/cli/harness/settings.fragment.json index 56a0aab..5d961ba 100644 --- a/cli/harness/settings.fragment.json +++ b/cli/harness/settings.fragment.json @@ -10,6 +10,16 @@ "timeout": 5 } ] + }, + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { + "type": "command", + "command": ".claude/hooks/arcrun-guard.sh", + "timeout": 5 + } + ] } ] } diff --git a/cli/harness/skills/arcrun-mindset/SKILL.md b/cli/harness/skills/arcrun-mindset/SKILL.md index a039a5a..4fcc5d8 100644 --- a/cli/harness/skills/arcrun-mindset/SKILL.md +++ b/cli/harness/skills/arcrun-mindset/SKILL.md @@ -1,78 +1,245 @@ --- name: arcrun-mindset description: >- - arcrun 的世界觀 — 用 arcrun 開發自動化時的預設心態 + 資源去哪取。當你(AI 操盤手)要在 - arcrun 上做任何事(串服務、處理資料、認證、把東西開放給人用)前讀這個。它讓你做出「方向對」 - 的選擇、知道資源在哪,避免技術上能跑但架構上錯、或自己重刻 arcrun 已有的東西。 + 在 Arcrun 上做任何事時使用(用戶說「幫我用 Arcrun 做 X」「用 arcrun 寫一個工作流」「把 X 自動化」)。 + Arcrun 是跑在 Cloudflare 上的工作流引擎——你用 `>>` 寫「意圖」,系統告訴你有哪些現成零件與 recipe, + 你只填 payload,不必自己寫程式。**不要上網搜 Arcrun 文件**(網路上沒有),也不要自己猜 YAML 格式: + 先讀本 skill,再用 `acr` 指令(或 MCP 工具)查現成零件。 + 涵蓋:意圖工作流語法、四份實跑過的範本、零件 vs recipe 的分別、缺件的兩條路、已知的坑。 --- -# arcrun mindset(給 AI 操盤手) +# Arcrun:怎麼寫意圖工作流 -你在 arcrun 上幫使用者開發自動化。arcrun 很簡單,簡單到你常會把它想複雜、或退回自己熟悉的 -Python/Node 自刻。這份幫你在岔路上選對方向,並告訴你資源在哪。 +> **你已經配備 Arcrun**(此專案裝了 `acr` CLI,可能另有 `arcrun_*` MCP 工具)。 +> **別上網找文件**——網路上沒有 Arcrun 的文件,找到的都是錯的。答案都在本 skill 與 `acr` 指令裡。 + +## 先做這三件(照順序) + +1. `acr whoami` — 確認連到哪個帳號(**勿自行 curl 猜帳號 URL**) +2. 讀本 skill 下面的語法與範本 → 寫出 `>>` 意圖 +3. `acr parts`/`acr recipe list`(或 `acr search <關鍵字>` 一次掃全部)— 確認零件與 recipe 真的存在 + +**卡住時**:`acr search <關鍵字>` 跨類搜尋;有 MCP 就 `arcrun_get_skill('INDEX')` 拿全館導航。 --- ## 0. 一句話世界觀 -**arcrun 裡幾乎所有東西都是工作流(workflow)。** 工作流 = 一張紙,寫「用哪些零件、什麼順序、什麼條件」。 -你大部分時間在寫紙、改紙,不是在造新零件、也不是自己寫腳本。 +**Arcrun 裡幾乎所有東西都是工作流(workflow)。** 工作流 = 一張紙,寫「用哪些零件、什麼順序、什麼條件」。 +你大部分時間在**寫紙、改紙**,不是在造新零件、也不是自己寫腳本。 + +**Arcrun 只有三種東西,先分清楚就不會做歪:** + +| 東西 | 是什麼 | 你能做的 | +|---|---|---| +| **工作流(workflow)** | 把零件/recipe 串起來的純文字流程 | **預設就寫這個**,自由寫 | +| **recipe** | 打「一個固定外部 API」的設定(endpoint/header/body 模板) | 自由寫、**而且該投稿**(缺就自己補) | +| **零件(component)** | WASM 程式(流程控制/資料處理/`http_request`/auth),固定一小套 | **你不自製**,走 PR 由維護者管 | + +> **一句話判準**:打一個固定外部 endpoint → 寫 **recipe**;流程控制/資料處理/通用 HTTP → 用既有**零件**;其他 → 寫**工作流**串起來。 --- -## 1. 工作流是 default,不要退回自己寫 Python + -使用者選 arcrun,就是不要「每次重刻、跑完即丟」的腳本。所以你的預設順序: +## 1. 意圖工作流的語法 -1. **先想能不能用工作流做**(串現有零件 / recipe + 流程控制)。99% 可以。 -2. 要打的服務有 HTTP API、但沒有對應 recipe → **寫一個 recipe**(http_request + 固定設定 YAML,不用部署、不用審核)。 -3. **只有**封閉純邏輯(流程控制 / 資料處理)、現有零件不夠、且值得全 arcrun 重用 → 才考慮零件(而零件走 PR,不是你現在做)。 +一串「誰接誰」,每行一個關係: -> 典型走歪:「我先用 Python 測一下」。停。使用者要的是 arcrun 工作流。先 `acr parts` 看有什麼,用工作流串。 +``` +<節點A> >> <邊> >> <節點B> +``` -## 2. 資源去哪取(不要自己重造 arcrun 已有的) +- **節點**=一個步驟。用你想得到的名字(中文可以),**不必是真實零件名** +- **邊**=什麼情況下往下走 + +## 2. 邊只有兩種(真範本裡出現過的) + +| 邊 | 意思 | 真例 | +|---|---|---| +| `ON_SUCCESS` | 上一步成功就往下 | `input >> ON_SUCCESS >> prep` | +| `對每個 <變數>` | 上一步產出清單,逐項處理(FOREACH)| `parse_card >> 對每個 block >> post_block` | + +⚠️ **不要寫 `ON_FAILURE`/`ON_TRUE`/`ON_FALSE`**——引擎目前**沒有條件分支** +(實測 `grep ON_TRUE|ON_FALSE` 於 cypher-executor = 0;見 Gitea Arcrun#5)。 +需要判斷時:**寫成一個獨立節點**(例 `check_amount`)再接 `ON_SUCCESS`, +讓查詢告訴你有沒有零件可用。 + +## 3. 第一個節點固定是 `input` + +所有真範本都以 `input` 起頭——那是「觸發時帶進來的資料」。 + +--- + +## 4. 真範本(照抄結構、改內容) + +> 以下四份**全部是實際部署且 `verdict=success` 的 workflow**,不是簡化示範。 +> 用 `acr logs `(有 MCP 則 `arcrun_get_workflow()`) 可以拿完整定義。 + +### A. 最短:取資料 → 處理 (`graph_neighbors`) +``` +input >> ON_SUCCESS >> fetch_triplets +fetch_triplets >> ON_SUCCESS >> bfs_neighbors +``` + +### B. 長鏈:多次查詢 → 組裝 → 問 AI → 收尾 (`rag_chat`) +``` +input >> ON_SUCCESS >> prep +prep >> ON_SUCCESS >> kw_search +kw_search >> ON_SUCCESS >> sem_search +sem_search >> ON_SUCCESS >> fetch_triplets +fetch_triplets >> ON_SUCCESS >> fetch_blocks_a +fetch_blocks_a >> ON_SUCCESS >> assemble +assemble >> ON_SUCCESS >> ask_llm +ask_llm >> ON_SUCCESS >> finalize +``` +`prep` 前處理/`assemble` 組 prompt/`finalize` 收拾回應——三個常見的整形節點。 + +### C. 一節點分岔兩條 FOREACH (`rag_ingest_card`) +``` +input >> ON_SUCCESS >> parse_card +parse_card >> 對每個 block >> post_block +parse_card >> 對每個 rel >> post_triplet +``` +同一節點可有多條出邊,各自處理不同清單。 + +### D. 混合:直線 + 兩段 FOREACH (`rag_takedown_direct`) +``` +input >> ON_SUCCESS >> prep +prep >> ON_SUCCESS >> list_dead_blocks +list_dead_blocks >> ON_SUCCESS >> build_deprecations +build_deprecations >> 對每個 dead_entry >> deprecate_entry +build_deprecations >> ON_SUCCESS >> list_triplets +list_triplets >> ON_SUCCESS >> pick_dead_triplets +pick_dead_triplets >> 對每個 dead_record >> deprecate_triplet +``` +`build_deprecations` 同時有 FOREACH 出邊與 `ON_SUCCESS` 出邊—— +前者處理清單、後者繼續主線。 + +--- + +## 5. 節點怎麼命名(照真範本的模式,查詢較容易媒合) + +| 意圖 | 模式 | 真例 | +|---|---|---| +| 前處理/正規化 | `prep` | `rag_chat.prep` | +| 取一批資料 | `fetch_*`/`list_*` | `fetch_triplets`/`list_dead_blocks` | +| 搜尋 | `*_search` | `kw_search`/`sem_search` | +| 解析/切塊 | `parse_*` | `parse_card` | +| 寫入 | `post_*` | `post_block`/`post_triplet` | +| 組裝 | `assemble`/`build_*` | `assemble`/`build_deprecations` | +| 問 AI | `ask_llm` | `rag_chat.ask_llm` | +| 收尾整形 | `finalize` | `rag_chat.finalize` | + +--- + +## 6. 寫完一定要查(**不要直接部署**) + +```bash +curl -s -X POST https://arcrun-cypher-executor..workers.dev/cypher/search \ + -H 'content-type: application/json' -H 'X-Arcrun-API-Key: ' \ + -d '{"triplets":["input >> ON_SUCCESS >> fetch_data","fetch_data >> ON_SUCCESS >> notify"]}' +``` + +回應的每個節點會有: + +| status | 意思 | 你該做什麼 | +|---|---|---| +| `found` | 有這個節點。`source: component` 附 `input_schema`(怎麼填 payload)與 `success_rate`;`source: recipe` 附 description/endpoint | **只填 payload** | +| `not_found` | **兩庫(零件 registry+recipe 庫)都查過,確定沒有** | 照 `suggestion` 欄走:缺 API → 寫 recipe(skill `write_recipe`);缺計算能力 → 投稿零件 PR(skill `add_new_wasm_component`)。`similar_components`/`similar_recipes` 是相近候選——先看有沒有現成的能直接用 | +| `unknown` | 查不到 registry | **不代表不存在**,別據此改寫成 code | + +> 註(2026-07-31):`/cypher/search` 曾對任何節點名都回假 `found`,已修為真查兩庫。 +> 舊實例(未更新部署)仍可能假 found——status 可信度以該實例部署版本為準。 + +--- + +## 7. 常犯的錯 + +1. **用不存在的邊**(`ON_FAILURE`/`ON_TRUE`)→ 只有 `ON_SUCCESS` 與 `對每個 X` +2. **第一個節點不是 `input`** +3. **把 recipe 當零件寫**——`telegram_send`/`gmail`/`kbdb_get` 是 **recipe** 不是零件 + → 寫成 `http_request` + 該 recipe +4. 🔴 **查詢回 `not_found` 就改寫成 `code` 節點** + → 那叫「腹語術」(表面用 Arcrun、實際全寫 JS)。正解:缺 API 寫 recipe、缺能力投稿零件。 + `code` 只用在**局部整形**(例:剝掉 LLM 回應的雜訊),不用來取代零件與流程控制。 + +--- + +## 8. 相關 + +- 完整版指引與十題考卷(含 haiku 實測 10/10): + 頂層 repo `system-dev/docs/3-specs/arcrun-usable/` +- 下一步該讀哪支 skill(需 MCP):`arcrun_list_skills()` + - 定期掃資料 → `build_watcher_workflow` + - RAG 檢索問答 → `rag_with_arcrun` + - workflow 卡住不動 → `debug_paused_workflow` + + +--- + +## 9. 資源去哪取(不要自己重造 Arcrun 已有的) | 你想知道 | 跑這個 | |---|---| | 有哪些零件可用 | `acr parts` | | 某零件的設定範本 | `acr parts scaffold ` | +| 有哪些 recipe | `acr recipe list`/`acr recipe search <關鍵字>` | | 支援哪些服務的認證 | `acr auth-recipe list` | -| 某服務認證要哪些 credential + 範例 | `acr auth-recipe scaffold ` | -| 已上傳的 recipe | `acr recipe list` | +| 某服務認證要哪些 credential + 範例 | `acr auth-recipe scaffold ` | +| **一次掃全部**(零件/recipe/auth-recipe/workflow) | `acr search <關鍵字>` | +| 已部署的 workflow | `acr list` | +| 某次執行為什麼失敗 | `acr logs ` | | 工作流語法、指令 | `acr --help` | -**先查再動手**——arcrun 多半已經有你要的零件 / recipe / 認證,不要自刻。 +**先查再動手**——Arcrun 多半已經有你要的零件/recipe/認證,不要自刻。 -## 3. arcrun 是你(AI)用的工具,不是工具回頭呼叫 AI +## 10. 做出來以後:驗證 → 部署 -需要智慧判斷 / 自然語言轉換時,**你自己做**,再呼叫工作流執行確定性的下一步。 -**不要在工作流中間放零件回頭呼叫 LLM**。arcrun 的大腦就是操盤的你。 +```bash +acr validate .yaml # 先驗,別直接部署 +acr push .yaml # 部署(暴露動作,見 §12) +acr run # 觸發一次,看實際結果 +acr logs # 看執行紀錄/失敗原因 +``` -## 4. arcrun 不替你做授權判斷 +需要 credential(API key/token)時:`acr auth-recipe scaffold ` 看要哪些, +明確告訴使用者去哪取得、怎麼 `acr creds push`。 +🔑 **金鑰只拿名字**:workflow/recipe 裡只寫 `{{credential.<名字>}}`, +**真身絕不寫進定義檔**(執行前才由系統回填)。 -API 打不打得通由發 key 的服務決定。401/403 是對方服務在行使授權,**不是 arcrun 的 bug、不是你做錯**。 -不要在 arcrun 裡建「允許/禁止某 endpoint」的二次授權清單。 +## 11. Arcrun 是你(AI)用的工具,不是工具回頭呼叫 AI -## 5. 把東西開放給別人用 = 要使用者明示同意 +需要智慧判斷/自然語言轉換時,**你自己做**,再呼叫工作流執行確定性的下一步。 +**不要在工作流中間放零件回頭呼叫 LLM**——Arcrun 的大腦就是操盤的你。 +(唯一例外:`ask_llm` 這種「內容生成本身就是流程的一步」,見範本 B。) -部署對外 webhook、push recipe 會讓資料/能力**可被外部呼叫**(暴露面): +## 12. 把東西開放給別人用 = 要使用者明示同意 + +`acr push`(部署 workflow)與 `acr recipe push`(投稿 recipe)會讓資料/能力**可被外部呼叫**: - 停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。**不替他決定公開。** -- 非互動環境(你直跑)遇到 → 停,要人類確認,絕不自己塞 confirm 假裝同意。 -- arcrun 可提供保護(要求呼叫者帶 key / 限流)——提醒使用者。 +- 非互動環境(你直跑)遇到 → 停,把完整指令印給使用者自己貼上跑,絕不自己塞 confirm 假裝同意。 +- Arcrun 可提供保護(要求呼叫者帶 key/限流)——提醒使用者。 -## 6. 誠實(最重要) +## 13. Arcrun 不替你做授權判斷 + +API 打不打得通由發 key 的服務決定。401/403 是對方服務在行使授權,**不是 Arcrun 的 bug、不是你做錯**。 +不要在 Arcrun 裡建「允許/禁止某 endpoint」的二次授權清單。 + +## 14. 誠實(最重要) - **不假綠**:沒打通就誠實說。缺 credential 打不到 2xx → 標「未驗收:缺 X」,不 mock 充綠燈。 -- **不假裝防偽 / 不代替人類確認**有風險的動作(暴露資料)。 -- **完成 = 客觀證據**(HTTP 2xx + trace),不是口頭「做好了」。 +- **不假裝防偽/不代替人類確認**有風險的動作(暴露資料)。 +- **完成 = 客觀證據**(HTTP 2xx + trace),不是口頭「做好了」。 --- -## 怎麼用這份 mindset +## 動手前的自檢清單 -每次準備動手,先過一遍: -1. 這能用工作流 / recipe 做嗎?(多半能 → 別自己寫 Python、別造零件) -2. 我查過 `acr parts` / `acr auth-recipe` 了嗎?(arcrun 可能已有) -3. 我是不是讓工作流回頭呼叫 AI?(是 → 改成我自己做) -4. 這動作會把資料開放給別人嗎?(會 → 要使用者明示同意) -5. 我有沒有假裝(假綠 / 假防偽 / 代替人類確認)?(有 → 停,誠實標明) +1. 我把意圖寫成 `>>` 串了嗎?(還是直接跳去寫 YAML/寫程式) +2. 我查過 `acr search` / `acr parts` / `acr recipe list` 了嗎? +3. 查詢回 `not_found` 時,我走的是 recipe/零件 PR 兩條路,**還是偷偷改寫成 `code`**?(後者=腹語術) +4. 我是不是讓工作流回頭呼叫 AI 做判斷?(是 → 改成我自己做) +5. 這動作會把資料開放給別人嗎?(會 → 要使用者明示同意) +6. 我有沒有假裝(假綠/假防偽/代替人類確認)?(有 → 停,誠實標明) diff --git a/cli/harness/skills/arcrun-mindset/SKILL.md.head b/cli/harness/skills/arcrun-mindset/SKILL.md.head new file mode 100644 index 0000000..c24f20e --- /dev/null +++ b/cli/harness/skills/arcrun-mindset/SKILL.md.head @@ -0,0 +1,41 @@ +--- +name: arcrun-mindset +description: >- + 在 Arcrun 上做任何事時使用(用戶說「幫我用 Arcrun 做 X」「用 arcrun 寫一個工作流」「把 X 自動化」)。 + Arcrun 是跑在 Cloudflare 上的工作流引擎——你用 `>>` 寫「意圖」,系統告訴你有哪些現成零件與 recipe, + 你只填 payload,不必自己寫程式。**不要上網搜 Arcrun 文件**(網路上沒有),也不要自己猜 YAML 格式: + 先讀本 skill,再用 `acr` 指令(或 MCP 工具)查現成零件。 + 涵蓋:意圖工作流語法、四份實跑過的範本、零件 vs recipe 的分別、缺件的兩條路、已知的坑。 +--- + +# Arcrun:怎麼寫意圖工作流 + +> **你已經配備 Arcrun**(此專案裝了 `acr` CLI,可能另有 `arcrun_*` MCP 工具)。 +> **別上網找文件**——網路上沒有 Arcrun 的文件,找到的都是錯的。答案都在本 skill 與 `acr` 指令裡。 + +## 先做這三件(照順序) + +1. `acr whoami` — 確認連到哪個帳號(**勿自行 curl 猜帳號 URL**) +2. 讀本 skill 下面的語法與範本 → 寫出 `>>` 意圖 +3. `acr parts`/`acr recipe list`(或 `acr search <關鍵字>` 一次掃全部)— 確認零件與 recipe 真的存在 + +**卡住時**:`acr search <關鍵字>` 跨類搜尋;有 MCP 就 `arcrun_get_skill('INDEX')` 拿全館導航。 + +--- + +## 0. 一句話世界觀 + +**Arcrun 裡幾乎所有東西都是工作流(workflow)。** 工作流 = 一張紙,寫「用哪些零件、什麼順序、什麼條件」。 +你大部分時間在**寫紙、改紙**,不是在造新零件、也不是自己寫腳本。 + +**Arcrun 只有三種東西,先分清楚就不會做歪:** + +| 東西 | 是什麼 | 你能做的 | +|---|---|---| +| **工作流(workflow)** | 把零件/recipe 串起來的純文字流程 | **預設就寫這個**,自由寫 | +| **recipe** | 打「一個固定外部 API」的設定(endpoint/header/body 模板) | 自由寫、**而且該投稿**(缺就自己補) | +| **零件(component)** | WASM 程式(流程控制/資料處理/`http_request`/auth),固定一小套 | **你不自製**,走 PR 由維護者管 | + +> **一句話判準**:打一個固定外部 endpoint → 寫 **recipe**;流程控制/資料處理/通用 HTTP → 用既有**零件**;其他 → 寫**工作流**串起來。 + +--- diff --git a/cli/harness/skills/arcrun-mindset/SKILL.md.tail b/cli/harness/skills/arcrun-mindset/SKILL.md.tail new file mode 100644 index 0000000..c25343b --- /dev/null +++ b/cli/harness/skills/arcrun-mindset/SKILL.md.tail @@ -0,0 +1,67 @@ + +--- + +## 9. 資源去哪取(不要自己重造 Arcrun 已有的) + +| 你想知道 | 跑這個 | +|---|---| +| 有哪些零件可用 | `acr parts` | +| 某零件的設定範本 | `acr parts scaffold ` | +| 有哪些 recipe | `acr recipe list`/`acr recipe search <關鍵字>` | +| 支援哪些服務的認證 | `acr auth-recipe list` | +| 某服務認證要哪些 credential + 範例 | `acr auth-recipe scaffold ` | +| **一次掃全部**(零件/recipe/auth-recipe/workflow) | `acr search <關鍵字>` | +| 已部署的 workflow | `acr list` | +| 某次執行為什麼失敗 | `acr logs ` | +| 工作流語法、指令 | `acr --help` | + +**先查再動手**——Arcrun 多半已經有你要的零件/recipe/認證,不要自刻。 + +## 10. 做出來以後:驗證 → 部署 + +```bash +acr validate .yaml # 先驗,別直接部署 +acr push .yaml # 部署(暴露動作,見 §12) +acr run # 觸發一次,看實際結果 +acr logs # 看執行紀錄/失敗原因 +``` + +需要 credential(API key/token)時:`acr auth-recipe scaffold ` 看要哪些, +明確告訴使用者去哪取得、怎麼 `acr creds push`。 +🔑 **金鑰只拿名字**:workflow/recipe 裡只寫 `{{credential.<名字>}}`, +**真身絕不寫進定義檔**(執行前才由系統回填)。 + +## 11. Arcrun 是你(AI)用的工具,不是工具回頭呼叫 AI + +需要智慧判斷/自然語言轉換時,**你自己做**,再呼叫工作流執行確定性的下一步。 +**不要在工作流中間放零件回頭呼叫 LLM**——Arcrun 的大腦就是操盤的你。 +(唯一例外:`ask_llm` 這種「內容生成本身就是流程的一步」,見範本 B。) + +## 12. 把東西開放給別人用 = 要使用者明示同意 + +`acr push`(部署 workflow)與 `acr recipe push`(投稿 recipe)會讓資料/能力**可被外部呼叫**: +- 停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。**不替他決定公開。** +- 非互動環境(你直跑)遇到 → 停,把完整指令印給使用者自己貼上跑,絕不自己塞 confirm 假裝同意。 +- Arcrun 可提供保護(要求呼叫者帶 key/限流)——提醒使用者。 + +## 13. Arcrun 不替你做授權判斷 + +API 打不打得通由發 key 的服務決定。401/403 是對方服務在行使授權,**不是 Arcrun 的 bug、不是你做錯**。 +不要在 Arcrun 裡建「允許/禁止某 endpoint」的二次授權清單。 + +## 14. 誠實(最重要) + +- **不假綠**:沒打通就誠實說。缺 credential 打不到 2xx → 標「未驗收:缺 X」,不 mock 充綠燈。 +- **不假裝防偽/不代替人類確認**有風險的動作(暴露資料)。 +- **完成 = 客觀證據**(HTTP 2xx + trace),不是口頭「做好了」。 + +--- + +## 動手前的自檢清單 + +1. 我把意圖寫成 `>>` 串了嗎?(還是直接跳去寫 YAML/寫程式) +2. 我查過 `acr search` / `acr parts` / `acr recipe list` 了嗎? +3. 查詢回 `not_found` 時,我走的是 recipe/零件 PR 兩條路,**還是偷偷改寫成 `code`**?(後者=腹語術) +4. 我是不是讓工作流回頭呼叫 AI 做判斷?(是 → 改成我自己做) +5. 這動作會把資料開放給別人嗎?(會 → 要使用者明示同意) +6. 我有沒有假裝(假綠/假防偽/代替人類確認)?(有 → 停,誠實標明) diff --git a/cli/package-lock.json b/cli/package-lock.json index 7d44afd..cb68f34 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "arcrun", - "version": "1.3.13", + "version": "1.3.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "arcrun", - "version": "1.3.13", + "version": "1.3.14", "license": "MIT", "dependencies": { "chalk": "^5.3.0", diff --git a/cli/package.json b/cli/package.json index 365cd62..ea08223 100644 --- a/cli/package.json +++ b/cli/package.json @@ -8,7 +8,9 @@ "main": "./dist/index.js", "type": "module", "scripts": { - "build": "tsc", + "build": "npm run build:harness && npm run check:harness && tsc", + "build:harness": "node scripts/build-harness-skill.mjs", + "check:harness": "node scripts/check-harness-generation.mjs", "dev": "tsc --watch", "test": "node --test \"tests/**/*.test.ts\"", "prepublishOnly": "npm run build && chmod +x dist/index.js" diff --git a/cli/scripts/build-harness-skill.mjs b/cli/scripts/build-harness-skill.mjs new file mode 100644 index 0000000..afaa7c0 --- /dev/null +++ b/cli/scripts/build-harness-skill.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node +/** + * build-harness-skill.mjs — 由 registry/skills/ 組出 harness 的 arcrun-mindset SKILL.md + * + * 【為什麼是「建置期複製」而不是人工維護兩份】 + * `registry/skills/write_intent_workflow.md` 是意圖語法的**單一真相源**——它同時是 + * MCP `arcrun_get_skill()` 回給雲端 AI 的內容。harness 的 skill 若人工再抄一份, + * 兩份必然漂移(2026-07-31 實錄:harness 那份停在上一代,grep「意圖」「>>」= 0 命中, + * 只講世界觀,害新裝的用戶 AI 學不到 `>>`)。 + * + * 作法:harness skill = 三段拼接 + * SKILL.md.head ← harness 專屬(frontmatter/CLI 入口/三種東西的分型) + * registry 的 write_intent_workflow.md 正文 ← 單一真相源,只此一份被維護 + * SKILL.md.tail ← harness 專屬(acr 指令表/暴露同意/誠實鐵律) + * + * 為什麼不用 symlink / npm 打包直接引用:npm `files` 只收 `harness/`, + * registry/ 不進套件;symlink 在 npm pack 與 Windows 上不可靠。建置期複製最單純。 + * + * 產物 `SKILL.md` **有 commit 進 repo**(npm 套件裝的是它,不會跑 build), + * 由 check-harness-generation.mjs 驗證它與 registry 沒有漂移。 + */ +import { readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const here = dirname(fileURLToPath(import.meta.url)); // cli/scripts +const repoRoot = join(here, '..', '..'); // repo 根 +const skillDir = join(here, '..', 'harness', 'skills', 'arcrun-mindset'); +const registrySkill = join(repoRoot, 'registry', 'skills', 'write_intent_workflow.md'); + +const head = readFileSync(join(skillDir, 'SKILL.md.head'), 'utf8').trimEnd(); +const tail = readFileSync(join(skillDir, 'SKILL.md.tail'), 'utf8').trimEnd(); +const body = readFileSync(registrySkill, 'utf8'); + +// 取 registry skill 的正文:去掉它自己的 H1 標題與「何時用這個 skill」那段 +// (harness 的 head 已用 CLI 語境寫過入口),從第一個 `## 1.` 章節起收。 +const idx = body.indexOf('## 1. 意圖工作流的語法'); +if (idx < 0) { + console.error('❌ registry/skills/write_intent_workflow.md 找不到「## 1. 意圖工作流的語法」章節;'); + console.error(' registry skill 結構變了 → 請同步更新 cli/scripts/build-harness-skill.mjs 的取段規則。'); + process.exit(1); +} +const middle = body + .slice(idx) + // registry 版把 MCP 工具當預設介面;harness 裝在有 acr CLI 的專案 → 補上 CLI 等價指令 + .replace(/`arcrun_get_workflow\(\)`/g, '`acr logs `(有 MCP 則 `arcrun_get_workflow()`)') + .replace(/`arcrun_list_components` \/ `arcrun_search_components`/g, '`acr parts` / `acr search`') + .replace(/下一步該讀哪支 skill:`arcrun_list_skills\(\)`/g, '下一步該讀哪支 skill(需 MCP):`arcrun_list_skills()`') + .trimEnd(); + +const out = [ + head, + '', + '', + '', + middle, + '', + tail, + '', +].join('\n'); + +writeFileSync(join(skillDir, 'SKILL.md'), out, 'utf8'); +console.log(`✓ harness skill 已由 registry 重建:${out.length} bytes`); diff --git a/cli/scripts/check-harness-generation.mjs b/cli/scripts/check-harness-generation.mjs new file mode 100644 index 0000000..334d068 --- /dev/null +++ b/cli/scripts/check-harness-generation.mjs @@ -0,0 +1,130 @@ +#!/usr/bin/env node +/** + * check-harness-generation.mjs — 世代閘:harness 內容脫節就讓 build/publish 失敗 + * + * 【為什麼要這道閘】 + * 2026-07-31 實錄:`acr install-harness` 的管道一直是好的,但它鋪出去的**內容停在上一代**—— + * harness skill grep「意圖」「>>」= 0 命中,只講世界觀。管道綠燈、交付物過時, + * 沒有任何機械檢查會抱怨 ⇒ 世代脫節可以無聲存在好幾個月。 + * + * 這道閘檢查四件交付物的「現世代指紋」。缺指紋 = exit 1,擋掉 build 與 npm publish。 + * 指紋要挑「上一代絕不會有、現世代一定有」的字串,不是隨便的關鍵字。 + */ +import { readFileSync, existsSync, statSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { execFileSync } from 'node:child_process'; + +const here = dirname(fileURLToPath(import.meta.url)); +const harness = join(here, '..', 'harness'); +const repoRoot = join(here, '..', '..'); + +/** @type {{file: string, must: [string, string][], mustNot?: [string,string][]}[]} */ +const CHECKS = [ + { + file: 'skills/arcrun-mindset/SKILL.md', + must: [ + ['>>', '意圖語法(`A >> 邊 >> B`)——步驟 1 的核心教材'], + ['ON_SUCCESS', '合法邊之一'], + ['對每個', 'FOREACH 邊(十題裡有四題要用)'], + ['input', '第一個節點固定是 input'], + ['not_found', '現世代查詢狀態(舊版寫 missing/假 found)'], + ['腹語術', '缺件不准改寫成 code 的紅線'], + ['recipe', '零件 vs recipe 分型'], + ], + mustNot: [ + // ON_TRUE 只准出現在「教它不存在」的脈絡(否定詞/實測證據)。 + // 若哪天它出現在範本裡(正面示範),就是教材寫錯,該擋。 + ['ON_TRUE', '引擎沒有條件邊,教材不該把它當可用的邊', /不要寫|不存在|沒有條件|❌|非法|grep|= 0/], + ], + }, + { + file: 'CLAUDE.block.md', + must: [ + ['>>', '意圖語法要在 CLAUDE.md 就先亮相'], + ['not_found', '缺件兩條路的觸發點'], + ], + }, + { + file: 'commands/arcrun.md', + must: [ + ['>>', '/arcrun 的第一步就該是寫意圖'], + ['acr search', '現世代的跨類搜尋指令'], + ], + }, + { + file: 'hooks/arcrun-guard.sh', + must: [ + ['arcrun-mindset', 'hook 被擋下時要把 AI 導向 skill,而不是叫它去翻 repo 文件'], + ['>>', 'hook 的正路提示要提到意圖語法'], + ], + }, +]; + +let fail = 0; +const say = (s) => console.log(s); + +say('\n 世代閘:檢查 harness 交付物是否為現世代內容\n'); + +for (const c of CHECKS) { + const p = join(harness, c.file); + if (!existsSync(p)) { + say(` ❌ ${c.file} — 檔案不存在`); + fail++; + continue; + } + const text = readFileSync(p, 'utf8'); + const missing = c.must.filter(([needle]) => !text.includes(needle)); + const badNot = (c.mustNot ?? []).filter(([needle, , allowIfNear]) => { + if (!text.includes(needle)) return false; + if (!allowIfNear) return true; + // 允許「在教『不要用』的脈絡裡」出現:看該字串所在行是否有豁免詞 + return !text + .split('\n') + .filter((l) => l.includes(needle)) + .every((l) => allowIfNear.test(l)); + }); + + if (missing.length === 0 && badNot.length === 0) { + say(` ✓ ${c.file}`); + } else { + fail++; + say(` ❌ ${c.file}`); + for (const [needle, why] of missing) say(` 缺指紋「${needle}」— ${why}`); + for (const [needle, why] of badNot) say(` 不該出現「${needle}」— ${why}`); + } +} + +// harness skill 必須是由 registry 重建的最新版(防「改了 registry 忘了重跑 build」) +const skillPath = join(harness, 'skills', 'arcrun-mindset', 'SKILL.md'); +const registrySkill = join(repoRoot, 'registry', 'skills', 'write_intent_workflow.md'); +if (existsSync(skillPath) && existsSync(registrySkill)) { + try { + execFileSync(process.execPath, [join(here, 'build-harness-skill.mjs')], { stdio: 'pipe' }); + const rebuilt = readFileSync(skillPath, 'utf8'); + const before = statSync(skillPath); // 重建後內容即為期望值 + void before; + // 重建是冪等的:若重建後與 git 中的版本不同,git diff 會在 CI 顯示; + // 這裡直接比對「重建結果是否含 registry 當前的關鍵段落」 + const reg = readFileSync(registrySkill, 'utf8'); + const marker = reg.includes('## 7. 常犯的錯') ? '## 7. 常犯的錯' : null; + if (marker && !rebuilt.includes(marker)) { + say(` ❌ harness skill 與 registry 漂移:registry 有「${marker}」但重建產物沒有`); + fail++; + } else { + say(' ✓ harness skill 與 registry/skills/write_intent_workflow.md 同步'); + } + } catch (e) { + say(` ❌ 無法由 registry 重建 harness skill:${e.message}`); + fail++; + } +} + +say(''); +if (fail) { + say(` 🔴 世代閘擋下(${fail} 項)。harness 交付的內容落後於現世代。`); + say(' 修法:改 registry/skills/write_intent_workflow.md(單一真相源)或對應的'); + say(' cli/harness/ 檔案,然後跑 `npm run build:harness` 重建,再跑本檢查。\n'); + process.exit(1); +} +say(' ✅ 世代閘通過:四件交付物都帶現世代指紋\n'); diff --git a/cli/src/commands/install-harness.ts b/cli/src/commands/install-harness.ts index e63fa45..0b69eee 100644 --- a/cli/src/commands/install-harness.ts +++ b/cli/src/commands/install-harness.ts @@ -110,11 +110,18 @@ function mergeSettings(cwd: string, src: string): void { writeFileSync(path, JSON.stringify(settings, null, 2) + '\n', 'utf8'); } -/** 遞迴複製目錄樹(覆蓋同名檔)。 */ +/** 建置期產物的來源片段(`SKILL.md.head` / `.tail`),只給 build-harness-skill.mjs 用, + * 不該被鋪進使用者專案(使用者拿到的是拼接好的 `SKILL.md`)。 */ +function isBuildSource(name: string): boolean { + return name.endsWith('.head') || name.endsWith('.tail'); +} + +/** 遞迴複製目錄樹(覆蓋同名檔;跳過建置期來源片段)。 */ function copyTree(srcDir: string, dstDir: string): void { if (!existsSync(srcDir)) return; mkdirSync(dstDir, { recursive: true }); for (const name of readdirSync(srcDir, { withFileTypes: true })) { + if (isBuildSource(name.name)) continue; const s = join(srcDir, name.name); const d = join(dstDir, name.name); if (name.isDirectory()) copyTree(s, d); diff --git a/system-dev/docs/3-specs/workflow-discovery/tasks.md b/system-dev/docs/3-specs/workflow-discovery/tasks.md index 3dc6928..b4b5436 100644 --- a/system-dev/docs/3-specs/workflow-discovery/tasks.md +++ b/system-dev/docs/3-specs/workflow-discovery/tasks.md @@ -112,6 +112,33 @@ 前兩者與 target 走同一條路;recipe_search 搜公庫 vs target=recipe 搜私庫=語料不同 是設計(installed vs marketplace),回應互相指路,非行為漂移 +- [x] 3.11 `acr install-harness` 交付內容升級到現世代(CP arcrun-usable **步驟 1** 最後一筆; + 頂層交棒)— **管道本來就好的,過時的是內容**:`cli/harness/skills/arcrun-mindset/SKILL.md` + (4066B)grep「意圖」「>>」=**0 命中**,只講世界觀/別寫 Python, + 新裝封測者拿不到步驟 1 的核心教材(`>>` 意圖語法)。 + - **單一真相源**:harness skill 改為**建置期由 `registry/skills/write_intent_workflow.md` + 複製**(`cli/scripts/build-harness-skill.mjs`,head+registry 正文+tail 三段拼接)。 + 選建置期複製而非 symlink/npm 引用:npm `files` 只收 `harness/`,registry 不進套件; + symlink 在 npm pack 與 Windows 不可靠。產物 commit 進 repo(npm 裝的是產物,不跑 build)。 + head/tail 為 harness 專屬(CLI 語境入口/`acr` 指令表/暴露同意/誠實鐵律), + `install-harness` 的 `copyTree` 跳過 `.head`/`.tail` 不鋪給用戶。 + - **其餘三件同步升級**:`CLAUDE.block.md`(補 `>>` 意圖語法+`not_found` 兩條路+ + 零件 vs recipe 分型+腹語術紅線+金鑰只拿名字);`commands/arcrun.md`(步驟改成 + 「先寫意圖→丟去查→再寫 YAML」,補 `acr search`/`acr validate`); + `hooks/arcrun-guard.sh`(**正路提示改為指向 arcrun-mindset Skill +意圖語法**, + 呼應「hook 沒提 skill 反而把 AI 導向 repo 文件」的教訓;新增 code 節點腹語術提醒, + settings.fragment 補 `Write|Edit|MultiEdit` matcher)。 + - **世代閘**(防再度脫節):`cli/scripts/check-harness-generation.mjs` 檢查四件交付物的 + 現世代指紋(`>>`/`ON_SUCCESS`/`對每個`/`not_found`/腹語術/`arcrun-mindset`), + 缺指紋 exit 1;掛進 `npm run build`(故 `prepublishOnly` 也擋)。 + 反向驗證:把 skill/CLAUDE.block 換回上一代 → 兩者都被擋下並逐條點名缺哪個指紋。 + - **驗收**(考生 haiku/受測物=環境):乾淨臨時目錄跑 `acr install-harness` + → 四件鋪好、重跑冪等(全檔 md5 不變、CLAUDE.md 66 行不變、hooks 條目 2 不變、 + arcrun 區塊仍 1 個);haiku 只讀該目錄的 CLAUDE.md+SKILL.md(明令禁讀 ~/.claude、 + 禁上網;兩份教材 md5 與大小均不同可證非考本機那支)答十題 + → `grade-step1.sh` **10 / 10 通過**(判分器同時反向驗證仍會抓 ON_TRUE/ON_FAILURE/ + 第一節點非 input) + --- ## 跨任務鐵律提醒