@yun-zero/claw-memory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +68 -0
- package/README.md +323 -0
- package/dist/config/llm.d.ts +13 -0
- package/dist/config/llm.d.ts.map +1 -0
- package/dist/config/llm.js +96 -0
- package/dist/config/llm.js.map +1 -0
- package/dist/config/plugin.d.ts +15 -0
- package/dist/config/plugin.d.ts.map +1 -0
- package/dist/config/plugin.js +32 -0
- package/dist/config/plugin.js.map +1 -0
- package/dist/db/entityRepository.d.ts +21 -0
- package/dist/db/entityRepository.d.ts.map +1 -0
- package/dist/db/entityRepository.js +55 -0
- package/dist/db/entityRepository.js.map +1 -0
- package/dist/db/repository.d.ts +22 -0
- package/dist/db/repository.d.ts.map +1 -0
- package/dist/db/repository.js +77 -0
- package/dist/db/repository.js.map +1 -0
- package/dist/db/schema.d.ts +5 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +112 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/todoRepository.d.ts +26 -0
- package/dist/db/todoRepository.d.ts.map +1 -0
- package/dist/db/todoRepository.js +54 -0
- package/dist/db/todoRepository.js.map +1 -0
- package/dist/hooks/bootstrap.d.ts +3 -0
- package/dist/hooks/bootstrap.d.ts.map +1 -0
- package/dist/hooks/bootstrap.js +28 -0
- package/dist/hooks/bootstrap.js.map +1 -0
- package/dist/hooks/message.d.ts +18 -0
- package/dist/hooks/message.d.ts.map +1 -0
- package/dist/hooks/message.js +52 -0
- package/dist/hooks/message.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +360 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/plugin.d.ts +18 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +62 -0
- package/dist/plugin.js.map +1 -0
- package/dist/services/entityGraphService.d.ts +87 -0
- package/dist/services/entityGraphService.d.ts.map +1 -0
- package/dist/services/entityGraphService.js +271 -0
- package/dist/services/entityGraphService.js.map +1 -0
- package/dist/services/memory.d.ts +26 -0
- package/dist/services/memory.d.ts.map +1 -0
- package/dist/services/memory.js +281 -0
- package/dist/services/memory.js.map +1 -0
- package/dist/services/memoryIndex.d.ts +34 -0
- package/dist/services/memoryIndex.d.ts.map +1 -0
- package/dist/services/memoryIndex.js +100 -0
- package/dist/services/memoryIndex.js.map +1 -0
- package/dist/services/metadataExtractor.d.ts +16 -0
- package/dist/services/metadataExtractor.d.ts.map +1 -0
- package/dist/services/metadataExtractor.js +75 -0
- package/dist/services/metadataExtractor.js.map +1 -0
- package/dist/services/retrieval.d.ts +24 -0
- package/dist/services/retrieval.d.ts.map +1 -0
- package/dist/services/retrieval.js +40 -0
- package/dist/services/retrieval.js.map +1 -0
- package/dist/services/scheduler.d.ts +122 -0
- package/dist/services/scheduler.d.ts.map +1 -0
- package/dist/services/scheduler.js +434 -0
- package/dist/services/scheduler.js.map +1 -0
- package/dist/services/summarizer.d.ts +43 -0
- package/dist/services/summarizer.d.ts.map +1 -0
- package/dist/services/summarizer.js +252 -0
- package/dist/services/summarizer.js.map +1 -0
- package/dist/services/tagService.d.ts +64 -0
- package/dist/services/tagService.d.ts.map +1 -0
- package/dist/services/tagService.js +281 -0
- package/dist/services/tagService.js.map +1 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +114 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/types.d.ts +128 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/plans/2026-03-02-claw-memory-design.md +445 -0
- package/docs/plans/2026-03-02-incremental-summary-design.md +157 -0
- package/docs/plans/2026-03-02-incremental-summary-implementation.md +468 -0
- package/docs/plans/2026-03-02-memory-index-design.md +163 -0
- package/docs/plans/2026-03-02-memory-index-implementation.md +836 -0
- package/docs/plans/2026-03-02-mvp-implementation.md +1703 -0
- package/docs/plans/2026-03-02-testing-implementation.md +395 -0
- package/docs/plans/2026-03-02-testing-plan.md +93 -0
- package/docs/plans/2026-03-03-claw-memory-openclaw-plugin-design.md +285 -0
- package/docs/plans/2026-03-03-claw-memory-plugin-implementation.md +642 -0
- package/docs/plans/2026-03-03-entity-graph-design.md +121 -0
- package/docs/plans/2026-03-03-entity-graph-implementation.md +687 -0
- package/docs/plans/2026-03-03-llm-generic-config-design.md +43 -0
- package/docs/plans/2026-03-03-llm-generic-config-implementation.md +186 -0
- package/docs/plans/2026-03-03-memory-e2e-stress-test-design.md +110 -0
- package/docs/plans/2026-03-03-memory-e2e-stress-test-implementation.md +464 -0
- package/docs/plans/2026-03-03-minimax-llm-fix.md +156 -0
- package/docs/plans/2026-03-03-scheduler-design.md +165 -0
- package/docs/plans/2026-03-03-scheduler-implementation.md +777 -0
- package/docs/plans/2026-03-03-tags-visualization-design.md +73 -0
- package/docs/plans/2026-03-03-tags-visualization-implementation.md +539 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +41 -0
- package/src/config/llm.ts +129 -0
- package/src/config/plugin.ts +47 -0
- package/src/db/entityRepository.ts +80 -0
- package/src/db/repository.ts +106 -0
- package/src/db/schema.ts +121 -0
- package/src/db/todoRepository.ts +76 -0
- package/src/hooks/bootstrap.ts +36 -0
- package/src/hooks/message.ts +84 -0
- package/src/index.ts +50 -0
- package/src/plugin.ts +85 -0
- package/src/services/entityGraphService.ts +367 -0
- package/src/services/memory.ts +338 -0
- package/src/services/memoryIndex.ts +140 -0
- package/src/services/metadataExtractor.ts +89 -0
- package/src/services/retrieval.ts +71 -0
- package/src/services/scheduler.ts +529 -0
- package/src/services/summarizer.ts +318 -0
- package/src/services/tagService.ts +335 -0
- package/src/tools/memory.ts +137 -0
- package/src/types.ts +139 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +16 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
# 实体关系图查询实现计划
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** 实现实体关系图查询功能,包括 MCP 工具和静态 HTML 可视化
|
|
6
|
+
|
|
7
|
+
**Architecture:** 使用 BFS 算法进行多跳查询,D3.js 力导向图实现可视化,复用现有 entity_relations 表
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, D3.js, SQLite
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 准备工作
|
|
14
|
+
|
|
15
|
+
### Task 1: 创建开发分支
|
|
16
|
+
|
|
17
|
+
**Step 1: 创建并切换到新分支**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd /home/ubuntu/openclaw/claw-memory
|
|
21
|
+
git checkout -b feature/entity-graph
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Step 2: 验证分支**
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git branch --show-current
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Expected: `feature/entity-graph`
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Task 2: 创建实体关系服务类
|
|
35
|
+
|
|
36
|
+
**Files:**
|
|
37
|
+
- Create: `src/services/entityGraphService.ts`
|
|
38
|
+
|
|
39
|
+
**Step 1: 创建基础结构**
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// src/services/entityGraphService.ts
|
|
43
|
+
import { getDatabase } from '../db/schema.js';
|
|
44
|
+
|
|
45
|
+
export interface EntityNode {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
type: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface EntityEdge {
|
|
52
|
+
source: string;
|
|
53
|
+
target: string;
|
|
54
|
+
type: string;
|
|
55
|
+
weight: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface GraphData {
|
|
59
|
+
nodes: EntityNode[];
|
|
60
|
+
edges: EntityEdge[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RelationStats {
|
|
64
|
+
most_connected: Array<{ entity: string; count: number }>;
|
|
65
|
+
relation_types: Record<string, number>;
|
|
66
|
+
total_relations: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class EntityGraphService {
|
|
70
|
+
private db: ReturnType<typeof getDatabase>;
|
|
71
|
+
|
|
72
|
+
constructor() {
|
|
73
|
+
this.db = getDatabase();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Step 2: 提交**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git add src/services/entityGraphService.ts
|
|
82
|
+
git commit -m "feat: add EntityGraphService class skeleton"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Task 3: 实现 getEntityRelations 方法
|
|
88
|
+
|
|
89
|
+
**Files:**
|
|
90
|
+
- Modify: `src/services/entityGraphService.ts`
|
|
91
|
+
|
|
92
|
+
**Step 1: 实现 getEntityRelations**
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
async getEntityRelations(entityName: string): Promise<{
|
|
96
|
+
entity: string;
|
|
97
|
+
relations: Array<{ target: string; type: string; weight: number }>;
|
|
98
|
+
}> {
|
|
99
|
+
// 查找实体
|
|
100
|
+
const entity = this.db.prepare(`
|
|
101
|
+
SELECT id, name FROM entities WHERE name = ? LIMIT 1
|
|
102
|
+
`).get(entityName) as { id: string; name: string } | undefined;
|
|
103
|
+
|
|
104
|
+
if (!entity) {
|
|
105
|
+
return { entity: entityName, relations: [] };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 查询直接关联(作为 source)
|
|
109
|
+
const asSource = this.db.prepare(`
|
|
110
|
+
SELECT e.name as target, er.relation_type as type, er.weight
|
|
111
|
+
FROM entity_relations er
|
|
112
|
+
JOIN entities e ON er.target_id = e.id
|
|
113
|
+
WHERE er.source_id = ?
|
|
114
|
+
`).all(entity.id) as Array<{ target: string; type: string; weight: number }>;
|
|
115
|
+
|
|
116
|
+
// 查询直接关联(作为 target)
|
|
117
|
+
const asTarget = this.db.prepare(`
|
|
118
|
+
SELECT e.name as target, er.relation_type as type, er.weight
|
|
119
|
+
FROM entity_relations er
|
|
120
|
+
JOIN entities e ON er.source_id = e.id
|
|
121
|
+
WHERE er.target_id = ?
|
|
122
|
+
`).all(entity.id) as Array<{ target: string; type: string; weight: number }>;
|
|
123
|
+
|
|
124
|
+
// 合并结果
|
|
125
|
+
const relations = [...asSource, ...asTarget];
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
entity: entity.name,
|
|
129
|
+
relations
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Step 2: 测试运行**
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx ts-node -e "
|
|
138
|
+
import { EntityGraphService } from './src/services/entityGraphService.js';
|
|
139
|
+
const svc = new EntityGraphService();
|
|
140
|
+
svc.getEntityRelations('技术').then(r => console.log(JSON.stringify(r, null, 2))).catch(e => console.error(e));
|
|
141
|
+
"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Expected: 返回关联数据(可能为空)
|
|
145
|
+
|
|
146
|
+
**Step 3: 提交**
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
git add src/services/entityGraphService.ts
|
|
150
|
+
git commit -m "feat: implement getEntityRelations method"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Task 4: 实现 queryEntityGraph 方法(多跳查询)
|
|
156
|
+
|
|
157
|
+
**Files:**
|
|
158
|
+
- Modify: `src/services/entityGraphService.ts`
|
|
159
|
+
|
|
160
|
+
**Step 1: 实现 BFS 多跳查询**
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
async queryEntityGraph(
|
|
164
|
+
startEntity: string,
|
|
165
|
+
endEntity?: string,
|
|
166
|
+
maxHops: number = 2
|
|
167
|
+
): Promise<GraphData> {
|
|
168
|
+
// 限制跳数
|
|
169
|
+
maxHops = Math.min(Math.max(1, maxHops), 5);
|
|
170
|
+
|
|
171
|
+
// 查找起点实体
|
|
172
|
+
const start = this.db.prepare(`
|
|
173
|
+
SELECT id, name, type FROM entities WHERE name = ? LIMIT 1
|
|
174
|
+
`).get(startEntity) as { id: string; name: string; type: string } | undefined;
|
|
175
|
+
|
|
176
|
+
if (!start) {
|
|
177
|
+
return { nodes: [], edges: [] };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// BFS 查询
|
|
181
|
+
const visited = new Set<string>();
|
|
182
|
+
const nodes: EntityNode[] = [];
|
|
183
|
+
const edges: EntityEdge[] = [];
|
|
184
|
+
const queue: Array<{ id: string; name: string; type: string; hop: number }> = [];
|
|
185
|
+
|
|
186
|
+
queue.push({ id: start.id, name: start.name, type: start.type, hop: 0 });
|
|
187
|
+
visited.add(start.id);
|
|
188
|
+
nodes.push({ id: start.id, name: start.name, type: start.type });
|
|
189
|
+
|
|
190
|
+
while (queue.length > 0) {
|
|
191
|
+
const current = queue.shift()!;
|
|
192
|
+
|
|
193
|
+
if (current.hop >= maxHops) continue;
|
|
194
|
+
|
|
195
|
+
// 查询关联(作为 source)
|
|
196
|
+
const asSource = this.db.prepare(`
|
|
197
|
+
SELECT e.id, e.name, e.type, er.relation_type, er.weight
|
|
198
|
+
FROM entity_relations er
|
|
199
|
+
JOIN entities e ON er.target_id = e.id
|
|
200
|
+
WHERE er.source_id = ?
|
|
201
|
+
`).all(current.id) as Array<{ id: string; name: string; type: string; relation_type: string; weight: number }>;
|
|
202
|
+
|
|
203
|
+
// 查询关联(作为 target)
|
|
204
|
+
const asTarget = this.db.prepare(`
|
|
205
|
+
SELECT e.id, e.name, e.type, er.relation_type, er.weight
|
|
206
|
+
FROM entity_relations er
|
|
207
|
+
JOIN entities e ON er.source_id = e.id
|
|
208
|
+
WHERE er.target_id = ?
|
|
209
|
+
`).all(current.id) as Array<{ id: string; name: string; type: string; relation_type: string; weight: number }>;
|
|
210
|
+
|
|
211
|
+
const allRelations = [
|
|
212
|
+
...asSource.map(r => ({ ...r, targetId: current.id, targetName: current.name })),
|
|
213
|
+
...asTarget.map(r => ({ ...r, targetId: r.id, targetName: r.name, isReverse: true }))
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
for (const rel of allRelations) {
|
|
217
|
+
if (!visited.has(rel.id) || (endEntity && rel.name === endEntity)) {
|
|
218
|
+
if (!visited.has(rel.id)) {
|
|
219
|
+
visited.add(rel.id);
|
|
220
|
+
queue.push({ id: rel.id, name: rel.name, type: rel.type, hop: current.hop + 1 });
|
|
221
|
+
nodes.push({ id: rel.id, name: rel.name, type: rel.type });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 添加边
|
|
225
|
+
edges.push({
|
|
226
|
+
source: current.name,
|
|
227
|
+
target: rel.name,
|
|
228
|
+
type: rel.relation_type,
|
|
229
|
+
weight: rel.weight
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// 如果找到终点,提前结束
|
|
233
|
+
if (endEntity && rel.name === endEntity) {
|
|
234
|
+
return { nodes, edges };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { nodes, edges };
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Step 2: 测试运行**
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npx ts-node -e "
|
|
248
|
+
import { EntityGraphService } from './src/services/entityGraphService.js';
|
|
249
|
+
const svc = new EntityGraphService();
|
|
250
|
+
svc.queryEntityGraph('技术', undefined, 2).then(r => console.log('Nodes:', r.nodes.length, 'Edges:', r.edges.length)).catch(e => console.error(e));
|
|
251
|
+
"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Step 3: 提交**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
git add src/services/entityGraphService.ts
|
|
258
|
+
git commit -m "feat: implement queryEntityGraph method with BFS"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Task 5: 实现 getRelationStats 方法
|
|
264
|
+
|
|
265
|
+
**Files:**
|
|
266
|
+
- Modify: `src/services/entityGraphService.ts`
|
|
267
|
+
|
|
268
|
+
**Step 1: 实现 getRelationStats**
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
async getRelationStats(): Promise<RelationStats> {
|
|
272
|
+
// 关联最多的实体
|
|
273
|
+
const mostConnected = this.db.prepare(`
|
|
274
|
+
SELECT e.name as entity, COUNT(*) as count
|
|
275
|
+
FROM entity_relations er
|
|
276
|
+
JOIN entities e ON er.source_id = e.id OR er.target_id = e.id
|
|
277
|
+
GROUP BY e.id
|
|
278
|
+
ORDER BY count DESC
|
|
279
|
+
LIMIT 10
|
|
280
|
+
`).all() as Array<{ entity: string; count: number }>;
|
|
281
|
+
|
|
282
|
+
// 关系类型分布
|
|
283
|
+
const typeStats = this.db.prepare(`
|
|
284
|
+
SELECT relation_type, COUNT(*) as count
|
|
285
|
+
FROM entity_relations
|
|
286
|
+
GROUP BY relation_type
|
|
287
|
+
`).all() as Array<{ relation_type: string; count: number }>;
|
|
288
|
+
|
|
289
|
+
const relation_types: Record<string, number> = {};
|
|
290
|
+
for (const s of typeStats) {
|
|
291
|
+
relation_types[s.relation_type] = s.count;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 总关系数
|
|
295
|
+
const total = this.db.prepare(`
|
|
296
|
+
SELECT COUNT(*) as count FROM entity_relations
|
|
297
|
+
`).get() as { count: number };
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
most_connected: mostConnected,
|
|
301
|
+
relation_types,
|
|
302
|
+
total_relations: total.count
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Step 2: 测试运行**
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
npx ts-node -e "
|
|
311
|
+
import { EntityGraphService } from './src/services/entityGraphService.js';
|
|
312
|
+
const svc = new EntityGraphService();
|
|
313
|
+
svc.getRelationStats().then(r => console.log(JSON.stringify(r, null, 2))).catch(e => console.error(e));
|
|
314
|
+
"
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Step 3: 提交**
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
git add src/services/entityGraphService.ts
|
|
321
|
+
git commit -m "feat: implement getRelationStats method"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Task 6: 添加 MCP 工具
|
|
327
|
+
|
|
328
|
+
**Files:**
|
|
329
|
+
- Modify: `src/mcp/tools.ts`
|
|
330
|
+
|
|
331
|
+
**Step 1: 添加 get_entity_relations**
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
{
|
|
335
|
+
name: 'get_entity_relations',
|
|
336
|
+
description: 'Get direct relations of an entity',
|
|
337
|
+
inputSchema: {
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {
|
|
340
|
+
entity_name: { type: 'string', description: 'Entity name to query' }
|
|
341
|
+
},
|
|
342
|
+
required: ['entity_name']
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Step 2: 添加 query_entity_graph**
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
{
|
|
351
|
+
name: 'query_entity_graph',
|
|
352
|
+
description: 'Query entity graph with multi-hop traversal',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: 'object',
|
|
355
|
+
properties: {
|
|
356
|
+
start_entity: { type: 'string', description: 'Start entity name' },
|
|
357
|
+
end_entity: { type: 'string', description: 'End entity name (optional)' },
|
|
358
|
+
max_hops: { type: 'number', description: 'Max hops (default 2, max 5)', default: 2 }
|
|
359
|
+
},
|
|
360
|
+
required: ['start_entity']
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Step 3: 添加 get_relation_stats**
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
{
|
|
369
|
+
name: 'get_relation_stats',
|
|
370
|
+
description: 'Get relationship statistics',
|
|
371
|
+
inputSchema: {
|
|
372
|
+
type: 'object',
|
|
373
|
+
properties: {}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Step 4: 实现工具处理函数**
|
|
379
|
+
|
|
380
|
+
在工具处理逻辑中添加:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
case 'get_entity_relations': {
|
|
384
|
+
const result = await entityGraphService.getEntityRelations(args.entity_name);
|
|
385
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
case 'query_entity_graph': {
|
|
389
|
+
const result = await entityGraphService.queryEntityGraph(
|
|
390
|
+
args.start_entity,
|
|
391
|
+
args.end_entity,
|
|
392
|
+
args.max_hops || 2
|
|
393
|
+
);
|
|
394
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
case 'get_relation_stats': {
|
|
398
|
+
const result = await entityGraphService.getRelationStats();
|
|
399
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Step 5: 提交**
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
git add src/mcp/tools.ts
|
|
407
|
+
git commit -m "feat: add entity graph MCP tools"
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Task 7: 实现 HTML 可视化生成器
|
|
413
|
+
|
|
414
|
+
**Files:**
|
|
415
|
+
- Modify: `src/services/entityGraphService.ts`
|
|
416
|
+
|
|
417
|
+
**Step 1: 添加 generateGraphHtml 方法**
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
generateGraphHtml(graph: GraphData): string {
|
|
421
|
+
const nodesJson = JSON.stringify(graph.nodes.map(n => ({
|
|
422
|
+
id: n.name,
|
|
423
|
+
group: n.type
|
|
424
|
+
})));
|
|
425
|
+
|
|
426
|
+
const linksJson = JSON.stringify(graph.edges.map(e => ({
|
|
427
|
+
source: e.source,
|
|
428
|
+
target: e.target,
|
|
429
|
+
type: e.type,
|
|
430
|
+
value: e.weight
|
|
431
|
+
})));
|
|
432
|
+
|
|
433
|
+
return `<!DOCTYPE html>
|
|
434
|
+
<html>
|
|
435
|
+
<head>
|
|
436
|
+
<meta charset="UTF-8">
|
|
437
|
+
<title>实体关系图 - ${graph.nodes.length} 节点</title>
|
|
438
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
439
|
+
<style>
|
|
440
|
+
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
441
|
+
#graph { width: 100vw; height: 100vh; }
|
|
442
|
+
.node { cursor: pointer; }
|
|
443
|
+
.node circle { stroke: #fff; stroke-width: 2px; }
|
|
444
|
+
.node text { font-size: 12px; }
|
|
445
|
+
.link { stroke: #999; stroke-opacity: 0.6; }
|
|
446
|
+
.tooltip { position: absolute; background: white; padding: 8px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
|
|
447
|
+
#info { position: absolute; top: 10px; left: 10px; background: white; padding: 10px; border-radius: 4px; }
|
|
448
|
+
</style>
|
|
449
|
+
</head>
|
|
450
|
+
<body>
|
|
451
|
+
<div id="info">
|
|
452
|
+
<h3>实体关系图</h3>
|
|
453
|
+
<p>节点: ${graph.nodes.length}</p>
|
|
454
|
+
<p>边: ${graph.edges.length}</p>
|
|
455
|
+
</div>
|
|
456
|
+
<div id="graph"></div>
|
|
457
|
+
<script>
|
|
458
|
+
const nodes = ${nodesJson};
|
|
459
|
+
const links = ${linksJson};
|
|
460
|
+
|
|
461
|
+
const width = window.innerWidth;
|
|
462
|
+
const height = window.innerHeight;
|
|
463
|
+
|
|
464
|
+
const color = d3.scaleOrdinal(d3.schemeCategory10);
|
|
465
|
+
|
|
466
|
+
const simulation = d3.forceSimulation(nodes)
|
|
467
|
+
.force("link", d3.forceLink(links).id(d => d.id).distance(100))
|
|
468
|
+
.force("charge", d3.forceManyBody().strength(-300))
|
|
469
|
+
.force("center", d3.forceCenter(width / 2, height / 2));
|
|
470
|
+
|
|
471
|
+
const svg = d3.select("#graph").append("svg")
|
|
472
|
+
.attr("width", width)
|
|
473
|
+
.attr("height", height)
|
|
474
|
+
.call(d3.zoom().on("zoom", (event) => {
|
|
475
|
+
g.attr("transform", event.transform);
|
|
476
|
+
}));
|
|
477
|
+
|
|
478
|
+
const g = svg.append("g");
|
|
479
|
+
|
|
480
|
+
const link = g.append("g")
|
|
481
|
+
.selectAll("line")
|
|
482
|
+
.data(links)
|
|
483
|
+
.enter().append("line")
|
|
484
|
+
.attr("class", "link")
|
|
485
|
+
.attr("stroke-width", d => Math.sqrt(d.value) * 2);
|
|
486
|
+
|
|
487
|
+
const node = g.append("g")
|
|
488
|
+
.selectAll("g")
|
|
489
|
+
.data(nodes)
|
|
490
|
+
.enter().append("g")
|
|
491
|
+
.attr("class", "node")
|
|
492
|
+
.call(d3.drag()
|
|
493
|
+
.on("start", dragstarted)
|
|
494
|
+
.on("drag", dragged)
|
|
495
|
+
.on("end", dragended));
|
|
496
|
+
|
|
497
|
+
node.append("circle")
|
|
498
|
+
.attr("r", 15)
|
|
499
|
+
.attr("fill", d => color(d.group));
|
|
500
|
+
|
|
501
|
+
node.append("text")
|
|
502
|
+
.text(d => d.id)
|
|
503
|
+
.attr("x", 18)
|
|
504
|
+
.attr("y", 4);
|
|
505
|
+
|
|
506
|
+
node.on("click", (event, d) => {
|
|
507
|
+
const connected = links.filter(l => l.source.id === d.id || l.target.id === d.id);
|
|
508
|
+
alert("关联: " + connected.map(l => l.source.id + " - " + l.target.id).join(", "));
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
simulation.on("tick", () => {
|
|
512
|
+
link
|
|
513
|
+
.attr("x1", d => d.source.x)
|
|
514
|
+
.attr("y1", d => d.source.y)
|
|
515
|
+
.attr("x2", d => d.target.x)
|
|
516
|
+
.attr("y2", d => d.target.y);
|
|
517
|
+
|
|
518
|
+
node.attr("transform", d => "translate(" + d.x + "," + d.y + ")");
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
function dragstarted(event, d) {
|
|
522
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
523
|
+
d.fx = d.x;
|
|
524
|
+
d.fy = d.y;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function dragged(event, d) {
|
|
528
|
+
d.fx = event.x;
|
|
529
|
+
d.fy = event.y;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function dragended(event, d) {
|
|
533
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
534
|
+
d.fx = null;
|
|
535
|
+
d.fy = null;
|
|
536
|
+
}
|
|
537
|
+
</script>
|
|
538
|
+
</body>
|
|
539
|
+
</html>`;
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**Step 2: 添加 generateStatsHtml 方法**
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
generateStatsHtml(stats: RelationStats): string {
|
|
547
|
+
return `<!DOCTYPE html>
|
|
548
|
+
<html>
|
|
549
|
+
<head>
|
|
550
|
+
<meta charset="UTF-8">
|
|
551
|
+
<title>关系统计</title>
|
|
552
|
+
<style>
|
|
553
|
+
body { font-family: -apple-system, sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }
|
|
554
|
+
h1, h2 { color: #333; }
|
|
555
|
+
.card { background: #f5f5f5; padding: 20px; margin: 10px 0; border-radius: 8px; }
|
|
556
|
+
.bar { background: #4a90d9; color: white; padding: 10px; margin: 5px 0; border-radius: 4px; }
|
|
557
|
+
table { width: 100%; border-collapse: collapse; }
|
|
558
|
+
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
|
|
559
|
+
</style>
|
|
560
|
+
</head>
|
|
561
|
+
<body>
|
|
562
|
+
<h1>实体关系统计</h1>
|
|
563
|
+
|
|
564
|
+
<div class="card">
|
|
565
|
+
<h2>总关系数</h2>
|
|
566
|
+
<p style="font-size: 36px; font-weight: bold; color: #4a90d9;">${stats.total_relations}</p>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
<div class="card">
|
|
570
|
+
<h2>关联最多的实体</h2>
|
|
571
|
+
<table>
|
|
572
|
+
<tr><th>实体</th><th>关联数</th></tr>
|
|
573
|
+
${stats.most_connected.map(m => `<tr><td>${m.entity}</td><td>${m.count}</td></tr>`).join('')}
|
|
574
|
+
</table>
|
|
575
|
+
</div>
|
|
576
|
+
|
|
577
|
+
<div class="card">
|
|
578
|
+
<h2>关系类型分布</h2>
|
|
579
|
+
${Object.entries(stats.relation_types).map(([type, count]) =>
|
|
580
|
+
`<div class="bar" style="width: ${count * 10}px">${type}: ${count}</div>`
|
|
581
|
+
).join('')}
|
|
582
|
+
</div>
|
|
583
|
+
</body>
|
|
584
|
+
</html>`;
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**Step 3: 提交**
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
git add src/services/entityGraphService.ts
|
|
592
|
+
git commit -m "feat: add HTML generators for entity graph visualization"
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## Task 8: 添加 CLI 命令
|
|
598
|
+
|
|
599
|
+
**Files:**
|
|
600
|
+
- Modify: `src/index.ts`
|
|
601
|
+
|
|
602
|
+
**Step 1: 导入 EntityGraphService**
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
import { EntityGraphService } from './services/entityGraphService.js';
|
|
606
|
+
import { writeFile } from 'fs/promises';
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**Step 2: 添加 relations 子命令**
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
.command('relations <action>')
|
|
613
|
+
.description('实体关系命令')
|
|
614
|
+
.option('-e, --entity <name>', '实体名称')
|
|
615
|
+
.option('-o, --output <file>', '输出文件')
|
|
616
|
+
.option('--hops <n>', '最大跳数', '2')
|
|
617
|
+
.action(async (action, options) => {
|
|
618
|
+
const svc = new EntityGraphService();
|
|
619
|
+
|
|
620
|
+
if (action === 'graph') {
|
|
621
|
+
const graph = await svc.queryEntityGraph(
|
|
622
|
+
options.entity || '技术',
|
|
623
|
+
undefined,
|
|
624
|
+
parseInt(options.hops)
|
|
625
|
+
);
|
|
626
|
+
const html = svc.generateGraphHtml(graph);
|
|
627
|
+
const output = options.output || 'entity-graph.html';
|
|
628
|
+
await writeFile(output, html);
|
|
629
|
+
console.log(`关系图已生成: ${output}`);
|
|
630
|
+
} else if (action === 'stats') {
|
|
631
|
+
const stats = await svc.getRelationStats();
|
|
632
|
+
const html = svc.generateStatsHtml(stats);
|
|
633
|
+
const output = options.output || 'relation-stats.html';
|
|
634
|
+
await writeFile(output, html);
|
|
635
|
+
console.log(`统计数据已生成: ${output}`);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**Step 3: 测试编译**
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
npm run build
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**Step 4: 提交**
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
git add src/index.ts
|
|
650
|
+
git commit -m "feat: add relations CLI commands"
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## Task 9: 最终测试
|
|
656
|
+
|
|
657
|
+
**Step 1: 测试 CLI 命令**
|
|
658
|
+
|
|
659
|
+
```bash
|
|
660
|
+
node dist/index.js relations graph -e "技术" --hops 2 -o /tmp/test-graph.html
|
|
661
|
+
ls -la /tmp/test-graph.html
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**Step 2: 测试 stats 命令**
|
|
665
|
+
|
|
666
|
+
```bash
|
|
667
|
+
node dist/index.js relations stats -o /tmp/test-stats.html
|
|
668
|
+
ls -la /tmp/test-stats.html
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## Task 10: 合并到主分支
|
|
674
|
+
|
|
675
|
+
**Step 1: 切换到主分支并合并**
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
git checkout main
|
|
679
|
+
git merge feature/entity-graph
|
|
680
|
+
git push origin main
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
**Step 2: 删除功能分支(可选)**
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
git branch -d feature/entity-graph
|
|
687
|
+
```
|