chore: D22 落地——docs/SDD/wiki/CLAUDE.md 進 repo(Gitea private 預設全 push)
頂層 D22 決策(leo 2026-07-03 拍板):推什麼由開發環境歸屬決定, Gitea private=除機敏值/build 產物/.github 外全 push。 解 T1.5 卡點:雲端工人 clone 拿得到 credential-store-migration.md,可就地改寫 SDD。 機敏掃描兩輪通過(新增 189 檔約 2.1MB,node_modules/dist/wasm 照舊排除)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
---
|
||||
description: 處理本 repo 的 GitHub issue(讀/回/結案),跨 repo 發要先問人
|
||||
---
|
||||
|
||||
# /issue-handle — GitHub issue 處理指引
|
||||
|
||||
你(CC)可以、也該主動用內建的 `gh` CLI 讀寫**自己 repo** 的 GitHub issue。
|
||||
很多人不知道這件事——`gh` 已內建認證,零開發、零外部依賴。issue 同源於 repo,
|
||||
比 Notion / Sheets 更適合做交辦與待辦,不必引入外部 SaaS。
|
||||
|
||||
這份指引分四層,界線要守住。
|
||||
|
||||
---
|
||||
|
||||
## 1. 讀 / 回 / 結案(普世基本功 — 直接做,不用問)
|
||||
|
||||
對**自己這個 repo**,主動處理 open issue:
|
||||
|
||||
```bash
|
||||
gh issue list --state open # 看有哪些待辦
|
||||
gh issue view <n> # 讀完整內容
|
||||
# …實作…
|
||||
gh issue comment <n> --body "[<本 repo> CC] 做了什麼、怎麼決定的、改了哪些檔"
|
||||
gh issue close <n> # 確認解決後結案
|
||||
```
|
||||
|
||||
回覆要有料:說清楚**做了什麼、為什麼這樣決定、動了哪些檔**,而不是只回「done」。
|
||||
issue 作者(可能是另一個 repo 的 CC,或人類)要靠你的回覆判斷對不對。
|
||||
跨 repo 的 issue/comment 開頭一律署名 `[<本 repo> CC]`(見第 3 節鐵律)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 發 issue 給「別的 repo」(要先問人 — 不可擅自)
|
||||
|
||||
當你發現**別的 repo** 有值得修正的地方時:
|
||||
|
||||
> ❌ 不要擅自 `gh issue create -R other/repo …`
|
||||
> ✅ 先問人類:「我發現 X repo 有 Y 問題,要我幫你去那邊發 issue 嗎?」得到同意才發。
|
||||
|
||||
理由:通用 template 不知道使用者對那個 repo 有沒有權限、想不想發。留一道人類確認最安全。
|
||||
(對**自己 repo** 開 issue 記待辦則可直接做——那是自己的 repo。)
|
||||
|
||||
---
|
||||
|
||||
## 3. 跨 repo 署名(鐵律 — 絕不可漏)
|
||||
|
||||
所有 repo(mira / graph-plugin / ingest-plugin / Arcrun / template…)共用**同一個 GitHub 帳號**發 issue/comment,
|
||||
所以 issue/comment 的 author **全顯示同一個帳號、看不出是哪個 repo 的 CC 發的**。
|
||||
|
||||
> **跨 repo 的 issue/comment 一律在開頭署名 `[<本 repo> CC]`**,靠內容署名溯源。
|
||||
|
||||
- 收件方 CC 回報:`[graph-plugin CC]` / `[mira CC]` / `[ingest CC]` / `[arcrun CC]`…
|
||||
- 總管下令/追問:`[InkStoneCo 總管]`
|
||||
- 署名放 comment **第一行或標題式開頭**(既有的「## 回報(graph CC)」即合格)。
|
||||
|
||||
為什麼只能這樣:GitHub issue/comment 的 author = 發送帳號,**沒有 per-repo 身份這設定**;
|
||||
`git config user.name` 只影響 commit 作者、不影響 issue/comment author;
|
||||
給每個 repo 開獨立帳號 = 多帳號自動化 = 踩下方第 4 節 flag 鐵律,**不可**。
|
||||
身份只能在**內容層自報**。本 repo 名稱 → 看 `git remote -v` 或 repo 根目錄名。
|
||||
|
||||
---
|
||||
|
||||
## 4. flag 安全界線(最重要 — 絕不可越)
|
||||
|
||||
**「有事才讀」,禁止自動輪詢。**
|
||||
|
||||
- ✅ 想看 issue → 當下主動 `gh issue list`。
|
||||
- 🚫 **禁止**掛 GitHub Actions / cron / webhook 去**自動輪詢** issue。
|
||||
|
||||
為什麼這條是硬底線:自動輪詢 + 事件 fan-out 正是會觸發 GitHub 異常偵測、
|
||||
害你的 token 被 rate limit 砍掉的流量模式。template 給很多人用,這條界線必須守住,
|
||||
保護使用者不踩雷。需要「定期檢查」就由人類主動跑這個指令,不要自動化。
|
||||
|
||||
---
|
||||
|
||||
## (可選)用 label 分來源
|
||||
|
||||
若想用同一套流程同時管「內部交辦」與「外部回報」,可建兩個 label:
|
||||
`internal`(協作/交辦)、`user`(外部使用者回報),靠 label 分流。
|
||||
這對通用 template 不預設——你的 repo 需要再建。
|
||||
@@ -0,0 +1,55 @@
|
||||
# /sdd-check — 確認當前任務有沒有對應 SDD
|
||||
|
||||
動手前執行。確保 CC 有全局觀,不會在沒有設計文件的情況下猛衝。
|
||||
|
||||
---
|
||||
|
||||
## 執行流程
|
||||
|
||||
### 第一步:理解任務
|
||||
|
||||
確認使用者要做什麼:
|
||||
- 涉及哪個子系統?
|
||||
- 是新功能還是修改現有功能?
|
||||
- 影響範圍?
|
||||
|
||||
### 第二步:尋找對應 SDD
|
||||
|
||||
在 `system-dev/docs/3-specs/` 下尋找對應的子系統目錄,確認有沒有:
|
||||
- `design.md`(設計文件)
|
||||
- `tasks.md`(任務清單)
|
||||
|
||||
### 第三步:根據結果回應
|
||||
|
||||
**情況 A:找到對應 SDD**
|
||||
```
|
||||
✅ 找到 SDD:system-dev/docs/3-specs/[子系統]/
|
||||
📋 design.md:[確認]
|
||||
📋 tasks.md:[確認,列出相關 task]
|
||||
🎯 對應 task:[編號和描述]
|
||||
繼續嗎?
|
||||
```
|
||||
|
||||
**情況 B:找不到 SDD,任務明確**
|
||||
```
|
||||
⚠️ 找不到對應 SDD
|
||||
任務:[描述]
|
||||
建議在 system-dev/docs/3-specs/[建議子系統名]/ 建立 SDD
|
||||
|
||||
要我幫你起草 design.md 嗎?(需要你確認後才動手)
|
||||
```
|
||||
|
||||
**情況 C:找不到 SDD,任務模糊**
|
||||
```
|
||||
⚠️ 找不到對應 SDD,而且任務範圍不夠清楚
|
||||
請先回答:
|
||||
1. 這個功能屬於哪個子系統?
|
||||
2. 完成的標準是什麼?
|
||||
3. 有沒有不能動的邊界?
|
||||
```
|
||||
|
||||
### 注意
|
||||
|
||||
- 找不到 SDD **不等於可以直接動手**
|
||||
- 小修改(修 bug、改文字)可以豁免,但要明確說「這是小修改,範圍是 X」
|
||||
- 新功能、架構變動、跨模組的修改 → 一定要有 SDD
|
||||
@@ -0,0 +1,69 @@
|
||||
# /wiki-capture — 把對話結論存進 wiki
|
||||
|
||||
把這次對話中產生的決策、誤解釐清、或重要結論存入 wiki。
|
||||
解決「討論過了但知識消失」的問題。
|
||||
|
||||
---
|
||||
|
||||
## 執行流程
|
||||
|
||||
### 第零步:機敏檢查(寫入前一律先過)
|
||||
|
||||
把任何內容寫進 wiki 前,先確認**不含**密碼 / API 金鑰 / 私鑰 / 連線字串帳密 / 個資(身分證、信用卡)。
|
||||
- 命中 → 不要記「值」,改記「位置」(例:「DB 密碼放 `.env`,不入 wiki」)
|
||||
- 來源整檔機敏 → 提醒使用者加進 `system-dev/wiki/.wikiignore`
|
||||
- 真要保留示範格式 → 該行尾加 `wiki-secret-ok` 標記
|
||||
> 這是協議層自律。最後一道 `wiki-secret-scan.sh` hook 會在寫入 `system-dev/wiki/` 時機械攔截,但別依賴它兜底——當場就不要把機敏值帶進來。
|
||||
|
||||
### 第一步:辨識對話中的可記錄內容
|
||||
|
||||
掃描當前對話,找出:
|
||||
|
||||
| 類型 | 判斷標準 | 存到哪 |
|
||||
|------|---------|-------|
|
||||
| 架構決策 | 「為什麼選A不選B」「我們決定用X」 | `decisions-summary.md` + `system-dev/docs/2-architecture/decisions/` |
|
||||
| CC 的誤解被糾正 | CC 說了某件事,使用者說「不是,是...」 | `mistakes.md` |
|
||||
| 重要狀態更新 | 完成了某件事、阻擋了某件事 | `status.md` |
|
||||
| 技術發現 | 踩到坑、找到解法、重要行為確認 | `mistakes.md` 或對應 SDD |
|
||||
|
||||
### 第二步:列出清單給使用者確認
|
||||
|
||||
格式:
|
||||
```
|
||||
這次對話我整理了以下內容要存入 wiki:
|
||||
|
||||
1. [MISTAKE] CC 誤解了 X,正確是 Y
|
||||
2. [DECISION] 決定用 A 不用 B,原因是 C
|
||||
3. [STATUS] 完成了 task 2.3,下一步是 2.4
|
||||
|
||||
確認後存入,有需要修改的嗎?
|
||||
```
|
||||
|
||||
**停下來等確認。**
|
||||
|
||||
### 第三步:寫入
|
||||
|
||||
確認後,依照格式寫入對應檔案:
|
||||
|
||||
**mistakes.md 格式:**
|
||||
```
|
||||
⚠️ MISTAKE: [錯誤描述]
|
||||
症狀: [CC 的表現]
|
||||
正確做法: [應該怎麼做]
|
||||
原因: [背景]
|
||||
日期: [YYYY-MM-DD]
|
||||
```
|
||||
|
||||
**decisions-summary.md 格式:**
|
||||
```
|
||||
## [主題] — [YYYY-MM-DD]
|
||||
**結論**:[一句話]
|
||||
**原因**:[簡短說明]
|
||||
**詳細**:system-dev/docs/2-architecture/decisions/[檔名]
|
||||
```
|
||||
|
||||
重大決策同時在 `system-dev/docs/2-architecture/decisions/` 建立 ADR 檔案。
|
||||
|
||||
### 第四步:確認
|
||||
|
||||
告知存到哪些檔案,共幾條記錄。
|
||||
@@ -0,0 +1,230 @@
|
||||
# /wiki-init — 初始化或接入 LLM Wiki 系統
|
||||
|
||||
初始化這個專案的 LLM Wiki 記憶系統。
|
||||
新專案建立空白結構,已有專案掃描現有文件並**改寫**成 wiki。
|
||||
|
||||
---
|
||||
|
||||
## 核心概念:wiki 是 AI 改寫過的記憶,不是原文索引
|
||||
|
||||
記憶系統的目的,是讓 AI **之後讀得快**。但人類寫的原始文件——不管是 vault 的隨手記、開發專案的會議記錄、規格草稿、散落的 `.md`——天生是亂的:重複、流水帳、半成品、口語。
|
||||
|
||||
如果 wiki 只是一份 `[[原文檔名]]` 指回原文的**索引**,那每次未來要用都得重新解析那團亂,等於沒省到。**wiki 的價值在於「改寫一次,之後每次讀都便宜」**。
|
||||
|
||||
所以原則對**所有專案**一致(不分 vault 或一般開發):
|
||||
|
||||
> 人類寫的原文是 **SSoT**(真理來源,永遠唯讀)。
|
||||
> 但實際要長期保存、被 AI 反覆讀的是 **AI 改寫整理過的 wiki**。
|
||||
> **AI 是總編輯**——把原文改寫成自包含、概念原子化、互相連結、適於 AI 讀的知識條目。
|
||||
|
||||
唯一例外:原文是**不可改動的正式文件**(簽署過的規格、法規、合約),必須逐字讀原文——這種才在 wiki 裡用指針指回去,並註明「逐字依原文」。除此之外,一律改寫。
|
||||
|
||||
**raw source 永遠唯讀**:所有產出只往 `system-dev/wiki/` 寫,絕不改動、搬移、重新命名原文。
|
||||
|
||||
---
|
||||
|
||||
## 執行流程
|
||||
|
||||
### 第一步:偵測專案狀態
|
||||
|
||||
檢查以下項目,判斷是新專案還是已有專案:
|
||||
- 根目錄有沒有 `system-dev/wiki/`
|
||||
- 根目錄有沒有 `docs/`(或 vault 的 `pages/`、`journals/`、根目錄 `.md`)
|
||||
- 有沒有散落的 `.md` 檔案
|
||||
|
||||
同時**偵測 raw source 路徑**(同 install.sh 邏輯):
|
||||
- 根目錄有 `logseq/` → Logseq vault,raw source = `pages/` + `journals/`
|
||||
- 根目錄有 `.obsidian/` → Obsidian vault,raw source = 根目錄所有 `.md`
|
||||
- 都沒有 → 一般專案,raw source = `docs/` 下所有 `.md`(及散落的 `.md`)
|
||||
|
||||
**新專案**(幾乎空的)→ 直接建立結構,跳到第三步
|
||||
**已有專案**(有文件)→ 執行第二步
|
||||
|
||||
### 第二步:已有專案的掃描(已有專案才執行)
|
||||
|
||||
1. 遞迴找出 raw source 裡所有 `.md` 檔案
|
||||
2. **先套用 `system-dev/wiki/.wikiignore`**:命中 pattern 的檔案整個排除,不讀不編入。
|
||||
- 若 `.wikiignore` 不存在,從範本建立一份(預設排除 `.env`/`*.pem`/`*secret*` 等)
|
||||
- 被排除的檔案在清單裡標「🚫 .wikiignore 排除」,**不可被覆蓋**
|
||||
3. 對其餘檔案標注**改寫計畫**:會萃取成哪些 wiki 條目。一份原文可能拆成多個概念原子條目,多份相關原文也可能合併成一條。
|
||||
4. 列出清單給使用者確認,**停下來等確認**
|
||||
|
||||
> **量大時建議用 Haiku 改寫**:逐份原文「改寫成 wiki 格式」是重複、機械、判斷成本低的工作——正適合 Haiku。原文數量多(如數十、上百份)時,主動建議:
|
||||
> 「共 N 份原文要改寫,這類逐份萃取很適合用 Haiku 並行處理(便宜、夠快)。要我派 Haiku subagent 改寫嗎?」
|
||||
> 得同意後,用 Task / subagent 把每份原文(或每批)丟給 Haiku 改寫,主模型只負責切分概念、定條目邊界、最後審稿與互連。
|
||||
|
||||
> 機敏防護(三層):
|
||||
> - **L1 .wikiignore**:整檔排除(這一步)
|
||||
> - **L2 行內標記**:檔案要編入但某段不要 → 遇到 `<!-- wiki:ignore -->` … `<!-- wiki:end -->` 之間的內容**略過**,只留「(此處機敏,已略過)」
|
||||
> - **L3 hook**:萬一機敏值仍被寫進 wiki,`wiki-secret-scan.sh` 會 exit 2 擋下
|
||||
> 編入任何檔案前,先檢查是否含密碼/金鑰/個資——有就改記「位置」而非「值」。
|
||||
|
||||
### 第三步:建立缺少的結構
|
||||
|
||||
只建立不存在的目錄和檔案,**已有的一律不動**。
|
||||
|
||||
wiki 採**三層 + 標籤橫切**架構(183 卡實證,issue #8):
|
||||
|
||||
```
|
||||
system-dev/wiki/
|
||||
├── INDEX.md ← 索引:多角度視圖的家(標籤角度、決策角度、…)
|
||||
├── TAXONOMY.md ← 標籤字典(cards 的分類元資料,受控擴充)
|
||||
├── status.md ← [push] 時態狀態:當前進度、下一步
|
||||
├── mistakes.md ← [push] 踩過的坑、被糾正的誤解(防不自覺盲區)
|
||||
├── principles.md ← [push] 跨全局的設計原則(行動前必服從)
|
||||
└── cards/ ← [pull] 一切知識內容:原文摘要、AI 筆記、決策、概念…
|
||||
└── <bucket>/ ← 儲存桶(分類由 frontmatter 標籤承載)
|
||||
├── 00-INDEX.md ← 桶子索引(固定名,容器:只連不重寫,H2/H3 分節)
|
||||
└── <概念全名>.md ← 概念原子卡(一概念一檔,自包含)
|
||||
```
|
||||
|
||||
> **[push] / [pull] 是這套 wiki 的核心判準——因為 wiki 主要是給 AI(CC)看的。**
|
||||
> 見下方「核心判準:push vs pull」。`decisions-summary.md` 已**降級為 cards + INDEX 決策視圖**(決策是知識內容=card);既有的 decisions-summary 若存在,保留為相容,不刪。
|
||||
|
||||
關鍵原則:**資料夾只是儲存桶,分類由 frontmatter 標籤承載**。資料夾名不該硬繼承原稿目錄——原稿目錄是「人為了整理草稿」分的,wiki 連分類都該由 AI 重新組織。
|
||||
|
||||
> **桶子索引固定叫 `00-INDEX.md`**(issue #6):`00-` 前綴讓它排序最前、一眼可辨(像 README 之於資料夾),AI 載入任何 `cards/<bucket>/` 一律先讀它,不必猜。檔內 H1 仍寫主題名(如 `# PKM 知識管理`),語意不丟。
|
||||
|
||||
一般專案仍可同時建 `system-dev/docs/` 分類樹(SDD 等):
|
||||
```
|
||||
system-dev/docs/{1-vision,2-architecture/decisions,3-specs,4-guides,5-records/{incidents,test-reports},6-user}
|
||||
```
|
||||
(純 PKM vault 不需要 `system-dev/docs/` 分類樹時,只建 `system-dev/wiki/`。)
|
||||
|
||||
檔案(不存在才建):
|
||||
- `system-dev/wiki/INDEX.md`、`TAXONOMY.md`
|
||||
- `system-dev/wiki/status.md`、`mistakes.md`、`principles.md`(三個 push 檔)
|
||||
- `system-dev/docs/README.md`(一般專案才需要)
|
||||
|
||||
---
|
||||
|
||||
## 核心判準:push vs pull(wiki 是給 AI 看的)
|
||||
|
||||
整理任何內容前,先判斷它該 **push** 還 **pull**——判準是「**CC 做事時會不會被動看見**」:
|
||||
|
||||
- **push**:CC 行動前必須主動出現在 context(session 開始就由 hook 注入)。給「CC 不會主動去查、但不看就出事」的東西。
|
||||
- **pull**:CC 想到要查、或載入相關卡時才看見。給「CC 面對它時自然會查」的知識。
|
||||
|
||||
**為什麼這是核心**:mistakes 防的是 CC「不自覺的盲區」——一個你不知道存在的錯,你不會主動去檢索它。靠 CC 自覺去查自己沒自覺的盲區是自相矛盾的,所以 pull 對盲區失效,**必須 push**。原則同理:沒被推到眼前的準繩,CC 設計時很可能沒想到要服從就做了。
|
||||
|
||||
| 內容 | push/pull | 注入形態(hook)|
|
||||
|------|-----------|----------------|
|
||||
| **status** | push | **全文**——CC 必須知道精確的下一步,摘要會漏 task 編號 |
|
||||
| **principles** | push | **全文(一行一條)**——短而硬的約束,漏一條就違反;≤15 條,超過代表該下放成 card |
|
||||
| **mistakes** | push | **標題清單 + 一行症狀**,全文按需 pull——量可能大,摘要足以觸發「我正撞到某條」的認出 |
|
||||
| **decisions、原文摘要、概念知識、一切其餘** | pull | 寫成 cards;CC 面對時自然會查,INDEX 提供角度入口 |
|
||||
|
||||
**principles 維護規則**:一行一條精煉準繩(如「不污染用戶根目錄」「目標用戶 low-code」「wiki 主要給 AI 看」)。發現新的跨全局原則 → append 一行;超過 ~15 條代表某些該合併或下放成 card。**累積原則只改 principles.md,不必問用戶開新檔。**
|
||||
|
||||
### 第四步:訪談(每次一個問題)
|
||||
|
||||
依序問:
|
||||
1. 這個專案做什麼?(一句話)
|
||||
2. 有哪些絕對不能違反的限制?(技術棧、架構原則等)
|
||||
3. 現在進行到哪個階段?
|
||||
4. 有沒有 CC 曾經犯過的錯要先記下來?
|
||||
|
||||
把答案填進 `CLAUDE.md`(如果存在)或建立新的。
|
||||
|
||||
### 第五步:改寫成 wiki(AI 當總編輯)
|
||||
|
||||
(第二步確認後執行)
|
||||
|
||||
**不搬動原文**。逐份讀 raw source,改寫萃取成 `cards/<bucket>/` 裡的自包含原子卡:
|
||||
|
||||
- **概念原子化**:一張卡講一個概念,不是一篇原文對一張卡。原文太雜就拆,多份相關原文就合。
|
||||
- **自包含**:讀卡就懂,不必回去翻原文。把口語、重複、流水帳改寫成結構化知識,**不寫「詳見原文」**。
|
||||
- **保留來源指針**:每卡標 `**來源**:原文相對路徑`,為可追溯,不是要使用者回去讀。
|
||||
- **frontmatter 標籤分類**(見下方):分類走 frontmatter `tags:`,不靠資料夾、不靠行內 `#tag`。
|
||||
- **互相連結(typed-edge 三元組)**:`## 關聯` 不只列裸 `[[頁面]]`,改寫成帶語義的三元組(見下方)。
|
||||
- **萃 gloss(node 一句說明)**:frontmatter 放 `gloss:` —— 這張卡(= 一個 entity / graph node)的一句話定義,供下游語義 normalize(見下方)。
|
||||
|
||||
卡片格式(每張卡):
|
||||
```markdown
|
||||
---
|
||||
tags: [知識管理, AI協作, 方法論]
|
||||
gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用,選填、deep tier 才產)
|
||||
---
|
||||
# 概念全名
|
||||
|
||||
← [[<bucket>/00-INDEX]]
|
||||
|
||||
**來源**:`[raw source 相對路徑]`
|
||||
**最後更新**:YYYY-MM-DD
|
||||
|
||||
## 摘要
|
||||
[一句話核心]
|
||||
|
||||
## 重點
|
||||
- [自包含改寫的要點,不依賴原文]
|
||||
|
||||
## 實體
|
||||
> 本卡內文的關鍵實體(也是 graph node)。名+描述供下游 embedding normalize。集中放、一行一個、不縮排、不重複。
|
||||
- **原子筆記**(atomic note/卡片原子化)— 每張卡只承載一個不可再分論點的知識記錄單元。
|
||||
- **傳統筆記**(大鍋炒筆記)— 把多主題混雜在同一篇、難精確引用的記錄方式。
|
||||
|
||||
## 關聯
|
||||
### 內文知識關係(內文實體間;端點=上方 `## 實體` 正規名,一字不差)
|
||||
- 原子筆記 >> 對立於 >> 傳統筆記
|
||||
### 卡片關係(卡對卡)
|
||||
- [[本卡]] >> 謂詞(動詞短語) >> [[他卡]]
|
||||
```
|
||||
|
||||
**麵包屑用帶路徑 wikilink**(issue #7):H1 次行放 `← [[<bucket>/00-INDEX]]` 指回桶子索引。
|
||||
桶子索引固定名 `00-INDEX` 跨桶會撞名,故**指 00-INDEX 一律帶路徑**(`[[pkm/00-INDEX]]`,Logseq 原生支援、下游 ingest 也能對應到具體檔)。普通卡片間連結仍用裸 `[[卡名]]`(卡名唯一,不需路徑)。
|
||||
|
||||
**frontmatter 標籤分類**(issue #8):
|
||||
- **用 frontmatter `tags:` 而非行內 `#tag`**:卡片內文常大量用 `#`(講筆記法時的 `#猜想`、`#book100`),分類標籤若也行內 `#`,下游 ingest 無法區分「分類」與「內文範例」會污染 graph。frontmatter 與內文完全分開,零歧義。
|
||||
- **用標籤而非資料夾分類**:資料夾=強制單一歸屬;標籤=多重歸屬。一張卡可同時屬知識管理+AI協作+架構設計,硬塞一個資料夾會在其他檢索角度漏掉。
|
||||
- **雙軸 taxonomy**(寫進 `TAXONOMY.md` 當字典;**受控擴充**,非凍結):
|
||||
- 領域(主軸,1-3 個):如 知識管理/學習認知/AI協作/生產力/系統設計/工具教學
|
||||
- 形態(副軸,0-2 個):方法論/工具實作/觀點主張/架構設計/案例經驗
|
||||
- 一般開發專案的軸可不同(如 子系統/層級/決策類型),由 AI 依專案性質提出、寫進 TAXONOMY.md。
|
||||
- **遇到現有軸裝不下的內容**:先查是否只是現有標籤的同義詞;確實是新軸才加進 TAXONOMY.md(附定義)再用——**禁止繞過字典在卡片直接冒新標籤**。字典是 per-repo,跨 repo 不必共用。
|
||||
|
||||
**typed-edge 規則**(issue #5/#11,把「關係」也預編譯,下游 ingest 直接 parse 出帶類型的有向邊):
|
||||
- **重點抓內文實體關係,不只卡對卡**:卡對卡(`[[卡A]] >> 謂詞 >> [[卡B]]`)只是既有雙鏈加動詞、資訊量幾乎沒增加;價值在內文概念關係(`原子筆記 >> 對立於 >> 傳統筆記`,A/B 是內文概念非卡標題)。
|
||||
1. **方向性**:`A >> 謂詞 >> B` 必須讀成「A(謂詞)B」一句通順的話;A、B 順序就是主→賓真實方向。
|
||||
2. **謂詞用動詞 / 動詞短語**(反駁、奠基於、犧牲)。**禁名詞當謂詞**——`>> 存儲格式 >>`、`>> 操作體驗 >>` 讀不通,是錯的。
|
||||
3. **謂詞自由但別太天馬行空**:「參考/參照」皆可(下游 embed 自動聚類),別寫「瞄了一眼」這種抓不到同義的。
|
||||
4. **內文三元組端點用裸文字**(非 `[[wikilink]]`),避免 Logseq 紅色斷鏈;卡對卡那層才用 `[[]]`。
|
||||
5. **向後相容**:純 `[[A]]` 仍合法(視為無類型邊),盡量補謂詞。
|
||||
|
||||
> **★ 硬自檢(Haiku 量產必備)★** 內文三元組端點必須與 `## 實體` 某粗體正規名【一字不差】。**寫完逐條把 A、B 拿去 `## 實體` 比對**,沒有完全相同的 → 這條錯了,改用實體表已有的詞、或把端點補進 `## 實體` 再指它。禁止端點帶括號註解/整句補語/形容詞短語。(實證:光寫規則 Haiku 會略過,端點對不齊 14 條;寫成自檢動作後 14→0。跑 12 張才暴露。)
|
||||
> `>>` 是分隔語法,repo 可自選符號,但全程一致。
|
||||
|
||||
**萃 gloss 規則**(issue #9/#11,把「node 的一句說明」也預編譯,供下游 KBDB 語義 normalize):
|
||||
- **gloss = 這個 entity / graph node 是什麼的一句話**。下游對「entity 名 + gloss」一起做 embedding 求相似度,自動歸一同義詞(比只對名字準、比手維護 alias 表自動)。
|
||||
- **兩層 gloss**:① frontmatter `gloss:` 描述卡標題這個 node;② `## 實體` 每行描述句描述內文實體 node。**內文實體也是 graph node、也需描述句**才能 normalize(`黃仁勳` vs `Jensen Huang` 靠描述拉近向量)。
|
||||
- **實體要描述、謂詞不用**:實體同義詞字面差遠需描述拉近;謂詞同義詞字面本就近,裸詞 embed 自動聚類。
|
||||
- **在知識生產的當下、由 local CC 建**:gloss 跟三元組同階段萃,**不留給下游 ingest 臨時補**——下游只有單檔 / 跨庫視角,編不出貼合的 gloss(=胡扯)。
|
||||
- **選填、deep tier 才產**:淺萃(只要結構)時不浪費;deep 改寫時每張卡補。
|
||||
- **gloss ≠ 摘要**:`gloss` 是給機器 normalize 的定義句(「X 是…」);`## 摘要` 是給人讀的核心一句。
|
||||
- **格式對齊下游 envelope**:frontmatter `gloss:` 與 `## 實體` 詞條對應下游 ingest envelope 的 `nodes[].gloss`,ingest 直接取用。
|
||||
|
||||
**INDEX.md 是標籤視圖**(非資料夾列表),`00-INDEX.md` 是桶內容器(只連不重寫,H2/H3 分節)。
|
||||
頂層索引指桶子索引帶路徑:`[[pkm/00-INDEX]]`。
|
||||
|
||||
> 與 claude.ai Cowork 的 `system-dev/docs/SKILL.md` 改寫邏輯一致,兩條路徑(CC / Cowork)產出同一種 wiki。
|
||||
|
||||
### 第六步:完成報告 + 驗證
|
||||
|
||||
完成後**驗證原文 0 動**(踩過的坑,issue #8):
|
||||
```
|
||||
git status --short pages/ journals/ # 或一般專案的 docs/ ——須 0 新增 0 修改
|
||||
```
|
||||
|
||||
> **改寫時必守**(subagent 尤其):
|
||||
> 1. **絕不寫入 raw source**:subagent 目標一律給絕對路徑到 `cards/<bucket>/`,明寫「絕不寫入 pages/journals/docs 原稿」;事後用上面的 `git status` 驗。
|
||||
> 2. **檔名 = 卡片全名**,否則 `[[全名]]` 對不到檔。冒號用全形「:」、斜線用全形「/」,**全程一種字元**,避免 `/`、`∕`、`:` 混用斷鏈。
|
||||
> 3. **量大用 Haiku 並行改寫**,主模型只切概念邊界+審稿+修跨資料夾斷鏈。
|
||||
|
||||
告知:
|
||||
```
|
||||
✅ wiki-init 完成
|
||||
建立了:[列出新建的目錄和檔案]
|
||||
跳過了:[列出已有因此不動的]
|
||||
改寫了:[N 份原文 → M 張原子卡、K 條 typed-edge、M 條 gloss(deep tier)]
|
||||
原文驗證:pages/ journals/ git status 0 異動 ✅
|
||||
下一步:用 /wiki-capture 把重要決策存進 wiki
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
# /wiki-recall — Session 開始,手動接關
|
||||
|
||||
開新對話時接上次進度。**Fallback 命令**:SessionStart hook 沒啟動時手動接關;要完整脈絡時也用。
|
||||
|
||||
> 主路徑是 SessionStart hook 自動注入 status 重點,不靠你打命令。
|
||||
> 這支命令應對 hook 失效,以及需要比「status 重點」更完整脈絡的時候。
|
||||
|
||||
---
|
||||
|
||||
## 命名閉環
|
||||
|
||||
init(建) → update(存,session 末) ↔ **recall(接,session 初)** → capture(隨時存結論)
|
||||
|
||||
---
|
||||
|
||||
## 執行流程
|
||||
|
||||
### 第一步:讀 status.md(當前進度)
|
||||
|
||||
讀 `system-dev/wiki/status.md`,掌握:
|
||||
- 正在做什麼、阻擋點
|
||||
- 下次 session 第一件事
|
||||
- 待負責人確認、已知問題
|
||||
|
||||
### 第二步:讀 decisions-summary.md(為什麼這樣做)
|
||||
|
||||
讀 `system-dev/wiki/decisions-summary.md`,掌握相關的架構決策——避免重新討論已定案的事。
|
||||
|
||||
### 第三步:讀 mistakes.md(別重犯)
|
||||
|
||||
讀 `system-dev/wiki/mistakes.md`,掌握已知誤解 + 快速檢查清單。
|
||||
|
||||
### 第四步:掃 wishlist / HANDOFF(如果有)
|
||||
|
||||
- `docs/wishlist.md`:待補功能
|
||||
- 任何 `HANDOFF.md` / 交接note:上一棒留下的脈絡
|
||||
|
||||
### 第五步:回報接關結果
|
||||
|
||||
```
|
||||
📍 接關完成
|
||||
🔄 上次正在做:[status 的「正在做」]
|
||||
🎯 下次第一件事:[status 的「下次 session 第一件事」]
|
||||
⚠️ 待確認:[如有]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 鐵律:快照非即時狀態
|
||||
|
||||
status / wiki 是 **point-in-time 快照,不是即時狀態**。
|
||||
|
||||
接關 = 讀快照 **+ 核實快照**,**不盲信**。
|
||||
|
||||
> 實例:某專案 status 曾寫「待 A 收尾 X」,實際 X 早已完成。
|
||||
> 照舊資訊行動會去催一件已完成的事。
|
||||
|
||||
動手前,先用當前 code / git / 檔案核實快照寫的事項是否仍成立。發現落差 → 先更新 status,再動手。
|
||||
@@ -0,0 +1,50 @@
|
||||
# /wiki-update — Session 結束,更新狀態
|
||||
|
||||
每次 session 結束時執行。更新 status.md,確保下次 session 能無縫接上。
|
||||
|
||||
---
|
||||
|
||||
## 執行流程
|
||||
|
||||
### 第一步:整理這次 session 的結果
|
||||
|
||||
從對話中提取:
|
||||
- 完成了哪些 tasks(標記為 [x])
|
||||
- 進行中但未完成的(標記為 [🔄])
|
||||
- 遇到什麼問題或阻擋
|
||||
- 下次應該從哪裡開始
|
||||
|
||||
### 第二步:更新 tasks.md
|
||||
|
||||
把對應 SDD 的 tasks.md 狀態更新(如果這次有動到的話)。
|
||||
|
||||
### 第三步:更新 status.md
|
||||
|
||||
用以下格式覆蓋 status.md:
|
||||
|
||||
```markdown
|
||||
# 當前狀態
|
||||
> 更新時間:[YYYY-MM-DD]
|
||||
|
||||
## 正在做
|
||||
- [🔄] [task 描述] — 阻擋點:[如果有]
|
||||
|
||||
## 下次 session 第一件事
|
||||
[具體的第一個動作,越具體越好]
|
||||
|
||||
## 待負責人確認
|
||||
- [描述] — 等待:[什麼決定]
|
||||
|
||||
## 已知問題
|
||||
| 問題 | 優先級 | 狀態 |
|
||||
|------|--------|------|
|
||||
| [問題] | 🔴/🟡/⚪ | [狀態] |
|
||||
```
|
||||
|
||||
### 第四步:如果有新的誤解或決策
|
||||
|
||||
順帶執行 `/wiki-capture` 的邏輯,把這次的誤解和決策也存進去。
|
||||
|
||||
### 第五步:確認
|
||||
|
||||
告知 status.md 更新完成,下次 session 從哪裡開始。
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/post-edit-remind-tasks.sh
|
||||
# arcrun PostToolUse hook for Write / Edit / MultiEdit
|
||||
#
|
||||
# 職責:改完 code 後立刻提醒 CC 更新對應 tasks.md,避免批次更新
|
||||
# 退出 code:不 block,只提醒(exit 0)
|
||||
|
||||
set -o pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
|
||||
|
||||
# 只針對程式碼檔案提醒(不含 tasks.md / CLAUDE.md / rules/*.md 本身)
|
||||
if [[ "$FILE_PATH" =~ \.(go|ts|tsx|js|jsx|py|rs|wasm)$ ]] && [[ "$FILE_PATH" != *"tasks.md"* ]]; then
|
||||
cat >&2 <<EOF
|
||||
|
||||
📌 PostEdit 提醒(by arcrun hook)
|
||||
剛修改了:${FILE_PATH}
|
||||
|
||||
下一步動作:
|
||||
1. 找到對應的 docs/3-specs/*/tasks.md
|
||||
2. 如果這個 task 已完成 → 立刻把 [ ] 改成 [x](或把進行中的 [🔄] 改成 [x])
|
||||
3. 如果發現新的 sub-task → 立刻加入 tasks.md
|
||||
4. 不要等到 session 結束才批次更新
|
||||
|
||||
違反 SDD 協議會在 Stop hook 被提醒,建議現在就處理。
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/pre-bash-guard.sh
|
||||
# arcrun PreToolUse guard for Bash
|
||||
#
|
||||
# 職責:擋下會違反 CLAUDE rules 的 shell 指令
|
||||
# 退出 code:
|
||||
# 0 = 允許
|
||||
# 2 = 擋下(stderr 訊息會回傳給 CC)
|
||||
|
||||
set -o pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
|
||||
|
||||
block() {
|
||||
local rule="$1"
|
||||
local reason="$2"
|
||||
local fix="$3"
|
||||
cat >&2 <<EOF
|
||||
❌ BLOCKED by arcrun CLAUDE rules
|
||||
違反項:${rule}
|
||||
指令:${CMD}
|
||||
原因:${reason}
|
||||
正確做法:${fix}
|
||||
參考:.claude/rules/02-forbidden.md
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 1.2 / 3.3:禁止用 mkdir 建立違規的 auth/credential worker 目錄
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if echo "$CMD" | grep -qE "mkdir.*((auth|credential|jwt|oauth)[-_]worker|new[-_](auth|credential|jwt|oauth))"; then
|
||||
block "1.2/3.3" \
|
||||
"偵測到嘗試建立新的 auth/credential/jwt/oauth Worker 目錄" \
|
||||
"auth primitive 放在 registry/components/auth_*/;不需要另建 worker 目錄"
|
||||
fi
|
||||
|
||||
# 禁止建立同名零件的平行目錄
|
||||
if echo "$CMD" | grep -qE "mkdir.*/(gmail|telegram|google[-_]sheets|line[-_]notify|http[-_]request)[-_](v2|v3|new|worker|backup)"; then
|
||||
block "3.3" \
|
||||
"禁止為既有零件建立平行目錄(v2/new/worker/backup)" \
|
||||
"直接改 registry/components/<n>/main.go"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 1.3:禁止 wrangler init / generate auth-* credential-* jwt-*
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if echo "$CMD" | grep -qE "wrangler[[:space:]]+(init|generate).*[[:space:]](auth|credential|jwt|oauth)[-_]"; then
|
||||
block "1.3" \
|
||||
"禁止用 wrangler init/generate 建立 auth/credential/jwt Worker" \
|
||||
"auth primitive 透過 component-worker-template/ 搭配 WASM binary 部署,不要 wrangler init"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 3.1:Service Binding 新增警示
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 偵測在 wrangler.toml 新增 [[services]] 的 echo/cat/sed 操作(非 100% 準確,但夠用)
|
||||
if echo "$CMD" | grep -qE "echo.*\[\[services\]\].*>>"; then
|
||||
block "3.1" \
|
||||
"偵測到要在 wrangler.toml 新增 [[services]] binding" \
|
||||
"零件串接一律走 HTTP URL(cypher binding),不新增 service binding。若有特殊需求,先與 richblack 確認"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 一般性危險指令
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if echo "$CMD" | grep -qE "rm[[:space:]]+-rf[[:space:]]+(/|/\*|~|\\\$HOME|\.)"; then
|
||||
block "general" \
|
||||
"偵測到危險的 rm -rf 指令" \
|
||||
"明確指定要刪的目錄,不要對根目錄 / home / 當前目錄遞迴刪除"
|
||||
fi
|
||||
|
||||
# 禁止 force push 到 main
|
||||
if echo "$CMD" | grep -qE "git[[:space:]]+push.*--force.*(main|master)"; then
|
||||
block "general" \
|
||||
"禁止 force push 到 main/master" \
|
||||
"用 feature branch,或和 richblack 確認後手動操作"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 資料外流警示(data-exfil-warning SDD R2):AI 動手把資料變成可被外部呼叫前先擋
|
||||
# `acr push`(部署 webhook)/ `acr recipe push`(定義資料去向)= 暴露面動作。
|
||||
# 不含 `acr creds push`(上傳加密 credential 是保護,非暴露)。
|
||||
#
|
||||
# 信任修正(2026-05-30):無「旗標放行」捷徑——AI 自己能加的旗標 = 自己批准自己。
|
||||
# 這類動作一律擋,必須由人類在終端機親自執行(CLI 會跳互動、要人類輸入資源名確認)。
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 只在「指令本身就是執行 acr push / acr recipe push」時擋(錨定到指令開頭,
|
||||
# 允許前置 cd .. && 或環境變數)。避免誤判 git commit -m "...acr push..." 這類
|
||||
# 「字串裡剛好提到 acr push」的情況(commit message / echo / grep 不該被擋)。
|
||||
if echo "$CMD" | grep -qE "(^|&&|;|\|)[[:space:]]*(cd[[:space:]][^&;|]*(&&|;)[[:space:]]*)?([A-Za-z_]+=[^[:space:]]*[[:space:]]+)*acr[[:space:]]+(recipe[[:space:]]+)?push\b" \
|
||||
&& ! echo "$CMD" | grep -qE "acr[[:space:]]+creds[[:space:]]+push\b"; then
|
||||
cat >&2 <<'EOF'
|
||||
❌ BLOCKED:資料外流警示(arcrun data-exfil-warning)
|
||||
原因:acr push / acr recipe push 會把資料/workflow 變成「可被外部呼叫」(暴露面)。
|
||||
這種動作你(AI)不能自行執行,也沒有旗標捷徑——需人類明示知情同意(法律憑證)。
|
||||
正確做法:
|
||||
- 把這件事交給人類:請人類在終端機親自執行(CLI 會跳互動、要人類輸入資源名確認)
|
||||
- 人類第一次確認後 server 會記住,之後同資源不用再確認
|
||||
參考:docs/3-specs/data-exfil-warning/
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# Harness: Cold 啟動環境檢查
|
||||
# 用途:init 完成後強制檢查環境就緒,不靠提醒,違反條件直接擋住(exit 2)
|
||||
# 由 acr init 末尾調用
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo ".")"
|
||||
ARCRUN_CONFIG="${HOME}/.arcrun/config.yaml"
|
||||
|
||||
# 顏色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
FAILED=0
|
||||
|
||||
echo -e "${YELLOW}🔍 冷啟動環境檢查${NC}"
|
||||
echo ""
|
||||
|
||||
# 檢查 1: config.yaml 存在
|
||||
if [ ! -f "$ARCRUN_CONFIG" ]; then
|
||||
echo -e "${RED}❌ 缺少 $ARCRUN_CONFIG${NC}"
|
||||
echo " 執行:acr init"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ config.yaml 存在${NC}"
|
||||
fi
|
||||
|
||||
# 檢查 2: api_key 有值
|
||||
if grep -q "^api_key:" "$ARCRUN_CONFIG" 2>/dev/null; then
|
||||
api_key=$(grep "^api_key:" "$ARCRUN_CONFIG" | cut -d' ' -f2)
|
||||
if [ -z "$api_key" ] || [ "$api_key" = "null" ]; then
|
||||
echo -e "${RED}❌ api_key 未設${NC}"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ api_key 已設${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}❌ config.yaml 缺少 api_key${NC}"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# 檢查 3: .mcp.json 存在且指向自己(self-hosted 用)
|
||||
if grep -q "mode.*self-hosted" "$ARCRUN_CONFIG" 2>/dev/null; then
|
||||
if [ ! -f ".mcp.json" ]; then
|
||||
echo -e "${RED}❌ .mcp.json 缺失(self-hosted 必需)${NC}"
|
||||
echo " 執行:acr init 會自動生成"
|
||||
FAILED=1
|
||||
else
|
||||
# 檢查 mcp.json 是否指向自己(不是官方 URL)
|
||||
if grep -q "mcp.arcrun.dev" ".mcp.json" && ! grep -q "workers.dev" ".mcp.json"; then
|
||||
echo -e "${RED}❌ .mcp.json 指向官方 mcp.arcrun.dev(應指向自己的 worker)${NC}"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ .mcp.json 配置正確${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 檢查 4: acr 能執行
|
||||
if ! command -v acr &>/dev/null; then
|
||||
echo -e "${RED}❌ acr 未裝或不在 PATH${NC}"
|
||||
echo " 執行:npm i -g arcrun"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ acr 可執行${NC}"
|
||||
fi
|
||||
|
||||
# 檢查 5: D1 database 存在(如果 self-hosted)
|
||||
if grep -q "mode.*self-hosted" "$ARCRUN_CONFIG" 2>/dev/null; then
|
||||
if grep -q "d1_database_id" "$ARCRUN_CONFIG" 2>/dev/null; then
|
||||
db_id=$(grep "d1_database_id" "$ARCRUN_CONFIG" | cut -d' ' -f2)
|
||||
if [ -z "$db_id" ]; then
|
||||
echo -e "${RED}❌ D1 database_id 未設${NC}"
|
||||
echo " 執行:acr init 會建立 D1,或手動設定 d1_database_id"
|
||||
FAILED=1
|
||||
else
|
||||
echo -e "${GREEN}✓ D1 database_id 已設${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ 環境就緒${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ 環境檢查失敗${NC}"
|
||||
echo ""
|
||||
echo "修復步驟:"
|
||||
echo "1. 檢查 ~/.arcrun/config.yaml 是否完整"
|
||||
echo "2. 若 self-hosted,確認 .mcp.json 指向自己的 mcp worker(不是官方)"
|
||||
echo "3. 若缺少 D1,重跑 acr init --self-hosted --account-id=... --api-token=..."
|
||||
echo ""
|
||||
exit 2
|
||||
fi
|
||||
Executable
+257
@@ -0,0 +1,257 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/pre-write-guard.sh
|
||||
# arcrun PreToolUse guard for Write / Edit / MultiEdit
|
||||
#
|
||||
# 職責:擋下會違反 CLAUDE rules 的檔案寫入操作
|
||||
# 退出 code:
|
||||
# 0 = 允許
|
||||
# 2 = 擋下(stderr 訊息會回傳給 CC)
|
||||
#
|
||||
# 依賴:jq
|
||||
|
||||
set -o pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
|
||||
# 取得將要寫入的內容(Write: content;Edit: new_string;MultiEdit: edits[].new_string 全部串起來)
|
||||
CONTENT=$(echo "$INPUT" | jq -r '
|
||||
.tool_input.content
|
||||
// .tool_input.new_string
|
||||
// (.tool_input.edits // [] | map(.new_string // "") | join("\n"))
|
||||
// ""
|
||||
')
|
||||
|
||||
block() {
|
||||
local rule="$1"
|
||||
local reason="$2"
|
||||
local fix="$3"
|
||||
cat >&2 <<EOF
|
||||
❌ BLOCKED by arcrun CLAUDE rules
|
||||
違反項:${rule}
|
||||
檔案:${FILE_PATH}
|
||||
原因:${reason}
|
||||
正確做法:${fix}
|
||||
參考:.claude/rules/02-forbidden.md
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 1.1:registry/components/ 下不准 TS(除非是 AssemblyScript)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" == *"registry/components/"* && "$FILE_PATH" == *.ts ]]; then
|
||||
# 允許 asconfig.json 同目錄的 AssemblyScript
|
||||
COMP_DIR=$(dirname "$FILE_PATH")
|
||||
if [[ ! -f "$COMP_DIR/asconfig.json" ]]; then
|
||||
block "1.1" \
|
||||
"registry/components/ 下禁止 TypeScript(除非是 AssemblyScript 且同目錄有 asconfig.json)" \
|
||||
"零件必須用 TinyGo (main.go) 或 AssemblyScript 實作並編譯成 .wasm"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 1.2:禁止在非法位置新增 auth/credential 實作
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 合法位置:registry/components/auth_static_key | auth_oauth2 | auth_service_account | auth_mtls
|
||||
if [[ "$FILE_PATH" =~ auth[-_](static[-_]key|oauth2|service[-_]account|mtls) ]]; then
|
||||
if [[ "$FILE_PATH" != *"registry/components/auth_"* ]]; then
|
||||
block "1.2" \
|
||||
"auth primitive 實作只能放在 registry/components/auth_<type>/" \
|
||||
"改去 registry/components/auth_static_key/ 等目錄,用 TinyGo 實作 main.go"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 2.1:禁止新增含特定關鍵字的 TS 檔案(credential-injector / jwt-signer 等)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" == *.ts ]]; then
|
||||
BASE=$(basename "$FILE_PATH")
|
||||
# 既有的 credential-injector.ts / jwt-signer.ts 允許修改(為了刪除),但不准新增同名
|
||||
if [[ "$BASE" =~ ^(credential[-_]injector|jwt[-_]signer)\.ts$ ]]; then
|
||||
if [[ ! -f "$FILE_PATH" ]]; then
|
||||
block "2.1" \
|
||||
"禁止新增 ${BASE}(Phase 1-3 的目標是刪除此類檔案,不是重建)" \
|
||||
"credential 注入 / JWT signing 屬於 WASM 零件職責,改去 registry/components/auth_*/"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 2.2:cypher-executor TS 裡不准實作業務邏輯(只准 wasi-shim.ts 做 crypto)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" == *"cypher-executor/src/"* && "$FILE_PATH" == *.ts ]]; then
|
||||
BASE=$(basename "$FILE_PATH")
|
||||
|
||||
# 通用類別:種子資料檔(*-seeds.ts)= installer 要灌進 KV 的 recipe 種子清單。
|
||||
# 其中的 endpoint / {{template}} 字串是「資料欄位」(宣告 recipe 打哪 / 怎麼注入),
|
||||
# 會被序列化寫進 RECIPES KV,**不是** TS 裡的呼叫 / 展開實作;真正執行仍走零件 / WASM primitive。
|
||||
# 故整類豁免 endpoint / template 字串檢查。這是「種子資料檔」這個普遍類別的規則,
|
||||
# 不是為某個零件 / recipe 開的特例(richblack 2026-06-06 原則:不為單一零件改全域規則)。
|
||||
IS_SEED_DATA_FILE=false
|
||||
if [[ "$BASE" == *-seeds.ts ]]; then
|
||||
IS_SEED_DATA_FILE=true
|
||||
fi
|
||||
|
||||
# crypto.subtle.decrypt:只准在 wasi-shim.ts
|
||||
if echo "$CONTENT" | grep -qE "crypto\.subtle\.decrypt"; then
|
||||
if [[ "$BASE" != "wasi-shim.ts" ]]; then
|
||||
block "2.2" \
|
||||
"AES-GCM 解密(crypto.subtle.decrypt)只准出現在 wasi-shim.ts 的 crypto_decrypt host function" \
|
||||
"把解密邏輯移到 wasi-shim.ts 的 host function;或讓 WASM 零件透過 u6u.crypto_decrypt 呼叫"
|
||||
fi
|
||||
fi
|
||||
|
||||
# crypto.subtle.sign with RSASSA:只准在 wasi-shim.ts
|
||||
if echo "$CONTENT" | grep -qE "crypto\.subtle\.sign.*RSASSA"; then
|
||||
if [[ "$BASE" != "wasi-shim.ts" ]]; then
|
||||
block "2.2" \
|
||||
"RS256 簽章只准出現在 wasi-shim.ts 的 crypto_sign_rs256 host function" \
|
||||
"把簽章移到 wasi-shim.ts;或讓 auth_service_account WASM 透過 u6u.crypto_sign_rs256 呼叫"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Template 展開:{{secret.X}} 或 {{runtime.X}} 屬於 WASM 職責
|
||||
# 例外:種子資料檔(*-seeds.ts)裡的 {{secret.X}} / {{runtime.X}} 是「資料字面值」而非 TS 展開邏輯,
|
||||
# 真正的展開仍在 WASM auth primitive 內完成。
|
||||
if [[ "$IS_SEED_DATA_FILE" == "false" ]] && echo "$CONTENT" | grep -qE "\{\{(secret|runtime)\." ; then
|
||||
block "2.2" \
|
||||
"Template 展開({{secret.X}} / {{runtime.X}})屬於 WASM auth primitive 職責" \
|
||||
"把這段邏輯改寫到 registry/components/auth_static_key/main.go(TinyGo)"
|
||||
fi
|
||||
|
||||
# Hard-code 的 BUILTIN_API_RECIPES / BUILTIN_CREDENTIALS_MAP 新增
|
||||
if echo "$CONTENT" | grep -qE "(BUILTIN_API_RECIPES|BUILTIN_CREDENTIALS_MAP)\s*[:=]"; then
|
||||
# 允許「把它設成空物件」或「刪除」,但不准新增實作
|
||||
if echo "$CONTENT" | grep -qE "BUILTIN_API_RECIPES.*=.*\{\s*[a-zA-Z]"; then
|
||||
block "2.2" \
|
||||
"禁止在 TS 裡新增 BUILTIN_API_RECIPES / BUILTIN_CREDENTIALS_MAP 實作" \
|
||||
"API 呼叫邏輯屬於各自的 WASM 零件(gmail.wasm / telegram.wasm 等),cypher-executor 只做 routing"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Hard-code API endpoint 實作
|
||||
# 例外:種子資料檔(*-seeds.ts)的 endpoint 字串(sheets.googleapis.com 等)是 recipe 的
|
||||
# 「資料欄位」(宣告這個 recipe 預設打哪),會被序列化寫進 RECIPES KV,**不是** TS 裡的呼叫實作;
|
||||
# 真正的 API 呼叫仍走原本零件 / http_request 路徑。
|
||||
HARDCODED_APIS=(
|
||||
"gmail\.googleapis\.com/gmail/v1/users/me/messages/send"
|
||||
"api\.telegram\.org/bot.*sendMessage"
|
||||
"sheets\.googleapis\.com/v4/spreadsheets"
|
||||
"notify-api\.line\.me/api/notify"
|
||||
)
|
||||
for PATTERN in "${HARDCODED_APIS[@]}"; do
|
||||
if echo "$CONTENT" | grep -qE "$PATTERN"; then
|
||||
# 允許 wasi-shim.ts(http_request host function proxy)+ 種子資料檔(*-seeds.ts)
|
||||
if [[ "$BASE" != "wasi-shim.ts" && "$IS_SEED_DATA_FILE" == "false" ]]; then
|
||||
block "2.2" \
|
||||
"禁止在 cypher-executor TS 裡 hard-code API endpoint(偵測到: $PATTERN)" \
|
||||
"把 API 呼叫移到對應的 WASM 零件(registry/components/gmail/main.go 等)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# exchangeGoogleJwt / 類似 token exchange function
|
||||
if echo "$CONTENT" | grep -qE "(exchangeGoogleJwt|exchangeServiceAccountJwt|signGoogleJwt)"; then
|
||||
if [[ "$BASE" != "wasi-shim.ts" ]]; then
|
||||
block "2.2" \
|
||||
"Token exchange 邏輯屬於 auth_service_account WASM 零件" \
|
||||
"改到 registry/components/auth_service_account/main.go"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 3.3:禁止建立 *-v2 / new-* / *-worker 類複製貼上目錄
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" =~ /(auth|credential|jwt|oauth|gmail|telegram|google-sheets|line-notify|http-request)[-_](v2|v3|new|worker|backup|temp)/ ]]; then
|
||||
block "3.3" \
|
||||
"禁止為同一零件建立平行目錄(v2/new/worker/backup 等)" \
|
||||
"直接修改 registry/components/<name>/main.go 即可;需要版本管理請用 git branch"
|
||||
fi
|
||||
if [[ "$FILE_PATH" =~ /new-(auth|credential|jwt|oauth|gmail|telegram)/ ]]; then
|
||||
block "3.3" \
|
||||
"禁止為同一零件建立 new-<name>/ 平行目錄" \
|
||||
"直接修改 registry/components/<name>/main.go"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 7.x:薄殼原則(能力長在 API,介面只暴露)— 見 .claude/rules/07-thin-shell.md
|
||||
# 只擋語法層可偵測的反例;recipe 層拼裝 / 藏在 helper 的邏輯需 code review 把關(07 §6)。
|
||||
# 範圍:薄殼介面 = cli/src/ 與 arcrun-mcp/src/(不含 cypher-executor,那是 API 本體)。
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
block7() {
|
||||
cat >&2 <<EOF
|
||||
❌ BLOCKED by arcrun CLAUDE rules
|
||||
違反項:7(薄殼原則)
|
||||
檔案:${FILE_PATH}
|
||||
原因:$1
|
||||
正確做法:$2
|
||||
參考:.claude/rules/07-thin-shell.md
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
if [[ ( "$FILE_PATH" == *"/cli/src/"* || "$FILE_PATH" == *"arcrun-mcp/src/"* ) \
|
||||
&& ( "$FILE_PATH" == *.ts || "$FILE_PATH" == *.js ) ]]; then
|
||||
|
||||
# 7.1 seed 邏輯不准寫在介面層(§4.1 反例)。seed 是 API 行為,由 API 端點完成。
|
||||
# 允許薄殼「呼叫一個 seed 端點」(單一 fetch),但不准在介面層定義 seed 編排函式。
|
||||
if echo "$CONTENT" | grep -qE "(function|const|async)\s+(seedApiRecipes|seedAuthRecipes|seedRecipes)\b"; then
|
||||
block7 \
|
||||
"seed 編排邏輯寫在介面層(CLI/MCP),這是壓測 §4.1 的根因——seed 應是 API 行為" \
|
||||
"把 seed 改成 API 端點(cypher-executor POST /init/seed 或部署完成時自動 seed),薄殼只呼叫該端點一次"
|
||||
fi
|
||||
|
||||
# 7.2 「先 update 失敗再 insert」/「先 GET 找有則 PATCH 無則 POST」這類 upsert 拼裝
|
||||
# 在同一函式內同時出現 update/patch 與 insert/post 的條件分支 = 把 API 缺的能力拼在介面層。
|
||||
if echo "$CONTENT" | grep -qiE "method:\s*['\"]PATCH['\"]" && echo "$CONTENT" | grep -qiE "method:\s*['\"]POST['\"]"; then
|
||||
if echo "$CONTENT" | grep -qiE "(upsert|找到.*PATCH|沒找到.*POST|exists.*update.*else.*insert|if.*found.*patch)"; then
|
||||
block7 \
|
||||
"在介面層拼裝 upsert(先找/PATCH 否則 POST)——這是能力,該在 API 提供單一 upsert 端點" \
|
||||
"請 cypher-executor 提供 upsert 端點(API 內部做 GET→PATCH/POST),薄殼只呼叫該端點"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 7.3 client 端「全部成功才做下一步」這類補 API 缺口的 gate(§4.1 deployFullyOk 反例)
|
||||
if echo "$CONTENT" | grep -qE "(deployFullyOk|全部成功.*test|/全部成功/)"; then
|
||||
block7 \
|
||||
"用 client 端字串判斷 gate 後續行為(deployFullyOk 類)——脆弱且把編排責任推給介面層" \
|
||||
"讓 API 保證後置狀態(部署成功即 recipe 就緒),薄殼不做這種 gate"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# 規則 4.3:禁止自行在 docs/3-specs/ 下建新 SDD 目錄
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$FILE_PATH" == *"docs/3-specs/"* ]]; then
|
||||
# 檢查是否在已知 SDD 目錄內
|
||||
KNOWN_SDDS=(
|
||||
"docs/3-specs/arcrun"
|
||||
"docs/3-specs/arcrun-core-mvp"
|
||||
"docs/3-specs/arcrun-platform-evolution"
|
||||
"docs/3-specs/component-registry-canon"
|
||||
"docs/3-specs/component-gatekeeping" # 2026-05-29 richblack 確認新建(Phase 3 把關)
|
||||
"docs/3-specs/data-exfil-warning" # 2026-05-30 richblack 確認新建(資料外流警示)
|
||||
"docs/3-specs/user-cc-harness" # 2026-06-03 richblack 確認新建(用戶 CC harness 安裝)
|
||||
"docs/3-specs/llm-interface" # 2026-05-16 richblack 確認新建(AI 操盤手介面)
|
||||
"docs/3-specs/recipe-system" # richblack 確認新建(recipe 系統)
|
||||
"docs/3-specs/resumable-workflow" # richblack 確認新建(可恢復工作流)
|
||||
"docs/3-specs/workflow-discovery" # 2026-06-27 總管 issue #8 交辦新建(工作流 description slot + search_workflow,北極星入口缺口)
|
||||
"docs/3-specs/thin-shell-alignment" # 2026-06-27 總管 issue #11 交辦新建(CLI/MCP 薄殼漂移全面盤點 + 防複發機制)
|
||||
)
|
||||
IN_KNOWN=false
|
||||
for K in "${KNOWN_SDDS[@]}"; do
|
||||
if [[ "$FILE_PATH" == *"$K/"* ]]; then
|
||||
IN_KNOWN=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ "$IN_KNOWN" == "false" ]]; then
|
||||
block "4.3" \
|
||||
"禁止自行在 docs/3-specs/ 下建立新的頂層 SDD 目錄" \
|
||||
"正確程序(見 .claude/rules/00-sdd-protocol.md「新增 SDD 的完整程序」):(1) 先與 richblack 確認要新建此 SDD;(2) 取得明確確認後,在本檔 KNOWN_SDDS 陣列加一行 \"docs/3-specs/<目錄名>\"(執行已授權任務的必要步驟);(3) 白名單放行後才寫 design/tasks。若是現有 SDD 的補充檔,改放已知 SDD 目錄下。"
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse hook 範本骨架 —— 專案自訂禁令(預設空殼,不攔任何東西)
|
||||
#
|
||||
# ⚠️ 定位(讀清楚再用):
|
||||
# 這支跟其他三支 hook 不同——它不是「裝上就生效的警察」,而是一個「按需手填的
|
||||
# 空插槽」。預設狀態下 FORBIDDEN_PATTERNS 是空的,它【不攔任何東西】。
|
||||
# 別誤以為裝了它就有保護——空殼 = 沒保護。
|
||||
#
|
||||
# 🤖 有 CC 在場的話,通常不需要這個範本:
|
||||
# 直接叫你的 CC「幫我寫一支 guard hook,禁止改 X」。CC 現寫的條件邏輯,
|
||||
# 表達力遠勝這裡的 glob FORBIDDEN_PATTERNS(例如「禁子 repo 的 code 但放行 .md」
|
||||
# 這種細緻規則,glob 寫不出來,CC 的條件判斷寫得出來)。
|
||||
# 這個範本只對「不靠 CC、想自己手動 DIY bash」的用戶有價值。
|
||||
#
|
||||
# 要啟用(手動 DIY 路線):
|
||||
# 1. 在下面 FORBIDDEN_PATTERNS 填禁改的路徑/檔名 pattern
|
||||
# 2. 到 .claude/settings.json 的 PreToolUse 加掛這支
|
||||
#
|
||||
# 掛在 PreToolUse(matcher: Write|Edit)。stdin 收 JSON:{ tool_name, tool_input:{ file_path } }
|
||||
# 命中禁令 → exit 2 擋。
|
||||
#
|
||||
# 誠實限制:只擋直接寫檔。bash 繞道、helper 間接改動擋不到。留痕可審 ≠ 技術防偽。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── 專案自訂:禁改的 pattern(一行一個,case glob 語法)──────
|
||||
# 範例(已註解,啟用前請改成自己的):
|
||||
# "*/db/schema.sql" # 禁手改 schema
|
||||
# "*/migrations/*" # migration 一旦建立不可改
|
||||
FORBIDDEN_PATTERNS=(
|
||||
# "*/your/protected/path/*"
|
||||
)
|
||||
|
||||
# 沒設任何禁令 → 空殼狀態,安靜放行。
|
||||
# (不在這裡 print——PreToolUse 每次 Write/Edit 都會跑,每次喊話會洗版。
|
||||
# 「這是空殼」的提醒改由 install.sh / update.sh 安裝時告知,那裡用戶一定看得到。)
|
||||
[ ${#FORBIDDEN_PATTERNS[@]} -eq 0 ] && exit 0
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
else
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
||||
fi
|
||||
|
||||
[ -z "$FILE_PATH" ] && exit 0
|
||||
|
||||
for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
|
||||
# shellcheck disable=SC2254
|
||||
case "$FILE_PATH" in
|
||||
$pattern)
|
||||
cat >&2 <<EOF
|
||||
🚫 專案禁令攔截:$FILE_PATH 命中禁改規則($pattern)。
|
||||
|
||||
這是本專案 .claude/hooks/pre-write-guard.sh 設定的硬底線。
|
||||
要動 → 先和負責人確認,並更新禁令設定。
|
||||
EOF
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
exit 0
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse hook — 動 code 前檢查有沒有對應 SDD
|
||||
# wishlist §2:把 /sdd-check 從「命令要人打」升級成「hook 自動攔」。
|
||||
#
|
||||
# 掛在 settings.json 的 PreToolUse(matcher: Write|Edit)。
|
||||
# stdin 收到 JSON:{ tool_name, tool_input: { file_path, ... } }
|
||||
# 行為:動到 code 檔(.ts/.go/...)但 system-dev/docs/3-specs/ 下沒有任何 SDD → 警告(exit 2 擋)。
|
||||
#
|
||||
# 誠實限制(抄 arcrun):只擋語法層明顯違規(直接寫 code 檔)。
|
||||
# 藏在 helper 裡、用 bash 繞道的改動擋不到。
|
||||
# 價值是「想跳過會被抓到 + 留痕可審」,不是技術防偽。絕不聲稱「不可能繞過」。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
# 解析 file_path。優先用 jq,沒有 jq 退回 grep(容錯)。
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
else
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
||||
fi
|
||||
|
||||
# 拿不到路徑 → 不擋(容錯,寧可放過也不誤殺)
|
||||
[ -z "$FILE_PATH" ] && exit 0
|
||||
|
||||
# 只管 code 檔。docs/markdown/設定檔等放行。
|
||||
case "$FILE_PATH" in
|
||||
*.ts|*.tsx|*.js|*.jsx|*.go|*.py|*.rs|*.java|*.rb|*.php|*.c|*.cpp|*.h|*.hpp|*.swift|*.kt) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# 改 SDD 自己 / 測試檔 → 放行
|
||||
case "$FILE_PATH" in
|
||||
*system-dev/docs/3-specs/*) exit 0 ;;
|
||||
*_test.*|*.test.*|*.spec.*|*/tests/*|*/test/*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# system-dev/docs/3-specs/ 下完全沒有 design.md → 攔
|
||||
SDD_COUNT=0
|
||||
if [ -d "system-dev/docs/3-specs" ]; then
|
||||
SDD_COUNT=$(find system-dev/docs/3-specs -name 'design.md' -not -path '*TEMPLATE*' 2>/dev/null | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
if [ "$SDD_COUNT" -eq 0 ]; then
|
||||
cat >&2 <<EOF
|
||||
🚫 SDD 協議攔截:要動 code 檔 ($FILE_PATH),但 system-dev/docs/3-specs/ 下找不到任何 SDD。
|
||||
|
||||
絕對鐵律:任何 code 變動前必須有對應 SDD(design.md)。
|
||||
|
||||
請先:
|
||||
1. 確認這個改動屬於哪個子系統
|
||||
2. 在 system-dev/docs/3-specs/[子系統]/ 建立 design.md(可用 /sdd-check 協助)
|
||||
3. 在回覆開頭宣告已讀 SDD + 對應 task
|
||||
|
||||
小修改(修 bug、改文字)若確定豁免,請明確說明範圍後由人放行。
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# 有 SDD:放行,但留痕提醒要宣告(stderr 警告,不擋)
|
||||
echo "📋 提醒:system-dev/docs/3-specs/ 下有 SDD。動手前請確認已讀對應 design.md 並在回覆宣告。" >&2
|
||||
exit 0
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/session-start-load-sdd.sh
|
||||
# arcrun SessionStart hook
|
||||
#
|
||||
# 職責:session 啟動時注入當前進度、絕對禁令、SDD 位置,避免 CC 靠記憶猜測
|
||||
# 輸出走 stdout,會注入到 CC 的 context
|
||||
|
||||
set -o pipefail
|
||||
|
||||
cat <<'EOF'
|
||||
============================================================
|
||||
🚨 arcrun 工作規範(SessionStart 注入)
|
||||
============================================================
|
||||
|
||||
📌 絕對禁令(違反會被 pre-write / pre-bash hook 直接 block):
|
||||
|
||||
1. registry/components/ 下禁止 TypeScript 實作零件
|
||||
→ 只能 TinyGo (main.go) 或 AssemblyScript,編譯成 .wasm
|
||||
|
||||
2. cypher-executor TS 裡禁止實作 credential/auth/JWT 業務邏輯
|
||||
→ crypto.subtle.decrypt / sign 只准出現在 wasi-shim.ts 的 host function
|
||||
→ {{secret.X}} template 展開屬於 WASM 零件職責
|
||||
→ 禁止 hard-code gmail/telegram/sheets API endpoint
|
||||
|
||||
3. 禁止新增 Service Binding 綁零件
|
||||
→ 零件串接走 HTTP URL(cypher binding = YAML 裡的 URL 清單)
|
||||
→ Cypher binding 不是 Cloudflare 的 binding 機制
|
||||
|
||||
4. 每個 WASM 零件 = 獨立 Worker = 公開 URL
|
||||
→ 不是從 R2 動態讀(平台零件已 bundle 進各自 Worker)
|
||||
→ R2 只用於 Phase 5 的用戶自製零件(未啟用)
|
||||
|
||||
📋 任何 code 變動前,必須:
|
||||
|
||||
1. 讀 docs/3-specs/arcrun/arcrun.md(總進度)
|
||||
2. 讀對應 task 的 SDD(design.md + tasks.md)
|
||||
3. 在回覆開頭宣告(格式見 .claude/rules/00-sdd-protocol.md):
|
||||
📋 已讀 SDD:<檔案清單>
|
||||
🎯 本次對應 task:<task 編號>
|
||||
📐 本次 task 的 SDD 規範摘要:<重點>
|
||||
🚧 執行範圍:修改/建立/刪除 <檔案>
|
||||
4. 每完成一個 task,立刻更新 tasks.md 的 [x],不批次
|
||||
|
||||
🔥 當前進行中 Phase:Credential Primitives TS → WASM
|
||||
|
||||
SDD:docs/3-specs/arcrun/credential-primitives-wasm/
|
||||
已完成:Phase 0.1-0.5(核心合併 + u6u-core 刪除)
|
||||
未完成硬前置:
|
||||
- Phase 0.6 wasi-shim 加 host functions(kv_get / crypto_decrypt / crypto_sign_rs256)
|
||||
- Phase 0.7 component-loader WASM runner 路徑
|
||||
未完成主要任務:
|
||||
- Phase 1 auth_static_key WASM 零件(TinyGo)
|
||||
- Phase 2 auth_service_account WASM 零件
|
||||
- Phase 3 刪除 cypher-executor 的三套違規 TS:
|
||||
* src/actions/credential-injector.ts(整檔刪)
|
||||
* src/lib/jwt-signer.ts(整檔刪)
|
||||
* src/lib/component-loader.ts 的 BUILTIN_API_RECIPES + BUILTIN_CREDENTIALS_MAP(整段刪)
|
||||
|
||||
⛔ 封測狀態:推遲(richblack 2026-04-19 決定)
|
||||
原因:違規 TS 未清,不封測。
|
||||
|
||||
📚 詳細規範:
|
||||
.claude/rules/00-sdd-protocol.md — SDD 協議
|
||||
.claude/rules/01-tech-stack.md — 技術棧硬限制
|
||||
.claude/rules/02-forbidden.md — 禁止清單(hook 強制)
|
||||
.claude/rules/03-component-architecture.md — 零件架構(R2/cypher binding/service binding 的正確定義)
|
||||
.claude/rules/04-current-progress.md — 當前進度 + SDD 索引
|
||||
.claude/rules/06-mindset.md — mindset(為什麼層):工作流是default/零件稀有/AI→工具/誠實不假綠
|
||||
|
||||
============================================================
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# SessionStart hook — 開 session 自動注入 status.md 重點
|
||||
# wishlist §1 主路徑:不靠 CC 自覺、不用人說,開 session 就把進度推到眼前。
|
||||
#
|
||||
# 掛在 settings.json 的 SessionStart(matcher: startup|resume|clear)。
|
||||
# stdout 會被當成 context 注入給 CC。
|
||||
#
|
||||
# 鐵律:status 是 point-in-time 快照,非即時狀態。
|
||||
# 這個 hook 只負責「把快照推到眼前」,核實快照是 CC 的責任——下面的提醒就是要它別盲信。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
STATUS_FILE="system-dev/wiki/status.md"
|
||||
PRINCIPLES_FILE="system-dev/wiki/principles.md"
|
||||
MISTAKES_FILE="system-dev/wiki/mistakes.md"
|
||||
|
||||
# ── 舊結構防呆(1.9.0 遷移第 2 層保險)──
|
||||
# 新版 wiki 收進 system-dev/。若偵測到舊位置 .claude/wiki/ 還在、但新位置沒 status,
|
||||
# 代表使用者升級了規則卻沒跑遷移(low-code 使用者常見)→ 出聲提示,讓 CC 當場可代為遷移,
|
||||
# 不要默默用不到新結構而出錯。
|
||||
if [ -d ".claude/wiki" ] && [ ! -f "$STATUS_FILE" ]; then
|
||||
echo "⚠️ 偵測到舊版 wiki 結構(.claude/wiki/),尚未遷移到 system-dev/wiki/。"
|
||||
echo " 請跑:bash system-dev/scripts/update.sh"
|
||||
echo " 或直接叫我(CC):「幫我把 wiki 遷移到 system-dev/」——我可以代為搬移。"
|
||||
echo " (未遷移時接關與 /wiki-init 會找錯位置。)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 三個 push 檔都沒有 → 安靜退出,不干擾還沒 /wiki-init 的專案
|
||||
if [ ! -f "$STATUS_FILE" ] && [ ! -f "$PRINCIPLES_FILE" ] && [ ! -f "$MISTAKES_FILE" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── push 1/3:principles(全文,行動前必服從)──
|
||||
# 放最前:原則是「會被遺忘的盲區」,要第一眼看見。全文成本低(一行一條、≤15 條)。
|
||||
if [ -f "$PRINCIPLES_FILE" ] && grep -q '^- ' "$PRINCIPLES_FILE" 2>/dev/null; then
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo "📐 設計原則(行動前必服從,來自 principles.md)"
|
||||
echo "════════════════════════════════════════════════"
|
||||
grep '^- ' "$PRINCIPLES_FILE" # 只注入原則條目本身,不含說明區
|
||||
# context 保護:原則應 ≤15 條(push 全文)。超過 → 提示該合併或下放成 card,不截斷(截斷會漏原則)。
|
||||
P_COUNT=$(grep -c '^- ' "$PRINCIPLES_FILE")
|
||||
if [ "$P_COUNT" -gt 15 ]; then
|
||||
echo ""
|
||||
echo "(⚠️ principles 已 ${P_COUNT} 條 > 15:請考慮合併相近原則、或把較細的下放成 card)"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ── push 2/3:status(全文,當前進度)──
|
||||
if [ -f "$STATUS_FILE" ]; then
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo "📍 接關:上次進度(來自 $STATUS_FILE 快照)"
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo ""
|
||||
cat "$STATUS_FILE"
|
||||
fi
|
||||
|
||||
# ── push 3/3:mistakes(標題清單 + 一行症狀,全文按需展開)──
|
||||
# 不全文注入(可能累積很多、含長 context)。只給「標題行」讓 CC 認出「我正撞到某條」→ 再去讀全文。
|
||||
if [ -f "$MISTAKES_FILE" ] && grep -q 'MISTAKE' "$MISTAKES_FILE" 2>/dev/null; then
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────"
|
||||
echo "⚠️ 已知踩坑(標題;撞到時讀 $MISTAKES_FILE 全文)"
|
||||
grep -E 'MISTAKE' "$MISTAKES_FILE" | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────"
|
||||
echo "⚠️ 以上是 point-in-time 快照,非即時狀態。"
|
||||
echo " 動手前先核實:快照寫的事項是否真的還沒做完?"
|
||||
echo " 需要完整脈絡(decisions / mistakes / SDD)→ 執行 /wiki-recall"
|
||||
echo "════════════════════════════════════════════════"
|
||||
|
||||
# ── 遷移殘留偵測(1.9.x):wiki 已搬到 system-dev/,但 CLAUDE.md 內容裡可能還寫舊路徑 ──
|
||||
# update.sh 鐵則「絕不碰 CLAUDE.md」(用戶資料),所以遷移不會自動改它內容裡的死引用。
|
||||
# 這裡只「偵測 + 提示 CC 代修」——由 CC(看得懂語義、不誤傷用戶自寫內容)來改,不讓腳本盲改。
|
||||
if [ -f "CLAUDE.md" ] && grep -q '\.claude/wiki' CLAUDE.md 2>/dev/null; then
|
||||
echo ""
|
||||
echo "🔧 偵測到 CLAUDE.md 內仍有舊路徑 \`.claude/wiki/\`(wiki 已遷移到 system-dev/wiki/)。"
|
||||
echo " 這些是遷移後的死引用,會讓你(CC)找錯位置。"
|
||||
echo " 請主動幫使用者把 CLAUDE.md 裡的 \`.claude/wiki/\` 改成 \`system-dev/wiki/\`、"
|
||||
echo " 工具 docs 路徑改成 \`system-dev/docs/\`(但 raw source 宣告的 \`docs/\` 保留不動)。"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# .claude/hooks/stop-check-sync.sh
|
||||
# arcrun Stop hook
|
||||
#
|
||||
# 職責:session 結束前檢查 code 變動是否有對應的 SDD / tasks.md 更新
|
||||
# 退出 code:不 block,只警告
|
||||
|
||||
set -o pipefail
|
||||
|
||||
# 檢查 docs/3-specs 下本次 session 是否有變動
|
||||
SPECS_DIFF=$(git -C "$(pwd)" status --porcelain -- 'docs/3-specs/' 2>/dev/null | head -20)
|
||||
CODE_DIFF=$(git -C "$(pwd)" status --porcelain -- '*.go' '*.ts' '*.tsx' '*.py' 'cypher-executor/' 'registry/' 'cli/' 2>/dev/null | head -20)
|
||||
|
||||
if [[ -n "$CODE_DIFF" && -z "$SPECS_DIFF" ]]; then
|
||||
cat >&2 <<EOF
|
||||
|
||||
⚠️ Stop hook 警告(by arcrun hook)
|
||||
|
||||
偵測到本 session 有程式碼變動,但 docs/3-specs/ 下的 SDD 文件沒有任何變動。
|
||||
|
||||
未 commit 的程式碼變動:
|
||||
$(echo "$CODE_DIFF" | head -10)
|
||||
|
||||
請在結束前確認:
|
||||
1. 對應的 tasks.md 是否已更新 [x]?
|
||||
2. 是否有架構變動需要更新 design.md?
|
||||
3. 是否有 SDD 範圍外的 change 未標記?
|
||||
|
||||
SDD 協議要求:code 和 SDD 必須同步更新。
|
||||
參考:.claude/rules/00-sdd-protocol.md
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
# 若有暫存的 tasks.md 變動,提醒 commit
|
||||
TASKS_DIFF=$(git -C "$(pwd)" status --porcelain -- 'docs/3-specs/**/tasks.md' 2>/dev/null | head -5)
|
||||
if [[ -n "$TASKS_DIFF" ]]; then
|
||||
cat >&2 <<EOF
|
||||
|
||||
📝 提醒:tasks.md 有未 commit 的變動
|
||||
$(echo "$TASKS_DIFF")
|
||||
記得在結束前 commit。
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse hook — 寫入 wiki 前掃機敏資訊(L3 硬攔截)
|
||||
#
|
||||
# 為什麼存在:wiki 的 ignore 規則(.wikiignore + 行內標記)是「協議層」,靠 CC 遵守。
|
||||
# 但密碼/金鑰/個資外洩是「不可逆」後果——只靠口頭約束太危險。
|
||||
# 這支 hook 是機械式底線:CC 真的把機敏資訊寫進 system-dev/wiki/ 的那一刻 → exit 2 擋下。
|
||||
#
|
||||
# 掛在 settings.json 的 PreToolUse(matcher: Write|Edit)。
|
||||
# stdin 收到 JSON:{ tool_name, tool_input: { file_path, content?, new_string? } }
|
||||
# 行為:只在目標路徑是 system-dev/wiki/** 時啟動,掃要寫入的內容,命中機敏特徵 → exit 2。
|
||||
#
|
||||
# 誠實限制(抄 sdd-guard):regex 偵測有偽陰/偽陽。
|
||||
# 擋的是「明顯特徵的機敏字串被自動抄進 wiki」,擋不了刻意混淆/編碼的繞道。
|
||||
# 價值是「意外外洩的機械底線 + 留痕可審」,不是技術防偽。絕不聲稱「不可能繞過」。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
# ── 解析 file_path 與要寫入的內容。優先 jq,無 jq 退回 grep(容錯)──────
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
# Write 用 content;Edit 用 new_string。兩個都抓,合起來掃。
|
||||
CONTENT=$(printf '%s' "$INPUT" | jq -r '[.tool_input.content, .tool_input.new_string] | map(select(. != null)) | join("\n")')
|
||||
else
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
||||
# 無 jq 時內容解析不可靠(JSON 跳脫),退回掃整包 INPUT,寧可多掃不漏掃
|
||||
CONTENT="$INPUT"
|
||||
fi
|
||||
|
||||
# 拿不到路徑 → 不擋(容錯,寧可放過也不誤殺)
|
||||
[ -z "$FILE_PATH" ] && exit 0
|
||||
|
||||
# 只管寫進 wiki 的動作。其他路徑放行(這支專責 wiki 洩漏,不是全域 secret scanner)
|
||||
case "$FILE_PATH" in
|
||||
*system-dev/wiki/*) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
[ -z "$CONTENT" ] && exit 0
|
||||
|
||||
# 行內豁免:若該段內容已被標記為刻意保留(例:範例文件要示範格式),略過該行
|
||||
# 標記:行尾加 # wiki-secret-ok (或 <!-- wiki-secret-ok -->)
|
||||
# 先把標記過的行抽掉再掃。
|
||||
SCAN=$(printf '%s' "$CONTENT" | grep -v -E 'wiki-secret-ok' || true)
|
||||
[ -z "$SCAN" ] && exit 0
|
||||
|
||||
# ── 機敏特徵 pattern。一行一類,命中即攔。──────────────────────────
|
||||
# 設計取捨:偏向高訊號 pattern(有明確結構的金鑰/標記),降低偽陽。
|
||||
# 純「password=xxx」這類也納入,因為那正是使用者最擔心的場景。
|
||||
HITS=""
|
||||
|
||||
check() {
|
||||
local label="$1" regex="$2"
|
||||
# -e 讓以 - 開頭的 pattern(如 PEM 的 -----BEGIN)不被當成選項。
|
||||
# grep 無命中回傳 1,在 set -e 下會中止 → 用 if 包住吸收掉。
|
||||
if printf '%s' "$SCAN" | grep -qiE -e "$regex"; then
|
||||
HITS="${HITS}
|
||||
• ${label}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 密碼/密鑰賦值(password = ..., secret: ..., api_key=...)
|
||||
check "密碼/密鑰賦值 (password/secret/api_key/token = ...)" \
|
||||
'(pass(word)?|secret|api[_-]?key|access[_-]?key|auth[_-]?token|priv(ate)?[_-]?key)[[:space:]]*[:=][[:space:]]*[^[:space:]<>"'"'"']{6,}'
|
||||
|
||||
# 私鑰 PEM 區塊
|
||||
check "私鑰檔內容 (BEGIN ... PRIVATE KEY)" \
|
||||
'-----BEGIN[[:space:]].*PRIVATE KEY-----'
|
||||
|
||||
# 常見雲端/服務金鑰前綴
|
||||
check "服務金鑰特徵 (AWS/GitHub/Slack/Google/Stripe 等)" \
|
||||
'(AKIA[0-9A-Z]{16}|gh[pousr]_[0-9A-Za-z]{20,}|xox[baprs]-[0-9A-Za-z-]{10,}|AIza[0-9A-Za-z_-]{20,}|sk_(live|test)_[0-9A-Za-z]{16,})'
|
||||
|
||||
# JWT
|
||||
check "JWT token" \
|
||||
'eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}'
|
||||
|
||||
# 連線字串內嵌帳密 (proto://user:pass@host)
|
||||
check "連線字串內嵌帳密 (proto://user:pass@host)" \
|
||||
'[a-z][a-z0-9+.-]*://[^[:space:]:/@]+:[^[:space:]:/@]+@'
|
||||
|
||||
# 台灣身分證字號(個資)。BSD/GNU grep 都支援 ERE,避免 \b(BSD 不認),改用字元類邊界。
|
||||
check "台灣身分證字號 (個資)" \
|
||||
'(^|[^A-Za-z0-9])[A-Z][12][0-9]{8}([^0-9]|$)'
|
||||
|
||||
# 信用卡號(個資,粗略 13-16 連續數字,可含空格/連字號分隔)。避免 PCRE,用 ERE 近似。
|
||||
check "疑似信用卡號 (個資)" \
|
||||
'(^|[^0-9])[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{0,4}([^0-9]|$)'
|
||||
|
||||
# Email 不擋(wiki 常需記聯絡人),手機號也不擋(偽陽太高)——刻意留白。
|
||||
|
||||
if [ -n "$HITS" ]; then
|
||||
cat >&2 <<EOF
|
||||
🚫 Wiki 機敏攔截:偵測到可能的機敏資訊要寫進 ${FILE_PATH}。
|
||||
|
||||
命中特徵:${HITS}
|
||||
|
||||
wiki 是會被 CC 反覆讀取、可能進版控的記憶空間。
|
||||
密碼 / 金鑰 / 個資寫進去 = 不可逆外洩風險。
|
||||
|
||||
請改成下列任一做法:
|
||||
1. 不要把機敏值寫進 wiki,改記「位置」(例:「DB 密碼放 1Password / .env,不入 wiki」)
|
||||
2. 確定是誤判(例:在示範格式)→ 該行尾加註記 wiki-secret-ok 後重寫
|
||||
3. 整個來源檔本就機敏 → 加進 system-dev/wiki/.wikiignore,別讓它被編入
|
||||
|
||||
誠實限制:本掃描靠特徵比對,有偽陽/偽陰,是「意外外洩的機械底線」而非保險箱。
|
||||
真正的密鑰本就不該進版控。
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,99 @@
|
||||
# SDD 協議(每次啟動必讀)
|
||||
|
||||
## 第零原則:沒讀 SDD 不准動 code
|
||||
|
||||
任何 `.go` / `.ts` / `.tsx` / `.wasm` 相關變動,**必須**按以下順序執行。**不得簡化,不得跳過**。
|
||||
|
||||
### 步驟 1:讀總進度
|
||||
|
||||
先讀 `docs/3-specs/arcrun/arcrun.md`,了解當前 Phase。
|
||||
|
||||
### 步驟 2:定位對應 SDD
|
||||
|
||||
根據任務性質找對應 SDD:
|
||||
|
||||
| 任務類型 | 對應 SDD |
|
||||
|---------|---------|
|
||||
| Auth primitive WASM 零件(static_key/oauth2/service_account/mtls) | `docs/3-specs/arcrun/credential-primitives-wasm/` |
|
||||
| 清除 cypher-executor 裡的 TS 業務邏輯 | `docs/3-specs/arcrun/credential-primitives-wasm/` |
|
||||
| WASI shim host functions(kv_get / crypto_decrypt / crypto_sign_rs256) | `docs/3-specs/arcrun/credential-primitives-wasm/` |
|
||||
| Auth Recipe 系統(recipe schema、KV 格式) | `docs/3-specs/arcrun/auth-recipe.md` |
|
||||
| Landing Page | `docs/3-specs/arcrun/landing-page.md` |
|
||||
| CLI / SDK(Python/JS) | `docs/3-specs/arcrun/sdk-and-website/` |
|
||||
| arcrun-core-mvp 整體架構 | `docs/3-specs/arcrun-core-mvp/` |
|
||||
| Platform Evolution | `docs/3-specs/arcrun-platform-evolution/` |
|
||||
| Credential 長期規格(需求源) | `docs/user_requirements/credential_parts.md` |
|
||||
|
||||
讀 `design.md` 和 `tasks.md` 兩份。
|
||||
|
||||
### 步驟 3:宣告(強制格式)
|
||||
|
||||
開始動手前,在回覆開頭**逐字**貼出以下宣告:
|
||||
|
||||
```
|
||||
📋 已讀 SDD:
|
||||
- docs/3-specs/arcrun/arcrun.md(當前 Phase:<phase 名稱>)
|
||||
- <對應 SDD 的 design.md 路徑>
|
||||
- <對應 SDD 的 tasks.md 路徑>
|
||||
|
||||
🎯 本次對應 task:<task 編號,例如 "Phase 1.3 實作 auth_static_key main.go">
|
||||
|
||||
📐 本次 task 的 SDD 規範摘要:
|
||||
- <重點 1>
|
||||
- <重點 2>
|
||||
- <重點 3>
|
||||
|
||||
🚧 執行範圍:
|
||||
- 會修改:<檔案清單>
|
||||
- 會建立:<檔案清單>
|
||||
- 會刪除:<檔案清單>
|
||||
```
|
||||
|
||||
**不做這個宣告 = 違反 SDD 協議 = 停手等 richblack**。
|
||||
|
||||
### 步驟 4:check tasks.md 狀態
|
||||
|
||||
動手前:在 tasks.md 把對應 task 的 `- [ ]` 改成 `- [🔄]`(進行中標記)。
|
||||
完成後:改成 `- [x]`,不批次更新,每完成一個就立刻改。
|
||||
|
||||
## 什麼算「任務超出 SDD 範圍」?
|
||||
|
||||
以下情況屬於 **change**,不是 **modify**,**必須停手並與 richblack 確認**:
|
||||
|
||||
- SDD 沒寫到的新功能
|
||||
- 新增頂層目錄
|
||||
- 新增新的 Worker(不管是 cypher-executor / registry / 零件 worker)
|
||||
- 修改架構決策(例如「改用 xxx 取代 yyy」)
|
||||
- 跨多個子系統的連鎖修改
|
||||
|
||||
**停手不是怯懦,是專業**。猜錯方向比慢一小時更糟。
|
||||
|
||||
## 新增 SDD 的完整程序(richblack 確認後怎麼往下走)
|
||||
|
||||
> 2026-06-03 補:之前協議只寫「停手等確認」,沒寫「確認後怎麼做」,導致 CC 被
|
||||
> `pre-write-guard.sh` 規則 4.3 擋下後不知正路、卡死。這節補完整程序。
|
||||
|
||||
當「新增一個 SDD 子系統」(在 `docs/3-specs/` 下開新頂層目錄):
|
||||
|
||||
1. **停手,向 richblack 說明要新建哪個 SDD、為什麼**(change,見上節)。
|
||||
2. **取得 richblack 明確確認**後,依序:
|
||||
1. **更新白名單**:在 `.claude/hooks/pre-write-guard.sh` 的 `KNOWN_SDDS` 陣列加一行
|
||||
`"docs/3-specs/<新目錄名>" # YYYY-MM-DD richblack 確認新建(<一句用途>)`。
|
||||
(這步是「執行 richblack 已授權任務的必要步驟」,不是 AI 擅自放寬 guardrail——
|
||||
授權脈絡要明確,分類器才放行;沒有明確授權就改白名單 = 越界。)
|
||||
2. **建目錄 + 寫 design.md / tasks.md**(白名單放行後才寫得進去)。
|
||||
3. **若沒先更新白名單就 Write** → 規則 4.3 會擋你(這是對的,表示你跳了步驟 2.i)。
|
||||
|
||||
**為什麼白名單而非全放行**:開新 SDD 子系統 = 宣告新架構範圍,是稀有的人類決策點。
|
||||
白名單讓「AI 自己無中生有開子系統」必停下(AI 改不了白名單,除非 richblack 明確授權該任務)。
|
||||
已許可的目錄內寫檔零摩擦。
|
||||
|
||||
## 發現 SDD 本身有問題怎麼辦?
|
||||
|
||||
- SDD 和實作不一致 → 停手,列出矛盾點,與 richblack 確認哪一邊是對的
|
||||
- SDD 規範之間互相矛盾(例如禁令 A 和設計 B 衝突)→ 停手,引用矛盾原文,與 richblack 確認
|
||||
- **不可以自行猜哪個是對的**。CC 之前兩天就是這樣走錯的。
|
||||
|
||||
## 為什麼這個協議存在
|
||||
|
||||
arcrun 規範已經足夠細緻,CC 之前出錯不是因為不懂,而是因為**沒讀**或**讀了覺得「大概是這個意思」就動手**。SDD 協議強制把「先讀 → 定位 → 宣告 → 執行 → 更新」做成一條死規矩,沒有繞過去的路徑。
|
||||
@@ -0,0 +1,76 @@
|
||||
# 技術棧硬限制
|
||||
|
||||
## 三層語言對應(絕對不可混用)
|
||||
|
||||
| 層級 | 語言 | 位置 | 職責 |
|
||||
|-----|------|------|------|
|
||||
| 零件(Component) | **TinyGo 或 AssemblyScript → WASM** | `registry/components/{name}/` | 所有業務邏輯 |
|
||||
| 零件 Worker 包裝 | TypeScript(固定模板,不寫業務邏輯) | `.component-builds/{name}/` | WASI shim,stdin/stdout JSON |
|
||||
| Orchestration Worker | TypeScript + Hono | `cypher-executor/` | HTTP routing、workflow 執行排程、host functions |
|
||||
| CLI | TypeScript + Node.js | `cli/` | `acr` 指令 |
|
||||
| Python SDK | Python | `python-sdk/` | HTTP thin wrapper + client 端加密 |
|
||||
| JS SDK | TypeScript + Web Crypto | `js-sdk/` | HTTP thin wrapper + client 端加密 |
|
||||
| Frontend | React 19 + Vite + Tailwind v4 | `landing/` | Cloudflare Pages |
|
||||
|
||||
## 零件實作規範
|
||||
|
||||
### 只有兩種合法語言
|
||||
- **TinyGo**:`tinygo build -target=wasi -o {name}.wasm main.go`
|
||||
- **AssemblyScript**:`asc main.ts --target release -o {name}.wasm`
|
||||
|
||||
### I/O 模型
|
||||
- **stdin**:JSON input
|
||||
- **stdout**:JSON output
|
||||
- 不用 HTTP server,不監聽 socket(WASI preview1 沒 socket)
|
||||
|
||||
### Host Functions(零件呼叫外部能力的唯一管道)
|
||||
在 `u6u` namespace 下:
|
||||
|
||||
| Host Function | 用途 |
|
||||
|---|---|
|
||||
| `u6u.http_request` | 發 HTTP 請求 |
|
||||
| `u6u.kv_get` | 讀 Cloudflare KV(Worker 側依 key 前綴路由到正確 KV) |
|
||||
| `u6u.crypto_decrypt` | AES-GCM 解密(encryption key 永不暴露給 WASM) |
|
||||
| `u6u.crypto_sign_rs256` | RSA-SHA256 簽章(PKCS8 bytes 傳入) |
|
||||
|
||||
**所有 host function 在 `cypher-executor/src/lib/wasi-shim.ts` 實作**。零件透過 WASI import 使用。
|
||||
|
||||
## 資料儲存
|
||||
|
||||
| 儲存 | 用途 | Key 格式 |
|
||||
|-----|------|---------|
|
||||
| Cloudflare KV `WEBHOOKS` | workflow 定義(cypher binding YAML) | `webhook:{api_key}:{name}` |
|
||||
| Cloudflare KV `CREDENTIALS_KV` | 加密 credential | `{api_key}:cred:{name}` |
|
||||
| Cloudflare KV `RECIPES` | auth recipe / 動態 API recipe | `auth_recipe:{service}`, `rec_{hash}` |
|
||||
| Cloudflare KV `USERS_KV` | 用戶資料 | `user:{api_key}` |
|
||||
| Cloudflare KV `SESSIONS_KV` | session | `session:{token}` |
|
||||
| Cloudflare KV `ANALYTICS_KV` | 執行分析 | `execution:{timestamp}:{id}` |
|
||||
| Cloudflare KV `EXEC_CONTEXT` | workflow 執行中的 context | `ctx:{execution_id}:{node_id}` |
|
||||
| Cloudflare R2 `WASM_BUCKET` | **只用於用戶自製零件**(Phase 5 以後啟用) | `{api_key}:cmp:{hash}` |
|
||||
|
||||
**警告:R2 不存平台內建零件的 WASM**。平台零件已 bundle 進各自的 Worker binary(`[[wasm_modules]]` 或 `import ... assert { type: 'webassembly' }`)。
|
||||
|
||||
## 加解密規範
|
||||
|
||||
- **演算法**:AES-GCM 256-bit
|
||||
- **加密位置**:Client 端(CLI / Python SDK / JS SDK)
|
||||
- Python:`cryptography` 套件
|
||||
- JS:Web Crypto API(`crypto.subtle`)
|
||||
- **解密位置**:Server 端 **WASM primitive**(透過 host function `crypto_decrypt`)
|
||||
- cypher-executor TS **不解密**,只提供 host function
|
||||
- `ENCRYPTION_KEY` 只在 Worker host function 內部讀取,**永不經 stdin / 回傳值傳給 WASM**
|
||||
- **傳輸格式**:`{ name, encrypted, iv }`(iv base64、encrypted base64)
|
||||
|
||||
## 網路部署
|
||||
|
||||
- **平台 API(對外)**:`cypher.arcrun.dev`(cypher-executor)
|
||||
- **Landing**:`arcrun.dev`
|
||||
- **每個零件 Worker**:
|
||||
- **對內(cypher-executor 用來 fetch component,避開同 zone 死鎖)**:`arcrun-{kebab}.{WORKER_SUBDOMAIN}.workers.dev`
|
||||
- 例:`arcrun-kbdb-get.uncle6-me.workers.dev`
|
||||
- cypher-executor 從 `wrangler.toml [vars] WORKER_SUBDOMAIN` 組出此 URL
|
||||
- **對外(可選,零件對全網開放被 curl 用)**:`{kebab}.arcrun.dev`
|
||||
- 例:`gmail.arcrun.dev`、`kbdb-get.arcrun.dev`
|
||||
- 仍允許保留,但**禁止 cypher-executor 透過此 URL fetch**(會撞同 zone 自循環,見 [docs/incidents/2026-05-13-cypher-outbound-522.md](../../docs/incidents/2026-05-13-cypher-outbound-522.md))
|
||||
- **新增 component worker 部署清單**:`name = "arcrun-{kebab}"` + `[[routes]]` 對外(可選)+ dashboard 啟用 workers.dev(必須)
|
||||
- **部署工具**:Wrangler
|
||||
@@ -0,0 +1,154 @@
|
||||
# 禁止行為清單(零容忍)
|
||||
|
||||
**這份清單由 `.claude/hooks/*.sh` 強制執行。違反會 block 工具呼叫(exit 2)**。
|
||||
|
||||
---
|
||||
|
||||
## 第一類:零件實作層級的禁令
|
||||
|
||||
### 1.1 禁止在 `registry/components/` 下建立 TypeScript 檔案
|
||||
零件**只能**用 TinyGo(`.go`)或 AssemblyScript(`.ts` 但需 `asconfig.json`)實作,並編譯成 `.wasm`。
|
||||
cypher-executor/registry Worker 或 `.component-builds/` 內的 TS 不算零件邏輯,那是 WASI shim。
|
||||
|
||||
**Hook 會擋**:新增 `registry/components/*/{檔案}.ts`(除非目錄內有 `asconfig.json` 明確標記為 AssemblyScript)。
|
||||
|
||||
### 1.2 禁止建立新的 `auth_*` 目錄以外的 auth 實作
|
||||
所有 auth 邏輯只能在:
|
||||
- `registry/components/auth_static_key/`
|
||||
- `registry/components/auth_oauth2/`
|
||||
- `registry/components/auth_service_account/`
|
||||
- `registry/components/auth_mtls/`
|
||||
|
||||
**不可以**出現 `cypher-executor/src/auth-primitive/`、`cypher-executor/src/lib/auth-*.ts`、`auth-worker/`、`credential-worker/` 等目錄。
|
||||
|
||||
**Hook 會擋**:`mkdir` 或 `Write` 到上述違規路徑。
|
||||
|
||||
### 1.3 禁止用 `wrangler init/generate` 建立 auth/credential/jwt 相關的 TS Worker
|
||||
Auth primitive 必須透過 `component-worker-template/` 搭配 WASM binary 部署。
|
||||
|
||||
**Hook 會擋**:bash 指令含 `wrangler (init|generate) ... auth_`、`... credential_`、`... jwt_` 的 pattern。
|
||||
|
||||
---
|
||||
|
||||
## 第二類:cypher-executor TS 的禁令
|
||||
|
||||
### 2.1 禁止新增任何 credential / auth / jwt 相關的 TS 檔案
|
||||
**已存在但要刪**(Phase 1-3 範圍):
|
||||
- `cypher-executor/src/actions/credential-injector.ts` → 刪除(走 WASM auth primitive)
|
||||
- `cypher-executor/src/lib/jwt-signer.ts` → 刪除(RS256 移入 auth_service_account WASM)
|
||||
- `cypher-executor/src/lib/component-loader.ts` 的 `BUILTIN_API_RECIPES` 和 `BUILTIN_CREDENTIALS_MAP` → 整段刪除
|
||||
|
||||
**Hook 會擋**:新增任何路徑含以下關鍵字的 `.ts` 檔案:
|
||||
- `credential-injector`、`credential_injector`
|
||||
- `jwt-signer`、`jwt_signer`
|
||||
- `auth-dispatcher` 的 TS 若嘗試在裡面實作 credential 解密 / template 展開 / JWT signing,block
|
||||
|
||||
### 2.2 禁止在 cypher-executor 任何 TS 裡實作以下邏輯
|
||||
這些邏輯全部屬於 WASM 零件職責:
|
||||
|
||||
- AES-GCM 解密(`crypto.subtle.decrypt`)— 只准出現在 `wasi-shim.ts` 的 `crypto_decrypt` host function
|
||||
- RSA-SHA256 簽章(`crypto.subtle.sign` with RSASSA-PKCS1-v1_5)— 只准出現在 `wasi-shim.ts` 的 `crypto_sign_rs256` host function
|
||||
- Template 展開(`{{secret.X}}` / `{{runtime.X}}` 替換)— 只能在 WASM 零件內
|
||||
- PEM → PKCS8 解析
|
||||
- JWT header/payload/signature 組裝
|
||||
- Token exchange(拿 service account JWT 換 access_token)
|
||||
- 具體 API call 實作(例如 gmail send / telegram sendMessage / google sheets append)
|
||||
|
||||
**Hook 會擋**:
|
||||
- Write/Edit 到 `cypher-executor/src/` 下的 `.ts` 時,內容含:
|
||||
- `crypto\.subtle\.decrypt` 且檔名不是 `wasi-shim.ts`
|
||||
- `crypto\.subtle\.sign.*RSASSA` 且檔名不是 `wasi-shim.ts`
|
||||
- `interpolateTemplate`、`\{\{secret\.` 的模板邏輯
|
||||
- `BUILTIN_API_RECIPES`、`BUILTIN_CREDENTIALS_MAP`(新增用)
|
||||
- `gmail.googleapis.com/gmail/v1/users/me/messages/send` 類 hard-code API URL
|
||||
- `api.telegram.org/bot.*sendMessage`
|
||||
- `sheets.googleapis.com/v4/spreadsheets`
|
||||
- `notify-api.line.me/api/notify`
|
||||
|
||||
### 2.3 cypher-executor TS 的合法職責(允許)
|
||||
- HTTP routing(Hono routes)
|
||||
- workflow 執行排程(`graph-executor.ts`)
|
||||
- 呼叫 WASM 零件(透過 HTTP fetch 到對應 Worker URL,或 Service Binding fallback)
|
||||
- 提供 host function(`wasi-shim.ts` 的 `kv_get` / `crypto_decrypt` / `crypto_sign_rs256`)
|
||||
- KV/R2/Service Binding 存取封裝
|
||||
|
||||
---
|
||||
|
||||
## 第三類:架構層級的禁令
|
||||
|
||||
### 3.1 禁止新增 Service Binding
|
||||
**Cypher binding 不是 Cloudflare service binding**。它是 YAML/KV 裡的 URL 清單。
|
||||
|
||||
零件串接(workflow 層)一律走 HTTP URL,不走 `[[services]]`。
|
||||
|
||||
13 個現有的 `SVC_*` 綁定(`cypher-executor/wrangler.toml`,邏輯零件)是歷史遺產(效能優化),**保留但不新增**。
|
||||
|
||||
> **2026-06-06 註(credential-primitives-wasm Phase 7)**:self-hosted 的 cypher 與 auth worker 同在 `{sub}.workers.dev` zone,cypher `fetch()` 打 auth 觸發 CF **same-zone 1042**(壓測階段 11)。**未用 service binding 解**(評估後廢:service binding 靜態、加/改要重 deploy cypher)。改用 **`global_fetch_strictly_public` compatibility flag**(cypher wrangler.toml)讓 same-zone fetch 走公網前門 → 同 zone 也通,**auth 維持 HTTP fetch、不加 binding**。故本禁令不變。
|
||||
|
||||
**Hook 會擋**:bash 指令含 `wrangler tail` 以外、涉及 `[[services]]` 新增的 pattern;Edit wrangler.toml 新增 `[[services]]` 區塊時警告確認。
|
||||
|
||||
### 3.2 禁止以「從 R2 取 WASM」為設計
|
||||
平台內建零件已 bundle 進各自 Worker,不從 R2 取。
|
||||
R2 只在 Phase 5(用戶自製零件)啟用。
|
||||
|
||||
**Hook 會警告**:TS 中出現 `env.WASM_BUCKET.get(` 的新增 code(除非在明確標註的 Phase 5 user-submit 路徑中)。
|
||||
|
||||
### 3.3 禁止複製貼上 Worker 程式碼到新目錄
|
||||
要改 `gmail` 零件 → 改 `registry/components/gmail/main.go`,重新編譯、部署。
|
||||
**不准**新建 `gmail-v2/`、`new-gmail/`、`gmail-worker/` 等目錄。
|
||||
|
||||
**Hook 會擋**:`mkdir` 或 `Write` 到 `{component-name}-v2/`、`new-{component-name}/`、`{component-name}-worker/` 類路徑。
|
||||
|
||||
### 3.4 禁止在 SDK 內做 server 職責
|
||||
- **禁止**:SDK 裡做 server 端解密、credential-injector 重實作、workflow executor、auth recipe 解析
|
||||
- **允許**:SDK 做 HTTP thin wrapper + client 端加密(AES-GCM)
|
||||
|
||||
---
|
||||
|
||||
## 第四類:流程層級的禁令
|
||||
|
||||
### 4.1 禁止沒讀 SDD 就動 code
|
||||
見 `00-sdd-protocol.md`。
|
||||
|
||||
### 4.2 禁止批次更新 tasks.md
|
||||
每完成一個 task 就立刻 mark `- [x]`。不准「先全部做完再一次更新」。
|
||||
|
||||
### 4.3 禁止新建 SDD 而不事先與 richblack 確認
|
||||
SDD 屬於架構決策,必須人確認。CC 不可以自行在 `docs/3-specs/` 底下建新目錄。
|
||||
例外:在現有 SDD 目錄內新增 `requirements.md` / `design.md` / `tasks.md` 的單檔補充(需在 CLAUDE.md 已註記的 SDD 範圍內)。
|
||||
|
||||
---
|
||||
|
||||
## 第五類:薄殼原則的禁令(詳見 07-thin-shell.md)
|
||||
|
||||
### 5.1 禁止在薄殼介面(cli/src/、arcrun-mcp/src/)實作業務邏輯
|
||||
能力只實作一次,放在 API(cypher-executor 端點)。薄殼只做介面轉換 + 暴露。
|
||||
|
||||
**Hook 會擋**(規則 7.x,範圍 `cli/src/` 與 `arcrun-mcp/src/` 的 .ts/.js):
|
||||
- 7.1 新增 `seedApiRecipes` / `seedAuthRecipes` / `seedRecipes` 編排函式(seed 是 API 行為,§4.1 反例)
|
||||
- 7.2 介面層拼裝 upsert(同函式內 PATCH + POST + upsert/找則改否則建 字樣)
|
||||
- 7.3 client 端「全部成功才做下一步」gate(`deployFullyOk` 類,§4.1 反例)
|
||||
|
||||
### 5.2 禁止用**零件**補 API 缺的能力(污染零件庫)
|
||||
缺能力 → 先看 07 §3.5 自力救濟階梯分流:**自家** API 缺 → 補 API endpoint;**第三方** API 缺(gsheets filter / 無 upsert)→ 走 **workflow/code-node 補丁**(合法,是資料產物非介面層 TS,hook 範圍外);純計算 → code-node;真需新穩定能力才建零件 PR。
|
||||
禁的是「在介面層拼裝」(5.1,hook 擋)與「亂建零件污染零件庫」,**不是禁任何補丁**(hook 偵測不到語意,靠 code review)。
|
||||
|
||||
### 5.3 禁止同一 API 能力在不同介面用不同參數簽名 / 連不同帳號
|
||||
`validate` 在 CLI 吃 YAML、MCP 卻要 `api_key`+`graph` = 底層分歧。薄殼差異只能來自介面慣例。
|
||||
所有薄殼讀同一份身份來源(見 07 §4)。
|
||||
|
||||
---
|
||||
|
||||
## Hook Block 訊息格式
|
||||
|
||||
當 hook 擋住一個操作時,訊息格式統一為:
|
||||
|
||||
```
|
||||
❌ BLOCKED by arcrun CLAUDE rules
|
||||
違反項:<禁令編號,例如 2.2>
|
||||
原因:<簡短說明>
|
||||
正確做法:<該改去哪裡、該用什麼方式>
|
||||
參考:.claude/rules/<對應檔案>
|
||||
```
|
||||
|
||||
這樣 CC 拿到錯誤訊息後有機會自行導正,不是被擋死就愣住。
|
||||
@@ -0,0 +1,165 @@
|
||||
# 零件架構與部署模式(必讀,CC 最常搞錯的地方)
|
||||
|
||||
## 第一核心概念:每個 WASM 零件 = 一個獨立 Worker = **兩個** URL
|
||||
|
||||
**不是**從 R2 即時載入 WASM 執行。
|
||||
**不是**用 service binding 串零件。
|
||||
**不是**一個 Worker 裡跑多個零件。
|
||||
|
||||
**是**:每個零件都是獨立部署的 Worker,每個都有**兩個 URL**:
|
||||
|
||||
| URL 類型 | Pattern | 用途 |
|
||||
|---|---|---|
|
||||
| 對內(cypher-executor 用)| `arcrun-{kebab}.{WORKER_SUBDOMAIN}.workers.dev` | cypher-executor fetch component 走這個,避開同 zone 自循環死鎖(P0 #9)|
|
||||
| 對外(直接 curl 用,可選)| `{kebab}.arcrun.dev` | 用戶單獨打 component 測試或 self-hosted 用法 |
|
||||
|
||||
例:`kbdb_get` 零件:
|
||||
- 對內:`arcrun-kbdb-get.uncle6-me.workers.dev`(cypher-executor 走這個)
|
||||
- 對外:`kbdb-get.arcrun.dev`(用戶 / 直 curl)
|
||||
|
||||
**為什麼這樣設計**:CF Workers 「同 zone 自循環防護」會讓綁 `cypher.arcrun.dev/*` 的 cypher-executor fetch 同 zone `*.arcrun.dev` 撞 522。完整事件報告:[docs/incidents/2026-05-13-cypher-outbound-522.md](../../docs/incidents/2026-05-13-cypher-outbound-522.md)。改走 workers.dev 子域繞過。
|
||||
|
||||
### 零件 Worker 的結構
|
||||
|
||||
```
|
||||
registry/components/{name}/
|
||||
├── main.go ← TinyGo 原始碼(實際零件邏輯)
|
||||
├── component.contract.yaml ← 輸入/輸出規格
|
||||
└── {name}.wasm ← TinyGo 編譯產物
|
||||
```
|
||||
|
||||
部署時,透過 `component-worker-template/` 把 WASM 包進一個 Hono Worker:
|
||||
```
|
||||
.component-builds/{name}/
|
||||
├── package.json
|
||||
├── wrangler.toml ← name = "arcrun-{name}",route = "{name}.arcrun.dev"
|
||||
├── component.wasm ← 從 registry/components/{name}/ 複製過來
|
||||
└── src/index.ts ← 固定的 WASI shim(POST / → stdin → WASM → stdout → JSON)
|
||||
```
|
||||
|
||||
**src/index.ts 是通用模板**,所有零件都用同一份。這個 TS 只做 WASI runtime,不是業務邏輯。
|
||||
|
||||
---
|
||||
|
||||
## R2(WASM_BUCKET)的真正用途
|
||||
|
||||
R2 存 WASM 只是**用戶自製零件上傳**用的。
|
||||
|
||||
**平台內建零件不從 R2 讀取**——它們在部署時就已 bundle 進 Worker 的 binary(透過 `[[wasm_modules]]` 或 `import` with `assert { type: 'webassembly' }`)。
|
||||
|
||||
Phase 5(封測後)才會啟用「用戶 push 自製零件 → 存 R2 → 動態執行」這條路徑。
|
||||
|
||||
**結論:當 CC 問「怎麼從 R2 取出 WASM」時,幾乎都是走錯路徑**。平台零件是獨立 Worker,走 HTTP 呼叫,不是 R2 動態載入。
|
||||
|
||||
---
|
||||
|
||||
## Cypher binding 的正確定義
|
||||
|
||||
**Cypher binding 不是 Cloudflare 的任何 binding 機制。**
|
||||
|
||||
Cypher binding 是一張 YAML 清單,內容是「一個 workflow 要呼叫哪些零件 URL」。存放在:
|
||||
- 本地:`workflow.yaml`(用戶寫的 workflow)
|
||||
- KV:`WEBHOOKS` KV(用戶 `acr push` 後存入)
|
||||
|
||||
Cypher executor 執行 workflow 時:
|
||||
1. 從 KV 讀出 workflow YAML
|
||||
2. 按 graph 順序解析每個節點的 `component`
|
||||
3. 用 HTTP fetch 打對應的零件 URL
|
||||
4. 把 output 當作下個節點的 input
|
||||
|
||||
**這就是 Cypher binding——用 HTTP URL 把零件串起來,存在 YAML/KV 裡**。
|
||||
|
||||
### 為什麼不能用 Service Binding?
|
||||
|
||||
Service binding 需要 `wrangler.toml` 裡寫死 `[[services]]`,且要 redeploy 才生效。arcrun 是類 n8n 服務,用戶建立新 workflow 時**絕對不可能**要他 redeploy。所以 workflow 層一定要 HTTP。
|
||||
|
||||
### Service Binding 的僅存合法用途
|
||||
|
||||
只在 `cypher-executor` 和**平台內建邏輯零件之間**保留(效能優化,避免公網往返)。看 `cypher-executor/wrangler.toml` 裡的 13 個 `[[services]]` 綁定就是這個用途。
|
||||
|
||||
**禁止新增任何 Service Binding**。所有新零件(含 auth primitive)都走 HTTP URL 路徑。
|
||||
|
||||
**same-zone 1042 的解(credential-primitives-wasm Phase 7,2026-06-06)**:self-hosted 的 cypher 與 auth worker 同在 `{sub}.workers.dev` zone,cypher `fetch()` 打 auth 觸發 CF **1042**(官方 docs:「fetch from another Worker on the **same zone**」;官方 cypher 在 `cypher.arcrun.dev`、打 `*.workers.dev` 屬跨 zone 故不踩——非官方有 flag)。**解法不是 service binding**(評估後廢:靜態、加/改要重 deploy),而是 cypher wrangler.toml 加 **`global_fetch_strictly_public` flag**——讓 same-zone fetch 走公網前門 → 同 zone 也通。auth 維持 HTTP fetch、不加 binding。官方加此 flag 行為不變(本就跨 zone),self-host 被修好 → **官方與 self-host 共用同一份 toml**。
|
||||
|
||||
**仍禁止**:為**用戶自製 / 服務專屬零件**(`gmail-worker`、`notion-worker` 之類)新增 binding——那些是 recipe 的事,不該有 binding。**workflow 層(用戶串零件)一律 HTTP URL 不變。**
|
||||
|
||||
---
|
||||
|
||||
## 零件之間怎麼串:實際流程
|
||||
|
||||
假設 workflow 是:webhook → gmail(要 auth)→ google_sheets(要 auth)
|
||||
|
||||
```
|
||||
用戶 POST https://cypher.arcrun.dev/webhooks/named/xxx/trigger
|
||||
│
|
||||
▼
|
||||
cypher-executor(Worker)讀 workflow YAML
|
||||
│
|
||||
├─ 節點 1: component = gmail
|
||||
│ a. 查 auth_recipe:gmail → primitive = static_key
|
||||
│ b. HTTP POST https://auth-static-key.arcrun.dev
|
||||
│ { action: "authenticate", api_key, service: "gmail" }
|
||||
│ → 回傳 { auth_headers: { Authorization: "Bearer ..." } }
|
||||
│ c. HTTP POST https://gmail.arcrun.dev
|
||||
│ { to, subject, body, _auth_headers }
|
||||
│ → gmail 零件 Worker 執行 WASM → 回傳 { success, data }
|
||||
│
|
||||
└─ 節點 2: component = google_sheets
|
||||
... 相同模式
|
||||
```
|
||||
|
||||
**cypher-executor 本身不做 credential 解密、不做 JWT signing、不做 auth header 組裝**。這些全在 auth primitive WASM 零件內,cypher-executor 只負責 HTTP routing 和工作流排程。
|
||||
|
||||
---
|
||||
|
||||
## 實際禁令(CC 看這裡)
|
||||
|
||||
### 禁止在 `registry/components/` 下建立 TypeScript 檔案
|
||||
零件邏輯一律 TinyGo 或 AssemblyScript,編譯成 `.wasm`。
|
||||
|
||||
### 禁止把 auth 邏輯寫在 `cypher-executor/src/` 裡
|
||||
credential 解密、JWT signing、template 展開(`{{secret.X}}`)全部屬於 auth primitive WASM 零件的職責。cypher-executor 只呼叫它們。
|
||||
|
||||
### 禁止問「怎麼從 R2 取 WASM」
|
||||
平台內建零件**不從 R2 取**。每個零件已部署成獨立 Worker,走 HTTP URL。用戶自製零件才用 R2(Phase 5,未啟用)。
|
||||
|
||||
### 禁止新增 Service Binding
|
||||
13 個現有的 SVC_*(邏輯零件)是歷史遺產,不新增。新零件(含 auth primitive)一律走 HTTP URL。self-hosted 的 same-zone 1042 用 `global_fetch_strictly_public` flag 解,不靠新增 binding(見上「合法用途」段,Phase 7)。
|
||||
|
||||
### 禁止重建已存在的零件 Worker
|
||||
要改 `gmail` 零件邏輯 → 改 `registry/components/gmail/main.go`,重新編譯 `.wasm`,重新部署對應 Worker。**不要**在 `cypher-executor/src/lib/` 或其他地方建「新的 gmail 實作」。
|
||||
|
||||
---
|
||||
|
||||
## 部署一個新零件的完整步驟(auth_static_key 為例)
|
||||
|
||||
1. 建立 `registry/components/auth_static_key/`:
|
||||
- `main.go`(TinyGo 實作)
|
||||
- `component.contract.yaml`(IO 規格)
|
||||
2. 編譯:`cd registry/components/auth_static_key && tinygo build -target=wasi -o auth_static_key.wasm main.go`
|
||||
3. 建立 `.component-builds/auth_static_key/`:
|
||||
- 複製 `component-worker-template/src/index.ts`
|
||||
- 複製 `component-worker-template/package.json`
|
||||
- 新建 `wrangler.toml`:
|
||||
```toml
|
||||
name = "arcrun-auth-static-key"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2025-02-19"
|
||||
[vars]
|
||||
COMPONENT_ID = "auth_static_key"
|
||||
[[routes]]
|
||||
pattern = "auth-static-key.arcrun.dev/*"
|
||||
zone_name = "arcrun.dev"
|
||||
```
|
||||
- 複製 `auth_static_key.wasm` 到此目錄為 `component.wasm`
|
||||
4. `cd .component-builds/auth_static_key && pnpm install && pnpm deploy`
|
||||
5. **Dashboard 啟用 workers.dev URL**(必須,否則 cypher-executor fetch 不到):
|
||||
- Workers & Pages → `arcrun-auth-static-key` → Settings → Domains & Routes → workers.dev → Enable
|
||||
- 啟用後 URL:`arcrun-auth-static-key.{WORKER_SUBDOMAIN}.workers.dev`
|
||||
6. 驗證對外:`curl https://auth-static-key.arcrun.dev` → 應回 `{ok: true, component: "auth_static_key"}`
|
||||
7. 驗證對內:`curl https://arcrun-auth-static-key.{WORKER_SUBDOMAIN}.workers.dev` → 應同樣回 200
|
||||
8. cypher-executor 透過 `wasmWorkerUrl()` 自動組對內 URL 呼叫(不用手動註冊)
|
||||
|
||||
**這是唯一正確的部署流程**。任何偏離這個流程的「替代方案」都要先和 richblack 確認。
|
||||
|
||||
**Step 5 為什麼必須**:見 arcrun.md P0 #9(2026-05-13)。cypher-executor 走對內 URL 避開同 zone 自循環死鎖;若 workers.dev 未啟用,cypher-executor fetch 該 component 會 404。
|
||||
@@ -0,0 +1,77 @@
|
||||
# 當前進度(SessionStart 會注入此檔重點)
|
||||
|
||||
> 更新時間:2026-04-19
|
||||
> 權威來源:`docs/3-specs/arcrun/credential-primitives-wasm/tasks.md`
|
||||
> 此檔僅摘要,詳細狀態以 tasks.md 為準。
|
||||
|
||||
---
|
||||
|
||||
## 封測狀態
|
||||
|
||||
**原定明天封測,richblack 決定推遲**,原因:cypher-executor 有三套 TS 業務邏輯違反「零件一律 WASM」架構原則(Phase 1-3 要清除的程式碼),在清除前不封測。
|
||||
|
||||
---
|
||||
|
||||
## 目前 Phase:Credential Primitives TS → WASM
|
||||
|
||||
**SDD 位置**:`docs/3-specs/arcrun/credential-primitives-wasm/design.md` + `tasks.md`
|
||||
|
||||
### 已完成
|
||||
|
||||
- **Phase 0.1–0.5**:核心合併(u6u-core 併入 arcrun、21 個零件 contract 完整、刪除重複 `credentials/` 目錄、CREDENTIALS_KV binding 確認、刪除 `matrix/u6u-core/`)
|
||||
- `registry/components/` 下 21 個零件(邏輯 + API)都有 `main.go` + `.wasm`
|
||||
|
||||
### 進行中 / 未完成
|
||||
|
||||
| Task | 狀態 | 阻擋關係 |
|
||||
|-----|------|---------|
|
||||
| 0.6 wasi-shim 新增 `kv_get` / `crypto_decrypt` / `crypto_sign_rs256` host functions | ⬜ 未開始 | **Phase 1-3 的硬前置** |
|
||||
| 0.7 component-loader 新增 WASM runner 路徑 | ⬜ 未開始 | **Phase 1-3 的硬前置** |
|
||||
| 1.1-1.8 `auth_static_key` WASM 零件(TinyGo) | ⬜ 未開始 | 涵蓋 80% 服務 |
|
||||
| 2.1-2.6 `auth_service_account` WASM 零件(JWT signing) | ⬜ 未開始 | Google Service Account 等 |
|
||||
| 3.1-3.5 清除 `component-loader.ts` 的 `BUILTIN_API_RECIPES` | ⬜ 未開始 | 要先有 Phase 1-2 的 WASM 零件 |
|
||||
| 4.1-4.4 `auth_oauth2` + `auth_mtls`(封測後) | ⬜ 未開始 | 非阻擋項 |
|
||||
| 5.1-5.7 核心穩定驗證(全域搜尋確認無殘餘 TS) | ⬜ 未開始 | 封測啟動門檻 |
|
||||
|
||||
### Phase 1-3 要**徹底刪除**的 TS 檔案(不是搬、不是改,是刪)
|
||||
|
||||
| 檔案 | 違反什麼 |
|
||||
|-----|---------|
|
||||
| `cypher-executor/src/actions/credential-injector.ts` | AES 解密、template 展開、JWT 邏輯 —— 應在 WASM |
|
||||
| `cypher-executor/src/lib/jwt-signer.ts` | RS256 JWT 簽章邏輯 —— 應在 `auth_service_account.wasm` |
|
||||
| `cypher-executor/src/lib/component-loader.ts` 的 `BUILTIN_API_RECIPES`(~100 行) | gmail/telegram/line/gsheets/http_request/cron 的 TS 實作 —— 應全部走對應 WASM 零件 |
|
||||
|
||||
---
|
||||
|
||||
## 下一個 session 第一件要做的事
|
||||
|
||||
**讀 `docs/3-specs/arcrun/credential-primitives-wasm/tasks.md`**,然後決定從 Phase 0.6 還是 0.7 開始。
|
||||
|
||||
0.6(host functions)和 0.7(WASM runner)是並列的前置工作,哪個先都可以,但都要在 Phase 1 開始之前完成。
|
||||
|
||||
---
|
||||
|
||||
## SDD 索引
|
||||
|
||||
| 子系統 | SDD |
|
||||
|--------|-----|
|
||||
| **主要(正在動)** Credential Primitives WASM 改寫 | `docs/3-specs/arcrun/credential-primitives-wasm/` |
|
||||
| **LI (LLM Interface)** — AI 操盤手使用體驗(2026-05-16 新建,mira dogfood 痛點轉化) | `docs/3-specs/llm-interface/` |
|
||||
| arcrun 總進度 | `docs/3-specs/arcrun/arcrun.md` |
|
||||
| Auth Recipe 系統(schema、預建 20 個服務) | `docs/3-specs/arcrun/auth-recipe.md` |
|
||||
| Landing Page | `docs/3-specs/arcrun/landing-page.md` |
|
||||
| SDK + Website | `docs/3-specs/arcrun/sdk-and-website/design.md` |
|
||||
| arcrun MVP 整體 | `docs/3-specs/arcrun-core-mvp/design.md` |
|
||||
| Credential 長期規格(需求源) | `docs/user_requirements/credential_parts.md` |
|
||||
| Platform Evolution | `docs/3-specs/arcrun-platform-evolution/design.md` |
|
||||
| Tech Stack 詳細 | `docs/3-specs/tech.md` |
|
||||
|
||||
---
|
||||
|
||||
## 技術備註(CC 常搞錯的點)
|
||||
|
||||
1. **每個 WASM 零件 = 獨立 Worker = 公開 URL**(例:`gmail.arcrun.dev`)。不是從 R2 動態讀。
|
||||
2. **Cypher binding = YAML 裡寫 URL 清單**。不是 Cloudflare service binding。
|
||||
3. **cypher-executor 只做 routing + host functions**。業務邏輯全在 WASM 零件。
|
||||
4. **TinyGo 有限制**:`crypto/rsa` 支援不全 → 用 host function `crypto_sign_rs256` 讓 Worker 代簽。
|
||||
5. 詳見 `.claude/rules/03-component-architecture.md`。
|
||||
@@ -0,0 +1,138 @@
|
||||
# 部署慣例(CI/CD)
|
||||
|
||||
> **核心原則:新增 Worker = 新目錄 + `wrangler.toml`,不用改 workflow。**
|
||||
|
||||
`.github/workflows/deploy.yml` 是**通用掃描式** workflow,不該為每個 Worker 手寫 job。
|
||||
|
||||
---
|
||||
|
||||
## Workflow 如何找到要部署的 Worker?
|
||||
|
||||
```
|
||||
find . -name 'wrangler.toml' -not -path '*/node_modules/*' -not -name 'wrangler.test.toml'
|
||||
```
|
||||
|
||||
每一個命中的目錄 = 一個部署單位。無論是:
|
||||
|
||||
- `cypher-executor/` (orchestration Worker)
|
||||
- `registry/` (合約管理 Worker)
|
||||
- `.component-builds/{name}/` (零件 Worker,25+ 個)
|
||||
- 未來新增的任何 Worker
|
||||
|
||||
**無需改 workflow,只要符合掃描規則就會自動部署**。
|
||||
|
||||
---
|
||||
|
||||
## 觸發邏輯
|
||||
|
||||
| 觸發 | 部署範圍 |
|
||||
|------|---------|
|
||||
| `push` 到 main | diff 涉及的 Worker 目錄才部署 |
|
||||
| `push` 到 main + 改 `registry/components/{name}/` | 連動 rebuild `.component-builds/{name}/component.wasm` 再 deploy |
|
||||
| `workflow_dispatch` + `force_all=true` | 全部 Worker |
|
||||
| `workflow_dispatch` + `only=a,b,c` | 只部署指定清單 |
|
||||
| `push` 但 base sha 不可及(首次) | 全部 Worker |
|
||||
|
||||
---
|
||||
|
||||
## 新增 Worker 的步驟
|
||||
|
||||
### 如果是新 WASM 零件 Worker
|
||||
|
||||
1. 在 `registry/components/{new_name}/` 建 `main.go` + `component.contract.yaml`
|
||||
2. 在 `.component-builds/{new_name}/` 建 Worker 模板:
|
||||
- `wrangler.toml`(name/routes/bindings)
|
||||
- `package.json`(hono + workers-types + wrangler 即可,參考 `auth_static_key/package.json`)
|
||||
- `tsconfig.json`(可直接複製)
|
||||
- `src/index.ts`(WASI shim,方案 A:import `../../cypher-executor/src/lib/wasi-shim`)
|
||||
3. 本地跑 `pnpm install` 產 `pnpm-lock.yaml`
|
||||
4. 本地跑 `tinygo build -target=wasi -o {new_name}.wasm main.go` 先驗證 build 通過
|
||||
5. Commit push → CI 自動 rebuild WASM + deploy
|
||||
|
||||
### 如果是新 orchestration/service Worker
|
||||
|
||||
1. 在 repo 根建新目錄(類似 `cypher-executor/`)
|
||||
2. `wrangler.toml` + `package.json` + `pnpm-lock.yaml` + `src/index.ts` + `tsconfig.json`
|
||||
3. Push → CI 自動部署
|
||||
|
||||
---
|
||||
|
||||
## Runtime Secret 管理
|
||||
|
||||
**CI 只提供 Cloudflare 驗證,不碰 runtime secret**。
|
||||
|
||||
- GH Actions secrets:`CLOUDFLARE_API_TOKEN`、`CLOUDFLARE_ACCOUNT_ID`(一次性設好)
|
||||
- Runtime secret(例:`ENCRYPTION_KEY`、`OPENAI_KEY`、`GOOGLE_API_KEY`):
|
||||
- **由 richblack 一次性手動** `wrangler secret put <KEY>` 設進各 Worker
|
||||
- 不進 CI,不進 `wrangler.toml` `[vars]`
|
||||
- 需要的 Worker:`auth_static_key`、`auth_service_account`(兩個都要 `ENCRYPTION_KEY`)
|
||||
|
||||
---
|
||||
|
||||
## Lockfile 規範
|
||||
|
||||
- **統一使用 pnpm**。新增 Worker 只放 `pnpm-lock.yaml`,不要 `package-lock.json`
|
||||
- 若新建 Worker 時用 `npm install` 產出 `package-lock.json`,**刪掉它**,改跑 `pnpm install`
|
||||
- `cypher-executor/` 和 `registry/` 的 `package-lock.json` 已於 2026-04-20 刪除
|
||||
|
||||
**現存例外**(歷史遺產,混合期不強制遷移):
|
||||
- `.component-builds/{if_control, switch, ... 16 個舊邏輯零件}/` 仍是 `package-lock.json`,workflow 有 fallback 分支(`pnpm install --no-frozen-lockfile`)可跑
|
||||
- `builtins/`、`landing/` 同上
|
||||
|
||||
**新增 Worker 一律 pnpm,不要製造新的混合情況**。
|
||||
|
||||
---
|
||||
|
||||
## WASM 來源
|
||||
|
||||
> **⚠️ 慣例變更(richblack 2026-06-02,self-hosted 開源策略)**:
|
||||
> 原慣例「`.component-builds/{name}/component.wasm` 不 commit 進 repo」**已推翻**。
|
||||
> 現在 **commit `.component-builds/*/component.wasm` 進 repo**,因為 self-host 用戶 / `acr init --self-hosted`
|
||||
> 從 GitHub(codeload tarball)直接拿這份 wasm 部署到自己的 CF——repo 必須自帶可部署的 wasm。
|
||||
> 決策依據:`docs/3-specs/arcrun/sdk-and-website/self-hosted-init.md §6`。
|
||||
|
||||
### 現行規則(2026-06-02 起)
|
||||
|
||||
- **`.component-builds/*/component.wasm` → commit 進 repo**(部署來源)。`.gitignore` 用否定規則放行:
|
||||
```
|
||||
*.wasm # 預設排除
|
||||
!.component-builds/**/component.wasm # 例外放行部署物
|
||||
```
|
||||
- **`registry/components/*.wasm` → 仍不 commit**(build 中間產物,部署不直接用,`.gitignore` 仍排除)。
|
||||
- 本地開發 build:`cd registry/components/{name} && tinygo build -target=wasi -o {name}.wasm main.go && cp {name}.wasm ../../../.component-builds/{name}/component.wasm`,**然後 commit `.component-builds/{name}/component.wasm`**。
|
||||
- CI(deploy.yml):仍在 deploy 前自動 rebuild + copy(部署 prod 用最新 source;與 repo 內 commit 的 wasm 不衝突——前者給 CI deploy prod,後者給 self-host 用戶當部署來源)。
|
||||
|
||||
### 誠實 trade-off(mindset §7)
|
||||
|
||||
commit wasm 進 repo → 每次 rebuild 在 git 歷史累積二進位,**repo 長期會膨脹**。
|
||||
可接受(self-host 體驗優先),未來若膨脹過劇再考慮 git-lfs / 按需安裝(self-hosted-init.md §6.6)。
|
||||
|
||||
---
|
||||
|
||||
## 並行度
|
||||
|
||||
`max-parallel: 5` — 避免觸發 Cloudflare Workers API rate limit。
|
||||
|
||||
Worker 數量 > 5 時,deploy 會分批跑。25 個 Worker 大約 5 輪 × ~30 秒 = 2-3 分鐘可完成全部。
|
||||
|
||||
---
|
||||
|
||||
## 禁止事項
|
||||
|
||||
1. **禁止**為新 Worker 手動加 deploy job 到 `deploy.yml`。通用掃描會自動處理,手加就是重複工作。
|
||||
2. **禁止**把 runtime secret(API key / encryption key / credential)放進 GH Actions secrets 或 `wrangler.toml` `[vars]`,只能用 `wrangler secret put`。
|
||||
3. **禁止**在 CI 裡跑不必要的測試阻擋 deploy。測試在 PR / 本地跑,`main` 推上去就 deploy(trunk-based)。若要測試關,開新 workflow 檔,不要污染 deploy workflow。
|
||||
4. **禁止**跳過 TinyGo rebuild 直接 deploy 舊 `.wasm`。CI 的 rebuild 步驟是確保部署的是最新 source。
|
||||
|
||||
---
|
||||
|
||||
## 驗證指令
|
||||
|
||||
本地模擬 CI 的掃描結果:
|
||||
|
||||
```bash
|
||||
find . -name 'wrangler.toml' -not -path '*/node_modules/*' -not -name 'wrangler.test.toml' \
|
||||
| xargs -n1 dirname | sort -u
|
||||
```
|
||||
|
||||
應列出 ~25 個目錄。任何「我新增了 Worker 但沒被 deploy」的問題,先跑這條確認目錄被掃到。
|
||||
@@ -0,0 +1,60 @@
|
||||
# arcrun mindset(給 CC 的世界觀 — 走歪前先讀這個)
|
||||
|
||||
> 這是「為什麼」層,不是禁令層(禁令在 02-forbidden)。
|
||||
> 違反禁令會被 hook 擋;違反 mindset 不會被擋,但會讓你做出「技術上對、方向上錯」的東西。
|
||||
> 來源:2026-05 richblack 與 CC 的一連串判準釐清(權威記錄在 DECISIONS.md)。
|
||||
>
|
||||
> 註:這是步驟 7「arcrun-mindset Skill」的前身。Skill 正式建立前,本檔是 CC 的 mindset 來源。
|
||||
|
||||
---
|
||||
|
||||
## 1. 工作流是 default,零件是稀有例外
|
||||
|
||||
AI 開發時的預設順序:
|
||||
1. **預設寫工作流**(串服務 / 自用 / 給少數人用 → 全部先工作流,用 http_request + 流程控制 primitive 串)。
|
||||
2. **要打的服務不提供串接但有 API,且有必要讓全 arcrun 生態重用** → 才建零件(零件 = API 薄殼,只打一個 endpoint)。
|
||||
3. **建零件前必問「你有必要嗎?」**:看到「有 API 可包成零件」≠「該包」。自用 → 工作流。
|
||||
|
||||
**CC 的典型走歪**:把「需要一個能力」直接翻成「做一個零件」,把「能包」當「該包」。
|
||||
mira 的 claude_api / km_writer 就是這樣被錯做成零件的(其實是自用服務膠水,該是工作流)。
|
||||
|
||||
## 2. Arcrun 是 AI 呼叫的工具(AI → 工具),不是工具回頭呼叫 AI
|
||||
|
||||
需要 AI 判斷 / 轉換時,是**操盤的 CC 自己做**,再呼叫工作流做確定性的下一步。
|
||||
**不要在工作流裡放零件回頭呼叫一個 LLM**。n8n 需要 AI 節點是因為它沒大腦;arcrun 的大腦就是 CC。
|
||||
(ai_transform_compile/run 因此被刪除。)
|
||||
|
||||
## 3. arcrun 不做授權判斷
|
||||
|
||||
「能不能打通」由發 API key 的服務裁決,不是 arcrun。401/403 是對方服務在行使授權,不是 arcrun 的 bug。
|
||||
auth_recipe 只定義「怎麼認證」,不含「誰准用」清單。不要加「arcrun 替用戶擋掉某些 endpoint」的功能。
|
||||
|
||||
## 4. 零件投稿走 GitHub PR(人 merge = 人類閘門)
|
||||
|
||||
零件投稿不是 registry self-service,是 GitHub PR。人 merge = 天然人類閘門(AI 偽造不了 GitHub approve),
|
||||
把關(假零件偵測 / 純WASI / Gherkin)由 CI PR check 跑(CI 能 runtime 跑 wasm,CF Worker 不能)。
|
||||
§8「不依賴 CI」指執行鏈路(高頻);零件投稿稀有,走 PR/CI 是例外、不違反。
|
||||
|
||||
## 5. 發佈安全的底氣是純 WASI 沙箱,不是 Gherkin
|
||||
|
||||
Gherkin 全綠 ≠ 零件安全(投稿者可寫避重就輕的 Gherkin)。真正框死破壞力的是**純 WASI 沙箱**
|
||||
(零件只能 stdin→stdout、無網路 syscall、無檔案系統)。Gherkin 驗契約 + 沙箱框死 + 市場補長尾 = 風險可控,非零風險。
|
||||
|
||||
## 6. 暴露 / 送資料的動作 → 人類明示同意(資料外流警示)
|
||||
|
||||
把資料 / workflow 變成「可被外部呼叫」(部署 webhook、recipe push)= 暴露面 → 需人類明示同意,不分公私庫。
|
||||
**不禁止**用戶公開(他的自由),但要**確定他自己明示同意**(不是 AI 替他決定)。
|
||||
警示同時是「保護措施入口」(提示可加 API Key / 權限 / 限流)。
|
||||
|
||||
## 7. 誠實限制(最重要的 mindset:不假裝、不假綠)
|
||||
|
||||
- **AI 技術上能偽造人類確認**(confirmed_by_human、exposure_consent、gherkin_evidence 都能塞)。
|
||||
這些機制的價值是**法律歸責 + 軌跡可審**,不是技術防偽。**絕不在文件 / 程式裡聲稱「不可能繞過」。**
|
||||
- **絕不代替人類做有風險的確認**(建零件、暴露資料)。非 TTY(你直跑)就拒絕,不要自己塞 flag 假裝人類同意了 —— 那是明確越界。
|
||||
- **禁假綠**(DECISIONS §3c/§7):stub / 未實作就回 success:false 或明確標 unimplemented,不要回傳假資料假裝成功。
|
||||
缺 credential 打不到 2xx 就誠實標「未驗收:缺 X」,不 mock 充綠燈。
|
||||
- **完成 = 客觀證據**(編譯 exit code / HTTP status + trace),不是口頭宣布「我做好了」。
|
||||
|
||||
---
|
||||
|
||||
詳細判準與來龍去脈見 `DECISIONS.md`。每條都有對應的慘痛教訓,不是憑空規定。
|
||||
@@ -0,0 +1,133 @@
|
||||
# 薄殼原則(鐵律)— 能力長在 API,介面只暴露
|
||||
|
||||
> 來源:`docs/壓測報告.md` §5.4/§5.5(設計者本人於壓測中釐清)+ DECISIONS §1。
|
||||
> 違反此原則的典型後果:每改一個能力要同步多份介面、介面間漂移(壓測 §5.1:CLI 改了讀
|
||||
> 全域/專案/.env,MCP 沒跟上,兩者打不同帳號)、能力被某個介面綁架後別的介面用不到。
|
||||
> 這條由 `.claude/hooks/pre-write-guard.sh` 規則 7.x 部分強制(見下「hook 強制範圍」)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 一句話
|
||||
|
||||
**所有能力(business logic)只實作一次,放在 API(cypher-executor HTTP 端點)。
|
||||
CLI / MCP / Python lib / JS lib 全是薄殼:只做「介面轉換 + 暴露」,不含任何商業邏輯。**
|
||||
|
||||
```
|
||||
CLI ─┐
|
||||
MCP ─┤ ← 全是薄殼:參數解析 / 格式轉換 / 暴露,不含商業邏輯
|
||||
Python lib ─┤
|
||||
JS lib ─┘
|
||||
↓ 全部呼叫同一個
|
||||
┌──────────────────────────────┐
|
||||
│ API(唯一真相,能力都在這) │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 什麼是「能力下沉到 API」(正例 vs 反例)
|
||||
|
||||
### 正例:upsert
|
||||
- ✅ **API 提供 `upsert` 端點**(內部 GET 找→有則 update 無則 insert)。CLI/MCP/lib 只呼叫它。
|
||||
- ❌ 在 MCP 裡自製「先 call update API、失敗再 call insert API」的拼裝邏輯。
|
||||
- ❌ 在 recipe 層拼湊 upsert(recipe/零件補 API 缺的能力 = 走歪;正解是補在 API)。
|
||||
|
||||
### 正例:seed recipe(壓測 §4.1 的反例修正)
|
||||
- ✅ **API 在「部署/註冊完成」時保證 recipe 就緒**(seed 是 API 行為,由一個端點完成 `POST /init/seed`)。
|
||||
- ✅ **種子資料(清單)放 server**:`cypher-executor/src/lib/*-seeds.ts`。「裝好後預設有哪些 recipe」
|
||||
是 API 的能力,種子資料是這能力的一部分。薄殼只呼叫 `/init/seed` 一次。
|
||||
- ❌ 在 CLI `init.ts` 裡用迴圈 POST 11 個 recipe + 客戶端「全部成功才 seed」的 if 判斷
|
||||
(這正是 §4.1 seed 永遠不被 seed 的根因:邏輯被寫進了某個介面)。
|
||||
|
||||
> **種子資料檔(`*-seeds.ts`)是一個普遍類別,不是某個零件的特例。**
|
||||
> 它含 endpoint / `{{template}}` 字串(recipe 的資料欄位),rule 02 §2.2 hook 對**整類** `*-seeds.ts`
|
||||
> 豁免 endpoint/template 檢查——因為那是「資料宣告」不是「呼叫實作」。新增任何 `xxx-seeds.ts`
|
||||
> 自動適用,**不需為個別零件/recipe 改 hook**(richblack 原則:不為單一零件改全域規則)。
|
||||
|
||||
### 判準口訣
|
||||
> **「這段邏輯換一個介面(CLI→MCP)要不要重寫?」**
|
||||
> 要重寫 → 它是能力,該在 API。
|
||||
> 不用重寫(只是把 API 回傳值換個格式印出來)→ 它是薄殼該做的事。
|
||||
|
||||
---
|
||||
|
||||
## 2. 薄殼「允許」做的事(窮舉)
|
||||
|
||||
1. 解析介面慣例的輸入(CLI 吃檔案路徑 / MCP 吃 JSON 參數)→ 轉成 API 期望的 payload。
|
||||
2. 呼叫 API(HTTP fetch / service binding)。
|
||||
3. 把 API 回傳值轉成該介面的輸出格式(CLI 印彩色文字 / MCP 回 structured JSON)。
|
||||
4. **client 端加密**(AES-GCM)——唯一例外,因 API 期望收到已加密 payload(見 rule 01 加解密)。
|
||||
5. 讀取「身份設定」(哪個帳號 / 哪個 cypher URL)——但所有薄殼必須讀**同一份**身份來源
|
||||
(見 §4 統一帳號來源)。
|
||||
|
||||
## 3. 薄殼「禁止」做的事
|
||||
|
||||
1. ❌ **在薄殼介面(CLI/MCP/lib)裡用多個 API 呼叫拼裝**出一個 API 沒有的能力(upsert / seed / 任何 N-step 編排)。
|
||||
> **界線(2026-06-26 收窄,issue #4)**:禁的是「**把編排邏輯寫進介面層 TS**」,不是禁「用資料方式(workflow/code-node)自救」。
|
||||
> 自家 API 缺能力 → 補進 API(你能改);**第三方 API 缺能力 → 走 workflow/code-node 補丁是合法的**(你改不了第三方 API,不能被規則卡死)。詳見 §3.5 自力救濟階梯。
|
||||
2. ❌ **用零件**補 API 缺的能力 → 污染零件庫(缺能力 → 先看 §3.5 階梯,真需新穩定能力才走零件 PR)。
|
||||
> 這條的原始精神(要保留):當初是 AI 把多步驟工作寫成**零件**污染零件庫,才訂此禁令。**禁的是「亂建零件」,不是「禁止任何補丁」。**
|
||||
3. ❌ 寫死判斷來補 API 缺口(例:`deployFullyOk` 那種 client 端 gate)。
|
||||
4. ❌ 同一 API 能力在不同介面用不同參數簽名(`validate` 在 CLI 吃 YAML、在 MCP 卻要 `api_key`+`graph` = 底層分歧,違反「同一 API」)。差異只能來自介面慣例(檔案路徑 vs 字串),不能來自底層實作。
|
||||
5. ❌ 任一薄殼連的帳號 / 後端與別的薄殼不同(CLI 連自架、MCP 連平台 = 違反「同一 API」前提)。
|
||||
|
||||
---
|
||||
|
||||
## 3.5 缺能力時怎麼補:自力救濟階梯(普世規則,issue #4)
|
||||
|
||||
> **問題**:§3 舊版預設「缺能力 → 去補 API」,這**預設 API 是你能改的**。對**第三方 API**(如 Google Sheets:一次只能倒全部、輸出前無法 filter)不成立——若 Google 不開該 API、規則又禁用 workflow「倒出來自己篩」,用戶被自己的規則卡死。
|
||||
> **解法**:把「補丁」分層,維持零件庫最小,但開放「用資料方式自救」的合法路徑。
|
||||
|
||||
**主界線(一句話)**:**「那個 API 你能不能改?」** 自家 API 缺能力 → 補 API;第三方 API 缺能力 → workflow/code-node 補丁。
|
||||
|
||||
| 情況 | 正解 | 為何 |
|
||||
|---|---|---|
|
||||
| 能打既有 API | **recipe**(沒有就建 recipe) | 單一 API 呼叫的封裝 |
|
||||
| **自家** API(KBDB / cypher)缺能力 | **補進 API** + 可同時發 issue | 你能改,能力該長在 API |
|
||||
| **第三方** API 缺能力(gsheets filter / 無 upsert API) | **可投稿的 workflow 補丁** + 發 issue 建議原廠加 API | 你改不了第三方 API,但不能被卡死 |
|
||||
| 非 call-api 的純計算(如整篇文章轉大寫) | **code-node**(空白 code 零件內寫 JS) | recipe/workflow 都做不到,又不該為此建一堆專用零件 |
|
||||
| 真需新穩定能力(極少數) | 自建零件 → PR | 維持零件庫最小,只有非用零件不可才建 |
|
||||
|
||||
**三個配套原則**:
|
||||
1. **補丁 workflow 可像 recipe 一樣被呼叫,但明示它是 workflow、且可投稿**(呼應 wishlist C6「工作流即零件」)。讓 AI 一遇阻就「用資料方式」自救,而非建零件。
|
||||
2. **code-node**:原廠不提供某純計算時,AI 至少能用一個空白 code 零件寫 JS 自救(呼應 wishlist C1;架構決策:JS 在 CF Workers isolate 跑,不嵌 QuickJS/Rust)。
|
||||
3. **補丁是過渡**:原廠出 API 後,補丁 workflow 因效能較差自然被減少使用、淘汰。
|
||||
|
||||
**upsert 範例**:有些服務原廠提供 upsert API(→ recipe 直接打),有些沒有(→ 做一個 upsert workflow 達成,**而非建專用零件**)。§1 把 upsert 當「該補進 API」的正例——那**只對自家 API 成立**;對改不了的第三方 API,arcrun 端永遠補不進去,正解是 workflow 補丁。
|
||||
|
||||
> **與 hook 的關係**:§3.1 禁的「介面層拼裝」由 `pre-write-guard.sh` 7.x 擋(範圍 `cli/src/`、`arcrun-mcp/src/` 的 TS)。**workflow/code-node 補丁是資料產物(YAML / 空白零件內的 JS),不是介面層 TS → 本就在 hook 範圍外,合法不被擋。** hook 防線不變,本次只釐清「資料方式自救」是合法路徑。
|
||||
|
||||
---
|
||||
|
||||
## 4. 統一帳號來源(薄殼共用同一身份)
|
||||
|
||||
所有薄殼讀**同一份**身份設定:
|
||||
- self-hosted:`~/.arcrun/config.yaml` / 專案層 `.arcrun.yaml` / `ARCRUN_*`、`CLOUDFLARE_*` env(見 `config-layering.md`)。
|
||||
- standard:平台 api_key。
|
||||
|
||||
**MCP 目前的已知違反**(壓測 §5.2):MCP 用 Cloudflare service binding 焊死平台 `arcrun-cypher-executor`,
|
||||
self-hosted 用戶用 MCP 連不到自己的 cypher。修法見
|
||||
`docs/3-specs/arcrun/sdk-and-website/mcp-account-source.md`(SDD proposal)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 出貨順序(最低出貨標)
|
||||
|
||||
- **CLI + MCP 兩個薄殼先到位**(AI 偏好 MCP,故 MCP 不可長期落後),且兩者覆蓋**同一組 API 能力**。
|
||||
- Python / JS lib 隨後補。
|
||||
- 出貨順序由「介面被誰用」決定,不是由「哪個好做」決定。
|
||||
- **介面進度本來就會不一致**(薄殼模型的預期狀態)——這本身不是 bug。
|
||||
bug 是「**底層 API 能力不齊 / 介面含了不該含的邏輯 / 帳號來源不統一**」這三者。
|
||||
|
||||
---
|
||||
|
||||
## 6. hook 強制範圍(與「靠人判斷」的邊界)
|
||||
|
||||
`pre-write-guard.sh` 規則 7.x 能擋的是**語法層可偵測**的反例:
|
||||
- CLI/MCP 檔案內出現「迴圈 POST 多個 recipe」「先 update 失敗再 insert」這類拼裝 pattern 的明顯特徵 → 警告/擋。
|
||||
- 新增 `seedApiRecipes` / `seedAuthRecipes` 這類「seed 邏輯寫在介面層」的函式 → 擋(改去 API)。
|
||||
|
||||
**hook 擋不了的**(需 CC 自律 + code review):
|
||||
- 把商業邏輯藏在看似無害的 helper 裡。
|
||||
- recipe 層拼裝(recipe 是資料,hook 不解析語意)。
|
||||
→ 故本檔是 mindset,hook 是底線;兩者都不可省。誠實限制見 mindset §7(不假裝 hook「不可能繞過」)。
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/session-start-load-sdd.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/pre-write-guard.sh",
|
||||
"timeout": 5
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/wiki-secret-scan.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/pre-bash-guard.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/post-edit-remind-tasks.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/stop-check-sync.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user