@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,73 @@
|
|
|
1
|
+
# 层级标签管理工具设计
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
为 Claw-Memory 实现层级标签可视化管理工具,生成静态 HTML 报告展示标签树形结构和统计信息。
|
|
6
|
+
|
|
7
|
+
## 需求
|
|
8
|
+
|
|
9
|
+
1. CLI 命令生成标签树和统计 HTML 报告
|
|
10
|
+
2. 标签树支持折叠展开
|
|
11
|
+
3. 显示标签使用统计、层级分布、最近使用
|
|
12
|
+
|
|
13
|
+
## CLI 命令设计
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 生成标签树 HTML 报告(默认 output: tags-tree.html)
|
|
17
|
+
claw-memory tags tree [--output <file>]
|
|
18
|
+
|
|
19
|
+
# 生成标签统计 HTML 报告(默认 output: tags-stats.html)
|
|
20
|
+
claw-memory tags stats [--output <file>]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 标签树 HTML 结构
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<!-- 可折叠的树形结构 -->
|
|
27
|
+
<div class="tag-tree">
|
|
28
|
+
<div class="tag-item" data-level="0">
|
|
29
|
+
<span class="tag-toggle">▶</span>
|
|
30
|
+
<span class="tag-name">技术</span>
|
|
31
|
+
<span class="tag-count">(15条记忆, 20次使用)</span>
|
|
32
|
+
<div class="tag-children">
|
|
33
|
+
<!-- 子标签 -->
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 标签统计 HTML 结构
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<!-- 使用频率柱状图 -->
|
|
43
|
+
<div class="chart-usage">
|
|
44
|
+
<div class="bar" style="width: 80%">技术 (20)</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- 层级分布饼图 -->
|
|
48
|
+
<div class="chart-levels">
|
|
49
|
+
<div class="pie-segment" data-level="0">Level 0: 10</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- 最近使用列表 -->
|
|
53
|
+
<div class="recent-tags">
|
|
54
|
+
<li>React - 2026-03-03</li>
|
|
55
|
+
</div>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 数据来源
|
|
59
|
+
|
|
60
|
+
使用现有的 EntityRepository 查询:
|
|
61
|
+
- `type = 'tag'` 的实体
|
|
62
|
+
- 通过 `parent_id` 构建层级关系
|
|
63
|
+
- 聚合 `memory_entities` 计算使用次数
|
|
64
|
+
|
|
65
|
+
## 实现方式
|
|
66
|
+
|
|
67
|
+
- 使用现有 `EntityRepository` 查询数据
|
|
68
|
+
- 纯 HTML + 内联 CSS + 少量 JS
|
|
69
|
+
- 单文件输出,无需服务器
|
|
70
|
+
|
|
71
|
+
## 新增依赖
|
|
72
|
+
|
|
73
|
+
无(使用 Node.js 内置功能)
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
# 层级标签可视化实现计划
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** 实现 CLI 命令生成静态 HTML 报告,展示标签树形结构和统计信息
|
|
6
|
+
|
|
7
|
+
**Architecture:** 使用现有 EntityRepository 查询标签数据,生成内联 CSS + 少量 JS 的单文件 HTML
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Node.js 内置功能(无新依赖)
|
|
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/tags-visualization
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Step 2: 验证分支**
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git branch --show-current
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Expected: `feature/tags-visualization`
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Task 2: 读取现有 EntityRepository
|
|
35
|
+
|
|
36
|
+
**Files:**
|
|
37
|
+
- Read: `src/db/entityRepository.ts`
|
|
38
|
+
|
|
39
|
+
**Step 1: 查看现有方法**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cat src/db/entityRepository.ts
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
了解现有的查询方法,特别是:
|
|
46
|
+
- `findChildren(parentId)` - 查询子实体
|
|
47
|
+
- `findByType(type)` - 按类型查询
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Task 3: 创建标签服务类
|
|
52
|
+
|
|
53
|
+
**Files:**
|
|
54
|
+
- Create: `src/services/tagService.ts`
|
|
55
|
+
|
|
56
|
+
**Step 1: 创建基础结构**
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// src/services/tagService.ts
|
|
60
|
+
import { getDatabase } from '../db/schema.js';
|
|
61
|
+
import { EntityRepository } from '../db/entityRepository.js';
|
|
62
|
+
|
|
63
|
+
export interface TagNode {
|
|
64
|
+
name: string;
|
|
65
|
+
level: number;
|
|
66
|
+
memoryCount: number;
|
|
67
|
+
usageCount: number;
|
|
68
|
+
children: TagNode[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface TagStats {
|
|
72
|
+
totalTags: number;
|
|
73
|
+
totalMemories: number;
|
|
74
|
+
usageStats: { name: string; count: number }[];
|
|
75
|
+
levelDistribution: Record<number, number>;
|
|
76
|
+
recentlyUsed: { name: string; lastUsed: string }[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class TagService {
|
|
80
|
+
private db: ReturnType<typeof getDatabase>;
|
|
81
|
+
private entityRepo: EntityRepository;
|
|
82
|
+
|
|
83
|
+
constructor() {
|
|
84
|
+
this.db = getDatabase();
|
|
85
|
+
this.entityRepo = new EntityRepository(this.db);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// TODO: 实现方法
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Step 2: 提交**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git add src/services/tagService.ts
|
|
96
|
+
git commit -m "feat: add TagService class skeleton"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Task 4: 实现 getTagTree 方法
|
|
102
|
+
|
|
103
|
+
**Files:**
|
|
104
|
+
- Modify: `src/services/tagService.ts`
|
|
105
|
+
|
|
106
|
+
**Step 1: 实现 getTagTree 方法**
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
async getTagTree(): Promise<{ totalTags: number; maxLevel: number; tree: TagNode[] }> {
|
|
110
|
+
// 1. 获取所有标签
|
|
111
|
+
const allTags = this.db.prepare(`
|
|
112
|
+
SELECT e.*, COUNT(me.memory_id) as memory_count
|
|
113
|
+
FROM entities e
|
|
114
|
+
LEFT JOIN memory_entities me ON e.id = me.entity_id
|
|
115
|
+
WHERE e.type = 'tag'
|
|
116
|
+
GROUP BY e.id
|
|
117
|
+
`).all() as any[];
|
|
118
|
+
|
|
119
|
+
// 2. 构建映射
|
|
120
|
+
const tagMap = new Map<string, TagNode>();
|
|
121
|
+
for (const tag of allTags) {
|
|
122
|
+
tagMap.set(tag.id, {
|
|
123
|
+
name: tag.name,
|
|
124
|
+
level: tag.level,
|
|
125
|
+
memoryCount: tag.memory_count || 0,
|
|
126
|
+
usageCount: tag.memory_count || 0,
|
|
127
|
+
children: []
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 3. 构建树形结构
|
|
132
|
+
const rootTags: TagNode[] = [];
|
|
133
|
+
let maxLevel = 0;
|
|
134
|
+
|
|
135
|
+
for (const tag of allTags) {
|
|
136
|
+
const node = tagMap.get(tag.id)!;
|
|
137
|
+
maxLevel = Math.max(maxLevel, tag.level);
|
|
138
|
+
|
|
139
|
+
if (tag.parent_id && tagMap.has(tag.parent_id)) {
|
|
140
|
+
tagMap.get(tag.parent_id)!.children.push(node);
|
|
141
|
+
} else {
|
|
142
|
+
rootTags.push(node);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { totalTags: allTags.length, maxLevel, tree: rootTags };
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Step 2: 测试运行**
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npx ts-node -e "
|
|
154
|
+
import { TagService } from './src/services/tagService.js';
|
|
155
|
+
const ts = new TagService();
|
|
156
|
+
ts.getTagTree().then(r => console.log('Tags:', r.totalTags)).catch(e => console.error(e));
|
|
157
|
+
"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Expected: 输出标签数量(可能为 0)
|
|
161
|
+
|
|
162
|
+
**Step 3: 提交**
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
git add src/services/tagService.ts
|
|
166
|
+
git commit -m "feat: implement getTagTree method"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Task 5: 实现 getTagStats 方法
|
|
172
|
+
|
|
173
|
+
**Files:**
|
|
174
|
+
- Modify: `src/services/tagService.ts`
|
|
175
|
+
|
|
176
|
+
**Step 1: 实现 getTagStats 方法**
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
async getTagStats(): Promise<TagStats> {
|
|
180
|
+
// 总标签数
|
|
181
|
+
const totalTags = this.db.prepare(`
|
|
182
|
+
SELECT COUNT(*) as count FROM entities WHERE type = 'tag'
|
|
183
|
+
`).get() as { count: number };
|
|
184
|
+
|
|
185
|
+
// 总记忆数
|
|
186
|
+
const totalMemories = this.db.prepare(`
|
|
187
|
+
SELECT COUNT(*) as count FROM memories
|
|
188
|
+
`).get() as { count: number };
|
|
189
|
+
|
|
190
|
+
// 使用频率统计
|
|
191
|
+
const usageStats = this.db.prepare(`
|
|
192
|
+
SELECT e.name, COUNT(me.memory_id) as count
|
|
193
|
+
FROM entities e
|
|
194
|
+
JOIN memory_entities me ON e.id = me.entity_id
|
|
195
|
+
WHERE e.type = 'tag'
|
|
196
|
+
GROUP BY e.id
|
|
197
|
+
ORDER BY count DESC
|
|
198
|
+
LIMIT 20
|
|
199
|
+
`).all() as { name: string; count: number }[];
|
|
200
|
+
|
|
201
|
+
// 层级分布
|
|
202
|
+
const levelDist = this.db.prepare(`
|
|
203
|
+
SELECT level, COUNT(*) as count
|
|
204
|
+
FROM entities
|
|
205
|
+
WHERE type = 'tag'
|
|
206
|
+
GROUP BY level
|
|
207
|
+
`).all() as { level: number; count: number }[];
|
|
208
|
+
|
|
209
|
+
const levelDistribution: Record<number, number> = {};
|
|
210
|
+
for (const d of levelDist) {
|
|
211
|
+
levelDistribution[d.level] = d.count;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 最近使用
|
|
215
|
+
const recentlyUsed = this.db.prepare(`
|
|
216
|
+
SELECT e.name, MAX(me.created_at) as last_used
|
|
217
|
+
FROM entities e
|
|
218
|
+
JOIN memory_entities me ON e.id = me.entity_id
|
|
219
|
+
WHERE e.type = 'tag'
|
|
220
|
+
GROUP BY e.id
|
|
221
|
+
ORDER BY last_used DESC
|
|
222
|
+
LIMIT 10
|
|
223
|
+
`).all() as { name: string; last_used: string }[];
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
totalTags: totalTags.count,
|
|
227
|
+
totalMemories: totalMemories.count,
|
|
228
|
+
usageStats,
|
|
229
|
+
levelDistribution,
|
|
230
|
+
recentlyUsed
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Step 2: 测试运行**
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
npx ts-node -e "
|
|
239
|
+
import { TagService } from './src/services/tagService.js';
|
|
240
|
+
const ts = new TagService();
|
|
241
|
+
ts.getTagStats().then(r => console.log('Stats:', JSON.stringify(r, null, 2))).catch(e => console.error(e));
|
|
242
|
+
"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Expected: 输出统计数据
|
|
246
|
+
|
|
247
|
+
**Step 3: 提交**
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
git add src/services/tagService.ts
|
|
251
|
+
git commit -m "feat: implement getTagStats method"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Task 6: 实现 HTML 生成器 - 标签树
|
|
257
|
+
|
|
258
|
+
**Files:**
|
|
259
|
+
- Modify: `src/services/tagService.ts`
|
|
260
|
+
|
|
261
|
+
**Step 1: 添加 generateTreeHtml 方法**
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
generateTreeHtml(data: { totalTags: number; maxLevel: number; tree: TagNode[] }): string {
|
|
265
|
+
const renderNode = (node: TagNode, indent: number = 0): string => {
|
|
266
|
+
const padding = ' '.repeat(indent);
|
|
267
|
+
let html = `${padding}<div class="tag-item" data-level="${node.level}">\n`;
|
|
268
|
+
html += `${padding} <div class="tag-header" onclick="toggle(this)">\n`;
|
|
269
|
+
html += `${padding} <span class="toggle">${node.children.length ? '▶' : '·'}</span>\n`;
|
|
270
|
+
html += `${padding} <span class="tag-name">${node.name}</span>\n`;
|
|
271
|
+
html += `${padding} <span class="tag-count">(${node.memoryCount}条记忆, ${node.usageCount}次使用)</span>\n`;
|
|
272
|
+
html += `${padding} </div>\n`;
|
|
273
|
+
|
|
274
|
+
if (node.children.length > 0) {
|
|
275
|
+
html += `${padding} <div class="tag-children" style="display:none;">\n`;
|
|
276
|
+
for (const child of node.children) {
|
|
277
|
+
html += renderNode(child, indent + 2);
|
|
278
|
+
}
|
|
279
|
+
html += `${padding} </div>\n`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
html += `${padding}</div>\n`;
|
|
283
|
+
return html;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
let html = `<!DOCTYPE html>
|
|
287
|
+
<html>
|
|
288
|
+
<head>
|
|
289
|
+
<meta charset="UTF-8">
|
|
290
|
+
<title>标签树 - ${data.totalTags} 个标签</title>
|
|
291
|
+
<style>
|
|
292
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 20px; }
|
|
293
|
+
.tag-item { margin: 4px 0; }
|
|
294
|
+
.tag-header { cursor: pointer; padding: 4px 8px; border-radius: 4px; }
|
|
295
|
+
.tag-header:hover { background: #f0f0f0; }
|
|
296
|
+
.toggle { display: inline-block; width: 20px; color: #666; }
|
|
297
|
+
.tag-name { font-weight: 500; color: #333; }
|
|
298
|
+
.tag-count { color: #999; font-size: 12px; margin-left: 8px; }
|
|
299
|
+
.tag-children { margin-left: 20px; border-left: 1px solid #eee; padding-left: 8px; }
|
|
300
|
+
</style>
|
|
301
|
+
</head>
|
|
302
|
+
<body>
|
|
303
|
+
<h1>标签树 (${data.totalTags} 个标签, 最大层级: ${data.maxLevel})</h1>
|
|
304
|
+
<script>
|
|
305
|
+
function toggle(el) {
|
|
306
|
+
const children = el.nextElementSibling;
|
|
307
|
+
if (children) children.style.display = children.style.display === 'none' ? 'block' : 'none';
|
|
308
|
+
const arrow = el.querySelector('.toggle');
|
|
309
|
+
if (arrow) arrow.textContent = children.style.display === 'none' ? '▶' : '▼';
|
|
310
|
+
}
|
|
311
|
+
</script>
|
|
312
|
+
`;
|
|
313
|
+
|
|
314
|
+
for (const node of data.tree) {
|
|
315
|
+
html += renderNode(node);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
html += `</body></html>`;
|
|
319
|
+
return html;
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Step 2: 提交**
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
git add src/services/tagService.ts
|
|
327
|
+
git commit -m "feat: add generateTreeHtml method"
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Task 7: 实现 HTML 生成器 - 标签统计
|
|
333
|
+
|
|
334
|
+
**Files:**
|
|
335
|
+
- Modify: `src/services/tagService.ts`
|
|
336
|
+
|
|
337
|
+
**Step 1: 添加 generateStatsHtml 方法**
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
generateStatsHtml(stats: TagStats): string {
|
|
341
|
+
const maxUsage = Math.max(...stats.usageStats.map(s => s.count), 1);
|
|
342
|
+
|
|
343
|
+
let html = `<!DOCTYPE html>
|
|
344
|
+
<html>
|
|
345
|
+
<head>
|
|
346
|
+
<meta charset="UTF-8">
|
|
347
|
+
<title>标签统计</title>
|
|
348
|
+
<style>
|
|
349
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 20px; }
|
|
350
|
+
h1, h2 { color: #333; }
|
|
351
|
+
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
|
|
352
|
+
.stat-card { background: #f9f9f9; padding: 20px; border-radius: 8px; text-align: center; }
|
|
353
|
+
.stat-number { font-size: 36px; font-weight: bold; color: #4a90d9; }
|
|
354
|
+
.stat-label { color: #666; margin-top: 8px; }
|
|
355
|
+
.bar-chart { margin: 20px 0; }
|
|
356
|
+
.bar { background: #4a90d9; color: white; padding: 8px 12px; margin: 4px 0; border-radius: 4px; }
|
|
357
|
+
.recent-list { list-style: none; padding: 0; }
|
|
358
|
+
.recent-list li { padding: 8px; border-bottom: 1px solid #eee; }
|
|
359
|
+
.level-grid { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
360
|
+
.level-badge { background: #e0e0e0; padding: 8px 16px; border-radius: 16px; }
|
|
361
|
+
</style>
|
|
362
|
+
</head>
|
|
363
|
+
<body>
|
|
364
|
+
<h1>标签统计</h1>
|
|
365
|
+
|
|
366
|
+
<div class="stat-grid">
|
|
367
|
+
<div class="stat-card">
|
|
368
|
+
<div class="stat-number">${stats.totalTags}</div>
|
|
369
|
+
<div class="stat-label">总标签数</div>
|
|
370
|
+
</div>
|
|
371
|
+
<div class="stat-card">
|
|
372
|
+
<div class="stat-number">${stats.totalMemories}</div>
|
|
373
|
+
<div class="stat-label">总记忆数</div>
|
|
374
|
+
</div>
|
|
375
|
+
<div class="stat-card">
|
|
376
|
+
<div class="stat-number">${stats.maxLevel !== undefined ? stats.maxLevel + 1 : '-'}</div>
|
|
377
|
+
<div class="stat-label">层级深度</div>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
<h2>使用频率排行</h2>
|
|
382
|
+
<div class="bar-chart">
|
|
383
|
+
`;
|
|
384
|
+
|
|
385
|
+
for (const s of stats.usageStats) {
|
|
386
|
+
const width = Math.round((s.count / maxUsage) * 100);
|
|
387
|
+
html += ` <div class="bar" style="width: ${width}%">${s.name} (${s.count})</div>\n`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
html += ` </div>
|
|
391
|
+
|
|
392
|
+
<h2>层级分布</h2>
|
|
393
|
+
<div class="level-grid">
|
|
394
|
+
`;
|
|
395
|
+
|
|
396
|
+
for (const [level, count] of Object.entries(stats.levelDistribution)) {
|
|
397
|
+
html += ` <div class="level-badge">Level ${level}: ${count}</div>\n`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
html += ` </div>
|
|
401
|
+
|
|
402
|
+
<h2>最近使用</h2>
|
|
403
|
+
<ul class="recent-list">
|
|
404
|
+
`;
|
|
405
|
+
|
|
406
|
+
for (const r of stats.recentlyUsed) {
|
|
407
|
+
const date = new Date(r.lastUsed).toLocaleDateString('zh-CN');
|
|
408
|
+
html += ` <li>${r.name} - ${date}</li>\n`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
html += ` </ul>
|
|
412
|
+
</body>
|
|
413
|
+
</html>`;
|
|
414
|
+
|
|
415
|
+
return html;
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Step 2: 提交**
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
git add src/services/tagService.ts
|
|
423
|
+
git commit -m "feat: add generateStatsHtml method"
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Task 8: 集成到 CLI
|
|
429
|
+
|
|
430
|
+
**Files:**
|
|
431
|
+
- Modify: `src/index.ts`
|
|
432
|
+
|
|
433
|
+
**Step 1: 导入 TagService**
|
|
434
|
+
|
|
435
|
+
在文件顶部添加:
|
|
436
|
+
```typescript
|
|
437
|
+
import { TagService } from './services/tagService.js';
|
|
438
|
+
import { writeFile } from 'fs/promises';
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Step 2: 添加 tags 子命令**
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
// 在现有命令后添加
|
|
445
|
+
.command('tags <action>')
|
|
446
|
+
.description('标签管理命令')
|
|
447
|
+
.option('-o, --output <file>', '输出文件路径')
|
|
448
|
+
.action(async (action, options) => {
|
|
449
|
+
const tagService = new TagService();
|
|
450
|
+
const outputFile = options.output || (action === 'tree' ? 'tags-tree.html' : 'tags-stats.html');
|
|
451
|
+
|
|
452
|
+
if (action === 'tree') {
|
|
453
|
+
const data = await tagService.getTagTree();
|
|
454
|
+
const html = tagService.generateTreeHtml(data);
|
|
455
|
+
await writeFile(outputFile, html);
|
|
456
|
+
console.log(`标签树已生成: ${outputFile}`);
|
|
457
|
+
} else if (action === 'stats') {
|
|
458
|
+
const stats = await tagService.getTagStats();
|
|
459
|
+
const html = tagService.generateStatsHtml(stats);
|
|
460
|
+
await writeFile(outputFile, html);
|
|
461
|
+
console.log(`标签统计已生成: ${outputFile}`);
|
|
462
|
+
} else {
|
|
463
|
+
console.error('未知命令: tree 或 stats');
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Step 3: 测试编译**
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
npm run build
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Step 4: 测试运行**
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
node dist/index.js tags tree -o /tmp/test-tags.html
|
|
479
|
+
cat /tmp/test-tags.html | head -30
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Expected: 生成 HTML 文件
|
|
483
|
+
|
|
484
|
+
**Step 5: 提交**
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
git add src/index.ts
|
|
488
|
+
git commit -m "feat: add tags CLI commands"
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Task 9: 最终测试
|
|
494
|
+
|
|
495
|
+
**Step 1: 测试 tree 命令**
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
node dist/index.js tags tree
|
|
499
|
+
ls -la tags-tree.html
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Expected: 生成 tags-tree.html
|
|
503
|
+
|
|
504
|
+
**Step 2: 测试 stats 命令**
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
node dist/index.js tags stats
|
|
508
|
+
ls -la tags-stats.html
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Expected: 生成 tags-stats.html
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Task 10: 合并到主分支
|
|
516
|
+
|
|
517
|
+
**Step 1: 切换到主分支**
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
git checkout main
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Step 2: 合并功能分支**
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
git merge feature/tags-visualization
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
**Step 3: 推送到远程**
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
git push origin main
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Step 4: 删除功能分支(可选)**
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
git branch -d feature/tags-visualization
|
|
539
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yun-zero/claw-memory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw 记忆插件 - 自动保存对话,智能注入上下文",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claw-memory": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"test:e2e": "vitest run test/e2e",
|
|
17
|
+
"test:stress": "vitest run test/stress",
|
|
18
|
+
"test:all": "vitest run"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"better-sqlite3": "^11.0.0",
|
|
22
|
+
"commander": "^12.0.0",
|
|
23
|
+
"node-cron": "^4.2.1",
|
|
24
|
+
"uuid": "^10.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"@types/node-cron": "^3.0.11",
|
|
30
|
+
"@types/uuid": "^10.0.0",
|
|
31
|
+
"tsx": "^4.0.0",
|
|
32
|
+
"typescript": "^5.0.0",
|
|
33
|
+
"vitest": "^2.0.0"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"openclaw",
|
|
37
|
+
"memory",
|
|
38
|
+
"ai",
|
|
39
|
+
"plugin"
|
|
40
|
+
]
|
|
41
|
+
}
|