# CLAUDE.md — KBDB-graph 插件開發規範 > **上游約束(InkStoneCo 總管)**:此目錄(原 matrix/kbdb,已改名 kbdb-graph-plugin)是 InkStoneCo 子專案,受頂層知識庫約束。 > 動工前讀 `github.com/uncle6me-web/InkStoneCo` 的 CLAUDE.md + `docs/3-specs/matrix-rearrange/`。 > **定位(leo 2026-06-13)**:此 repo = **KBDB-graph 插件**(triplet 採集 + graph 查詢),類比 **Apache AGE 之於 Postgres**。基本盤 = `arcrun/kbdb`(D1 三表 + CRUD API),**不在這裡、不動它**。 > **本目錄專屬交棒見 `docs/HANDOFF-kbdb-plugin.md`**,SDD 見 `system-dev/docs/3-specs/`(2026-07-17 已從舊 `docs/3-specs/` 遷移統一)。 > 本檔案由 Claude Code 自動讀取。所有在此目錄下的開發必須遵守以下規則。 --- ## 📐 SDD 生命週期鐵律(leo 2026-07-17 拍板,全文見 `system-dev/docs/3-specs/SDD-LIFECYCLE.md`) 1. **單一活性**:任何時刻整個 repo 最多一份 `status: active` 的 SDD,所有開發任務唯一對應它的 tasks;找不到對應任務 → 停下來問,不准直接做。 2. **禁止自行建立 SDD**:澄清問題→回答不動文件;任務層變更→更新現行 SDD 的 tasks(標日期原因);規格層變更→走第 3 條。 3. **規格變更只有一條路**:proposal 寫進 `system-dev/docs/3-specs/pending-changes.md`(摘要+原因+影響分析)→ **停止**等使用者「confirm」。 4. **開新 SDD 的唯一時機**:confirm 後——先把舊 SDD 未完成任務逐條搬入新 SDD(做完前不准寫 code)→ 舊的標 closed + superseded_by 移入 `archive/` → 列搬移/作廢清單請最終確認。 5. **每次 session 開始**:讀 active SDD 與 pending-changes.md,回報三個數字(現行規格〈名稱〉/未完成任務 N/待裁決 proposal M)。 > **本 repo 現況(2026-07-17)**:**0 份 active**(無現行開發,實作面已收斂、剩跨 repo 接通)——0 份 active 合法。三份 `paused`(ingest-contract / kbdb-graph-extraction / plugin-install,等基本盤部署/跨 repo 接通),兩份 `closed` 入 `archive/`。**重啟開發時先挑一份升 active**(一份、只能一份),再動 code——sdd-guard hook 會擋 0-active 下的 code 寫入。 --- ## 🔒 KBDB 鐵律(leo 2026-06-14 拍板,最高原則,違反會被 hook exit 2 擋下) 決策全文:`InkStoneCo/docs/3-specs/matrix-rearrange/DECISION-kbdb-v3-baseplane.md`。 1. **任何人不准動表** — 禁 `CREATE/ALTER/DROP TABLE`。那 3 表只有基本盤維護者(leo)能改。 2. **插件不准直接接觸表** — 禁 `SELECT/INSERT/UPDATE/DELETE`、禁 `JOIN`、禁 `.prepare(...sql...)`、禁綁 D1/Vectorize/AI。 3. **讀寫全走基本盤 API/CLI/MCP** — 插件與 AI/人同一條路(薄殼原則)。base URL = `KBDB_BASE_URL` env var。 4. **新資料類型 = 建 template + 填 slot,永不建表** — triplet=`template='triplet'`、entity=`template='entity'`,走 `POST /templates`+`POST /records`。 5. **零 migration、零 SQL** — 插件目錄無 `migrations/`。SQL 只存在於基本盤 worker 內部。 > 比 AGE-on-Postgres 更嚴:AGE 能讀 Postgres 表,KBDB 插件連表都不許碰,必須透過基本盤 API。真正的 API-as-Wall。 > **誠實限制**:hook 擋語法層明顯 SQL;藏在 helper 裡的繞道擋不了 → 文檔(本檔)+ hook 都不可省。想到「建表」時只准 template/slot。 ### 掛載架構 ``` 基本盤 arcrun/kbdb(不動) KBDB-graph 插件(本 repo) ─ entries / templates / entry_values ─ triplet template 定義 + graph 查詢函式 ─ CRUD API: ─ 寫 triplet → POST /records (template=triplet) POST /entries POST /templates ─ 查圖 → GET /records/by-template/triplet POST /records GET /entries/search → 插件層【記憶體】組鄰接表跑圖演算法 GET /records/by-template/:tpl ─ entity 正規化 → template='entity'(exact match) ─ 唯一對外通道 = src/lib/kbdb-client.ts ``` 基本盤 API 契約詳見 `system-dev/docs/3-specs/kbdb-graph-extraction/design.md`。 **基本盤缺口**(base 無 `PUT/DELETE /records/:id`、無 vectorize 語意搜尋)標 `[→arcrun]`,不得為此自建表。embedding/語意搜尋屬基本盤 optional embed 模組,**不是插件職責**。 --- ## 🔴 第一鐵律:wiki 是判準,不准跳過(2026-07-20/21 leo 兩度點破) **要查任何東西之前,先搜尋 wiki——用 grep,不是只讀開頭幾行。** > leo:「花很多力氣去產生 wiki,最重要的就是要可以查詢,**結果要查的時候就跳過,那就白寫了**。」 > 「重點是你自己的記憶對嗎?而你有按照規定去切實讀 wiki 嗎?」 ```bash grep -rin "<本題關鍵字>" system-dev/wiki/ ``` **三條硬規則**: 1. **wiki 與程式碼/歷史文件衝突 → 以 wiki 為準**。程式碼反映「還沒清乾淨」,不等於「還在用」。 2. wiki 寫「不可動/待廢除/進行中」→ **讀它的解除條件並逐條核對**。那是當時狀態,不是永久禁令。 3. 翻原文後得到新結論 → **回頭更新 wiki**(wiki 過時是債,要還)。 **動外部系統(部署/curl/wrangler/acr/gh)前**:先找 repo 有沒有**現成腳本或 README 部署段**, 別自創方法。(實例:2026-07-21 明明有 `npx wrangler deploy` 這條驗過的路,卻自己 curl 硬幹踩坑。) > hook `wiki-first-search.sh` 會在你查 code/下高風險指令時自動推 wiki 命中行; > **但機制只是提醒,判斷是你的責任**。 ## Wiki 讀取順序(LLM 記憶系統,CC 維護) | 檔案 | 時機 | 用途 | |------|------|------| | `system-dev/wiki/status.md` | session 開始第一件事 | 當前進度、下一步 | | `system-dev/wiki/mistakes.md` | 做新功能前 | 已知誤解、避坑清單 | | `system-dev/wiki/decisions-summary.md` | 設計判斷時 | 架構決策摘要 | 文件分類規則見 `docs/README.md`;SDD 在 `system-dev/docs/3-specs/[子系統]/`(design.md + tasks.md),動手前必須有這兩個檔案,且遵守上方生命週期鐵律。 session 結束用 `/wiki-update` 更新 status.md,被糾正後 `/wiki-capture` 寫進 mistakes.md。 --- ## 樂高法硬性限制(Layer 1) - `src/actions/` 目錄下的檔案**嚴禁超過 100 行**,建議 50-80 行 - **一檔一事**:每個 action 只做一件具體的事 - **無狀態**:Action 不保存記憶體狀態,所有狀態透過參數傳遞或走基本盤 API 持久化(**不碰 D1/Vectorize**) - Route 檔案(`src/routes/`)**不含業務邏輯**,僅驗證參數 + `makeKbdbClient(c.env)` + 呼叫 action ## 目錄結構 ``` src/ ├── lib/ ← kbdb-client.ts(唯一對外 API 通道)、templates.ts(插件 template 定義) ├── actions/ ← 核心業務邏輯(純函數,< 100 行,第一參數收 KbdbClient) ├── routes/ ← HTTP 入口(Hono route,只做驗證 + 呼叫 action) ├── types.ts ← 型別定義(= contracts) └── index.ts ← Worker 進入點(只掛 triplets/graph/entities/search 路由) ``` ## 開發流程 1. 在 `types.ts` 定義輸入/輸出型別 2. 在 `tests/` 寫測試(走 `tests/mock-client.ts` 的 mock KbdbClient,不打真網路) 3. 在 `actions/` 實作邏輯(透過 `KbdbClient` 讀寫,零 SQL) 4. 在 `routes/` 建立 HTTP 入口 --- ## 技術棧 - **Framework**: Hono(OpenAPIHono) - **資料層**: 全走基本盤 arcrun/kbdb HTTP API(`KBDB_BASE_URL`)。**插件本身無 D1/Vectorize/AI 綁定。** - **Validation**: Zod - **Testing**: Vitest(純 node + mock client) - **部署**: wrangler 直推 Cloudflare,**不開 Actions**(避免再被 flag,見頂層鐵律) > 2026-06-14:按 leo 鐵律改寫完成。21 個違規直接 SQL action 全改走基本盤 API;刪除所有 migrations(插件零建表);移除 D1/Vectorize/AI 綁定。基本盤規範歸 `arcrun/kbdb`。 --- ## 安裝與 KBDB_BASE_URL(安裝契約,leo 2026-06-14) **KBDB_BASE_URL 由「安裝時的 AI」自動填,不是人填。** `wrangler.toml` 留 `""`,因為它因部署而異(每個 self-hosted 用戶的 workers.dev subdomain 不同)。值不寫死、不叫人填、不放 .env。 ### URL 是確定性的,AI 必然查得到 - URL = `https://arcrun-kbdb..workers.dev` - `arcrun-kbdb` 固定(基本盤 worker name);`` 用 CF API `GET /accounts/{id}/workers/subdomain` 查(用戶已登入 wrangler → 查得到)。 - **「能 deploy 就能查 URL」**:deploy 與查 subdomain 用同一套 CF 憑證,不存在「能裝卻查不到」。 - 一律連 workers.dev(預設),**不要求用戶自訂域名**(自訂是進階選項,非安裝必填 → 安裝時不問人)。 ### 安裝流程(`scripts/install.sh`,AI 照跑;參考 arcrun `docs/3-specs/arcrun/sdk-and-website/self-hosted-init.md`) 用戶給 github 網址 → AI: 1. `wrangler whoami` → account_id(已登入用戶 CF) 2. `GET /accounts/{id}/workers/subdomain` → 拼基本盤 URL 3. `wrangler secret put KBDB_BASE_URL`(部署)— 不寫進 git 4. `wrangler deploy` 插件 → **用戶全程零填寫、零 toml、零 .env**。 ## 測試的 base URL(兩層,分開放) | 測試 | base URL | 放哪 | |---|---|---| | **單元測試** `tests/*.test.ts` | 不需(走 `tests/mock-client.ts`,不打網路) | KBDB_BASE_URL 留空照跑。日常主力。 | | **整合測試**(驗證打通基本盤) | 本地起基本盤:`cd ../arcrun/kbdb && wrangler dev`(如 `http://localhost:8787`) | `.dev.vars`(gitignore,**不進版控**)寫 `KBDB_BASE_URL=http://localhost:8787` | **為何不用 .env**:這是 CF Worker,wrangler 本地讀 `.dev.vars` 不是 `.env`。本地測試值→`.dev.vars`;部署→`wrangler secret`;toml 只留空占位。三者各司其職。