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
+57
View File
@@ -0,0 +1,57 @@
// 列出所有唯一實體(subject object)— 零 SQL / 零 D1
// 取全部 triplet record(走基本盤 API),在記憶體統計 as_subject/as_object/total。
// GET /entities
import type { KbdbClient } from '../lib/kbdb-client';
import { queryTriplets } from './triplet-crud';
export interface EntityStat {
name: string;
as_subject: number;
as_object: number;
total: number;
}
export interface EntityListResult {
entities: EntityStat[];
total: number;
limit: number;
offset: number;
}
export async function listTripletEntities(
client: KbdbClient,
{ limit = 200, offset = 0, q }: { limit?: number; offset?: number; q?: string },
): Promise<EntityListResult> {
const safeLimit = Math.min(limit, 500);
const { triplets } = await queryTriplets(client, { limit: 2000 });
const stats = new Map<string, EntityStat>();
const bump = (name: string, role: 'as_subject' | 'as_object') => {
if (!name) return;
let s = stats.get(name);
if (!s) {
s = { name, as_subject: 0, as_object: 0, total: 0 };
stats.set(name, s);
}
s[role] += 1;
s.total += 1;
};
for (const t of triplets) {
bump(t.subject, 'as_subject');
bump(t.object, 'as_object');
}
let entities = [...stats.values()];
if (q) {
const needle = q.toLowerCase();
entities = entities.filter((e) => e.name.toLowerCase().includes(needle));
}
entities.sort((a, b) => b.total - a.total);
const total = entities.length;
const page = entities.slice(offset, offset + safeLimit);
return { entities: page, total, limit: safeLimit, offset };
}