feat: KBDB-graph 插件獨立 — 全面改寫成走基本盤 API(API-as-Wall)

按 leo 鐵律(2026-06-14)把插件從「直接 SQL 操作基本盤表」改寫成
「只透過基本盤 arcrun/kbdb HTTP API 讀寫」。零建表、零 migration、零 SQL。

- 新增 src/lib/kbdb-client.ts:唯一對外通道,封裝 entries/templates/records API
- 新增 src/lib/templates.ts:triplet/entity template 定義(替代建表)
- 改寫 21 個違規 action(triplet/graph/entity/search)→ 走 client,圖在插件層記憶體組裝
- 移除所有 migrations、D1/Vectorize/AI 綁定;embedding/語意搜尋歸基本盤 optional 模組
- index.ts 只掛 triplets/graph/entities/search 路由;基本盤路由歸 arcrun/kbdb
- 測試改走 mock client(純 node);裁剪 CLAUDE.md 只留 graph 插件 + 鐵律
- 修正 SDD design.md「讀現狀推翻鐵律」的錯誤判斷(共用 D1 → API-as-Wall)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 20:59:41 +08:00
commit efe8e165cf
62 changed files with 7671 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
// 搜尋路由入口 — 僅驗證參數,呼叫 actions
//
// 插件層只做基本盤 keyword 搜尋(D1 LIKE,走 GET /entries/search)。
// 語意搜尋 / embedding 屬基本盤 optional embed 模組,不在插件 → 已移除 /search/embed。
import { Hono } from 'hono';
import { z } from 'zod';
import type { Bindings, Variables } from '../types';
import { keywordSearch } from '../actions/search-query';
import { suggestKnowledge } from '../actions/search-suggest';
import { makeKbdbClient } from '../lib/kbdb-client';
const searchRoutes = new Hono<{ Bindings: Bindings; Variables: Variables }>();
const UnifiedSearchSchema = z.object({
query: z.string().min(1),
type: z.enum(['keyword', 'suggest']).optional().default('keyword'),
topK: z.number().min(1).max(20).optional(),
owner_id: z.string().optional(),
});
// 統一搜尋入口:POST /search
searchRoutes.post('/', async (c) => {
const parsed = UnifiedSearchSchema.safeParse(await c.req.json());
if (!parsed.success) return c.json({ error: parsed.error.flatten() }, 400);
const { query, type, topK, owner_id } = parsed.data;
// Namespace 讀取過濾:partner token 只能搜到自己 namespace 的資料
const namespace = c.get('namespace');
const effectiveOwner = namespace ?? owner_id;
const client = makeKbdbClient(c.env);
if (type === 'suggest') {
const result = await suggestKnowledge(client, query, topK, effectiveOwner);
return c.json(result);
}
const matches = await keywordSearch(client, query, { limit: topK, owner_id: effectiveOwner });
return c.json({ matches, count: matches.length, mode: 'keyword' });
});
export { searchRoutes };