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
+65
View File
@@ -0,0 +1,65 @@
// 插件用到的基本盤 template 定義(= 替代建表)。
// 鐵律:插件新類型只能建 template,不建表。這裡集中宣告 slot schema
// 任何寫入前先 client.ensureTemplate 確保存在。
import type { KbdbClient } from './kbdb-client';
import type { Triplet, Entity } from '../types';
import type { BaseRecord } from './kbdb-client';
export const TPL_TRIPLET = 'triplet';
export const TPL_ENTITY = 'entity';
export const TPL_ENTITY_PENDING = 'entity_pending';
export const TRIPLET_SLOTS = [
'subject', 'predicate', 'object', 'source_block_id',
'confidence', 'clusters_json', 'bridge_score',
'subject_entity_type', 'object_entity_type',
];
export const ENTITY_SLOTS = ['canonical', 'aliases_json', 'entity_type', 'owner'];
export const ENTITY_PENDING_SLOTS = [
'raw_name', 'candidate_entity_id', 'candidate_canonical', 'similarity',
];
/** 確保插件三個 template 存在(idempotent,走 API)。 */
export async function ensurePluginTemplates(client: KbdbClient): Promise<void> {
await client.ensureTemplate(TPL_TRIPLET, TRIPLET_SLOTS, 'knowledge graph triplet (S-P-O)');
await client.ensureTemplate(TPL_ENTITY, ENTITY_SLOTS, 'normalized entity (canonical + aliases)');
await client.ensureTemplate(TPL_ENTITY_PENDING, ENTITY_PENDING_SLOTS, 'pending entity alias for review');
}
/** 基本盤 record → 插件 Triplet 型別。 */
export function recordToTriplet(rec: BaseRecord): Triplet {
const v = rec.values;
return {
id: rec.record_id,
subject: v.subject ?? '',
predicate: v.predicate ?? '',
object: v.object ?? '',
source_block_id: v.source_block_id ?? null,
confidence: parseFloat(v.confidence ?? '1.0'),
clusters: safeArr(v.clusters_json),
bridge_score: parseInt(v.bridge_score ?? '0', 10),
subject_entity_type: (v.subject_entity_type as Triplet['subject_entity_type']) || null,
object_entity_type: (v.object_entity_type as Triplet['object_entity_type']) || null,
created_at: 0,
updated_at: 0,
};
}
/** 基本盤 record → 插件 Entity 型別。 */
export function recordToEntity(rec: BaseRecord): Entity {
return {
id: rec.record_id,
canonical: rec.values.canonical ?? '',
aliases: safeArr(rec.values.aliases_json),
};
}
function safeArr(json?: string): string[] {
try {
const p = JSON.parse(json || '[]');
return Array.isArray(p) ? p : [];
} catch {
return [];
}
}