@gethmy/mcp 2.3.1 → 2.3.2
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/dist/lib/active-learning.js +939 -787
- package/dist/lib/api-client.js +2527 -644
- package/dist/lib/auto-session.js +177 -196
- package/dist/lib/cli.js +34954 -128
- package/dist/lib/config.js +235 -201
- package/dist/lib/consolidation.js +374 -289
- package/dist/lib/context-assembly.js +1265 -838
- package/dist/lib/graph-expansion.js +139 -155
- package/dist/lib/http.js +1917 -130
- package/dist/lib/index.js +29525 -5
- package/dist/lib/lifecycle-maintenance.js +663 -79
- package/dist/lib/memory-cleanup.js +1315 -409
- package/dist/lib/onboard.js +2588 -32
- package/dist/lib/prompt-builder.js +438 -445
- package/dist/lib/remote.js +31733 -143
- package/dist/lib/server.js +29388 -3229
- package/dist/lib/skills.js +315 -132
- package/dist/lib/tui/agents.js +128 -107
- package/dist/lib/tui/docs.js +1590 -687
- package/dist/lib/tui/setup.js +5698 -804
- package/dist/lib/tui/theme.js +183 -86
- package/dist/lib/tui/writer.js +1149 -176
- package/package.json +2 -2
- package/src/memory-cleanup.ts +2 -4
- package/dist/lib/__tests__/active-learning.test.js +0 -386
- package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
- package/dist/lib/__tests__/auto-session.test.js +0 -661
- package/dist/lib/__tests__/context-assembly.test.js +0 -362
- package/dist/lib/__tests__/graph-expansion.test.js +0 -150
- package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
- package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
- package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
- package/dist/lib/__tests__/pattern-detection.test.js +0 -295
- package/dist/lib/__tests__/prompt-builder.test.js +0 -418
|
@@ -1,303 +1,388 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
+
|
|
31
|
+
// src/graph-expansion.ts
|
|
32
|
+
async function autoExpandGraph(client, entityId, title, content, _tags, workspaceId, projectId, maxRelations = 5) {
|
|
33
|
+
try {
|
|
34
|
+
const contentSnippet = content.slice(0, 200).trim();
|
|
35
|
+
const query = [title, contentSnippet].filter(Boolean).join(" ");
|
|
36
|
+
let candidates = [];
|
|
37
|
+
const { entities } = await client.searchMemoryEntities(workspaceId, query, {
|
|
38
|
+
project_id: projectId,
|
|
39
|
+
limit: 20
|
|
40
|
+
});
|
|
41
|
+
candidates = entities.filter((e) => e.id !== entityId).slice(0, maxRelations);
|
|
42
|
+
if (candidates.length === 0) {
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
44
|
+
const retry = await client.searchMemoryEntities(workspaceId, query, {
|
|
28
45
|
project_id: projectId,
|
|
29
|
-
limit:
|
|
46
|
+
limit: 20
|
|
47
|
+
});
|
|
48
|
+
candidates = retry.entities.filter((e) => e.id !== entityId).slice(0, maxRelations);
|
|
49
|
+
}
|
|
50
|
+
let relationsCreated = 0;
|
|
51
|
+
for (const candidate of candidates) {
|
|
52
|
+
try {
|
|
53
|
+
await client.createMemoryRelation({
|
|
54
|
+
source_id: entityId,
|
|
55
|
+
target_id: candidate.id,
|
|
56
|
+
relation_type: "relates_to",
|
|
57
|
+
confidence: 0.6
|
|
58
|
+
});
|
|
59
|
+
relationsCreated++;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
const status = err?.status;
|
|
62
|
+
if (status !== 409) {}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { relationsCreated };
|
|
66
|
+
} catch {
|
|
67
|
+
return { relationsCreated: 0 };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function findSimilarEntities(client, title, content, workspaceId, options) {
|
|
71
|
+
const contentSnippet = content.slice(0, 200).trim();
|
|
72
|
+
const query = [title, contentSnippet].filter(Boolean).join(" ");
|
|
73
|
+
try {
|
|
74
|
+
const { entities } = await client.searchMemoryEntities(workspaceId, query, {
|
|
75
|
+
project_id: options?.projectId,
|
|
76
|
+
limit: options?.limit ?? 20,
|
|
77
|
+
type: options?.type
|
|
30
78
|
});
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
79
|
+
const minScore = options?.minRrfScore ?? 0;
|
|
80
|
+
const excludeSet = new Set(options?.excludeIds || []);
|
|
81
|
+
return entities.filter((e) => {
|
|
82
|
+
if (excludeSet.has(e.id))
|
|
83
|
+
return false;
|
|
84
|
+
if (minScore > 0 && (e.rrf_score ?? 0) < minScore)
|
|
85
|
+
return false;
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
var CAUSAL_LOOKUP = [
|
|
93
|
+
{
|
|
94
|
+
sourceType: "error",
|
|
95
|
+
targetType: "solution",
|
|
96
|
+
relation: "resolved_by",
|
|
97
|
+
direction: "forward"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
sourceType: "solution",
|
|
101
|
+
targetType: "error",
|
|
102
|
+
relation: "resolved_by",
|
|
103
|
+
direction: "reverse"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
sourceType: "lesson",
|
|
107
|
+
targetType: "error",
|
|
108
|
+
relation: "learned_from",
|
|
109
|
+
direction: "forward"
|
|
110
|
+
}
|
|
111
|
+
];
|
|
112
|
+
async function linkCrossTypeNeighbors(client, entityId, entityType, title, content, workspaceId, projectId) {
|
|
113
|
+
const rules = CAUSAL_LOOKUP.filter((r) => r.sourceType === entityType);
|
|
114
|
+
if (rules.length === 0)
|
|
115
|
+
return { relationsCreated: 0 };
|
|
116
|
+
let relationsCreated = 0;
|
|
117
|
+
for (const rule of rules) {
|
|
118
|
+
try {
|
|
119
|
+
const matches = await findSimilarEntities(client, title, content, workspaceId, {
|
|
120
|
+
projectId,
|
|
121
|
+
limit: 10,
|
|
122
|
+
minRrfScore: 0.04,
|
|
123
|
+
excludeIds: [entityId],
|
|
124
|
+
type: rule.targetType
|
|
125
|
+
});
|
|
126
|
+
for (const match of matches.slice(0, 3)) {
|
|
127
|
+
const sourceId = rule.direction === "forward" ? entityId : match.id;
|
|
128
|
+
const targetId = rule.direction === "forward" ? match.id : entityId;
|
|
129
|
+
try {
|
|
130
|
+
await client.createMemoryRelation({
|
|
131
|
+
source_id: sourceId,
|
|
132
|
+
target_id: targetId,
|
|
133
|
+
relation_type: rule.relation,
|
|
134
|
+
confidence: 0.65
|
|
135
|
+
});
|
|
136
|
+
relationsCreated++;
|
|
137
|
+
} catch {}
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
}
|
|
141
|
+
return { relationsCreated };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/consolidation.ts
|
|
145
|
+
async function consolidateMemories(client, workspaceId, projectId, options) {
|
|
146
|
+
const dryRun = options?.dryRun !== false;
|
|
147
|
+
const minClusterSize = options?.minClusterSize ?? 3;
|
|
148
|
+
const result = {
|
|
149
|
+
consolidated: 0,
|
|
150
|
+
clustersFound: 0,
|
|
151
|
+
entitiesProcessed: 0,
|
|
152
|
+
details: []
|
|
153
|
+
};
|
|
154
|
+
const listResult = await client.listMemoryEntities({
|
|
155
|
+
workspace_id: workspaceId,
|
|
156
|
+
project_id: projectId,
|
|
157
|
+
limit: 100
|
|
158
|
+
});
|
|
159
|
+
const allEntities = (listResult.entities || []).filter((e) => e.memory_tier === "draft" || e.memory_tier === "episode");
|
|
160
|
+
result.entitiesProcessed = allEntities.length;
|
|
161
|
+
if (allEntities.length < minClusterSize)
|
|
162
|
+
return result;
|
|
163
|
+
const typeGroups = new Map;
|
|
164
|
+
for (const entity of allEntities) {
|
|
165
|
+
const group = typeGroups.get(entity.type) || [];
|
|
166
|
+
group.push(entity);
|
|
167
|
+
typeGroups.set(entity.type, group);
|
|
168
|
+
}
|
|
169
|
+
for (const [type, entities] of typeGroups) {
|
|
170
|
+
if (entities.length < minClusterSize)
|
|
171
|
+
continue;
|
|
172
|
+
const clustered = new Set;
|
|
173
|
+
const clusters = [];
|
|
174
|
+
for (const entity of entities) {
|
|
175
|
+
if (clustered.has(entity.id))
|
|
176
|
+
continue;
|
|
177
|
+
const similar = await findSimilarEntities(client, entity.title, entity.content, workspaceId, {
|
|
178
|
+
projectId,
|
|
179
|
+
limit: 20,
|
|
180
|
+
minRrfScore: 0.01,
|
|
181
|
+
excludeIds: [...clustered]
|
|
182
|
+
});
|
|
183
|
+
const entityIdSet = new Set(entities.map((e) => e.id));
|
|
184
|
+
const clusterMembers = similar.filter((s) => entityIdSet.has(s.id) && !clustered.has(s.id) && s.id !== entity.id && s.type === type);
|
|
185
|
+
if (clusterMembers.length >= minClusterSize - 1) {
|
|
186
|
+
const cluster = [
|
|
187
|
+
entity,
|
|
188
|
+
...clusterMembers.slice(0, 5).map((s) => {
|
|
189
|
+
return entities.find((e) => e.id === s.id) || entity;
|
|
190
|
+
})
|
|
191
|
+
];
|
|
192
|
+
const uniqueCluster = [];
|
|
193
|
+
const seen = new Set;
|
|
194
|
+
for (const member of cluster) {
|
|
195
|
+
if (!seen.has(member.id)) {
|
|
196
|
+
seen.add(member.id);
|
|
197
|
+
uniqueCluster.push(member);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (uniqueCluster.length >= minClusterSize) {
|
|
201
|
+
clusters.push(uniqueCluster);
|
|
202
|
+
for (const member of uniqueCluster) {
|
|
203
|
+
clustered.add(member.id);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
41
207
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}),
|
|
71
|
-
];
|
|
72
|
-
// Deduplicate by id
|
|
73
|
-
const uniqueCluster = [];
|
|
74
|
-
const seen = new Set();
|
|
75
|
-
for (const member of cluster) {
|
|
76
|
-
if (!seen.has(member.id)) {
|
|
77
|
-
seen.add(member.id);
|
|
78
|
-
uniqueCluster.push(member);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (uniqueCluster.length >= minClusterSize) {
|
|
82
|
-
clusters.push(uniqueCluster);
|
|
83
|
-
for (const member of uniqueCluster) {
|
|
84
|
-
clustered.add(member.id);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
208
|
+
for (const cluster of clusters) {
|
|
209
|
+
result.clustersFound++;
|
|
210
|
+
const mergedTitle = deriveClusterTitle(cluster, type);
|
|
211
|
+
const memberTitles = cluster.map((e) => e.title);
|
|
212
|
+
const mergedContent = synthesizeClusterContent(cluster, type);
|
|
213
|
+
const maxConfidence = Math.max(...cluster.map((e) => e.confidence));
|
|
214
|
+
const allTags = [...new Set(cluster.flatMap((e) => e.tags || []))];
|
|
215
|
+
const detail = {
|
|
216
|
+
clusterSize: cluster.length,
|
|
217
|
+
mergedTitle,
|
|
218
|
+
memberTitles
|
|
219
|
+
};
|
|
220
|
+
if (!dryRun) {
|
|
221
|
+
try {
|
|
222
|
+
const createResult = await client.createMemoryEntity({
|
|
223
|
+
workspace_id: workspaceId,
|
|
224
|
+
project_id: projectId,
|
|
225
|
+
type,
|
|
226
|
+
scope: "project",
|
|
227
|
+
memory_tier: "reference",
|
|
228
|
+
title: mergedTitle,
|
|
229
|
+
content: mergedContent,
|
|
230
|
+
confidence: maxConfidence,
|
|
231
|
+
tags: [...allTags.slice(0, 15), "consolidated"],
|
|
232
|
+
metadata: {
|
|
233
|
+
source: "consolidation",
|
|
234
|
+
member_ids: cluster.map((e) => e.id),
|
|
235
|
+
consolidated_at: new Date().toISOString()
|
|
87
236
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// Union of all tags (deduped)
|
|
102
|
-
const allTags = [...new Set(cluster.flatMap((e) => e.tags || []))];
|
|
103
|
-
const detail = {
|
|
104
|
-
clusterSize: cluster.length,
|
|
105
|
-
mergedTitle,
|
|
106
|
-
memberTitles,
|
|
107
|
-
};
|
|
108
|
-
if (!dryRun) {
|
|
109
|
-
try {
|
|
110
|
-
// Create consolidated reference entity
|
|
111
|
-
const createResult = await client.createMemoryEntity({
|
|
112
|
-
workspace_id: workspaceId,
|
|
113
|
-
project_id: projectId,
|
|
114
|
-
type,
|
|
115
|
-
scope: "project",
|
|
116
|
-
memory_tier: "reference",
|
|
117
|
-
title: mergedTitle,
|
|
118
|
-
content: mergedContent,
|
|
119
|
-
confidence: maxConfidence,
|
|
120
|
-
tags: [...allTags.slice(0, 15), "consolidated"],
|
|
121
|
-
metadata: {
|
|
122
|
-
source: "consolidation",
|
|
123
|
-
member_ids: cluster.map((e) => e.id),
|
|
124
|
-
consolidated_at: new Date().toISOString(),
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
const newEntity = createResult.entity;
|
|
128
|
-
if (newEntity?.id) {
|
|
129
|
-
detail.entityId = newEntity.id;
|
|
130
|
-
// Create part_of relations from members → consolidated entity
|
|
131
|
-
for (const member of cluster) {
|
|
132
|
-
try {
|
|
133
|
-
await client.createMemoryRelation({
|
|
134
|
-
source_id: member.id,
|
|
135
|
-
target_id: newEntity.id,
|
|
136
|
-
relation_type: "part_of",
|
|
137
|
-
confidence: 0.8,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
// Skip duplicate relations
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// Downgrade member confidence by 0.3 (min 0.1)
|
|
145
|
-
for (const member of cluster) {
|
|
146
|
-
try {
|
|
147
|
-
const newConf = Math.max(member.confidence - 0.3, 0.1);
|
|
148
|
-
await client.updateMemoryEntity(member.id, {
|
|
149
|
-
confidence: newConf,
|
|
150
|
-
metadata: {
|
|
151
|
-
consolidated_into: newEntity.id,
|
|
152
|
-
original_confidence: member.confidence,
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
// Non-fatal
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
result.consolidated++;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
// Non-fatal: consolidation failure for one cluster shouldn't block others
|
|
165
|
-
}
|
|
237
|
+
});
|
|
238
|
+
const newEntity = createResult.entity;
|
|
239
|
+
if (newEntity?.id) {
|
|
240
|
+
detail.entityId = newEntity.id;
|
|
241
|
+
for (const member of cluster) {
|
|
242
|
+
try {
|
|
243
|
+
await client.createMemoryRelation({
|
|
244
|
+
source_id: member.id,
|
|
245
|
+
target_id: newEntity.id,
|
|
246
|
+
relation_type: "part_of",
|
|
247
|
+
confidence: 0.8
|
|
248
|
+
});
|
|
249
|
+
} catch {}
|
|
166
250
|
}
|
|
167
|
-
|
|
168
|
-
|
|
251
|
+
for (const member of cluster) {
|
|
252
|
+
try {
|
|
253
|
+
const newConf = Math.max(member.confidence - 0.3, 0.1);
|
|
254
|
+
await client.updateMemoryEntity(member.id, {
|
|
255
|
+
confidence: newConf,
|
|
256
|
+
metadata: {
|
|
257
|
+
consolidated_into: newEntity.id,
|
|
258
|
+
original_confidence: member.confidence
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
} catch {}
|
|
169
262
|
}
|
|
170
|
-
result.
|
|
171
|
-
|
|
263
|
+
result.consolidated++;
|
|
264
|
+
}
|
|
265
|
+
} catch {}
|
|
266
|
+
} else {
|
|
267
|
+
result.consolidated++;
|
|
268
|
+
}
|
|
269
|
+
result.details.push(detail);
|
|
172
270
|
}
|
|
173
|
-
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
174
273
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Synthesize cluster content by extracting unique, actionable knowledge
|
|
177
|
-
* from each member entity. Skips boilerplate (headers, metadata, agent names)
|
|
178
|
-
* and deduplicates similar lines across members.
|
|
179
|
-
*/
|
|
180
274
|
function synthesizeClusterContent(cluster, type) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (seenLines.has(normalized))
|
|
213
|
-
continue;
|
|
214
|
-
seenLines.add(normalized);
|
|
215
|
-
knowledgeLines.push(line);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (knowledgeLines.length === 0) {
|
|
219
|
-
// Fallback: if no knowledge was extractable, use a compact summary
|
|
220
|
-
return `${cluster.length} related ${type} entities consolidated. Original titles:\n${cluster.map((e) => `- ${e.title}`).join("\n")}`;
|
|
275
|
+
const SKIP_PATTERNS = [
|
|
276
|
+
/^##\s/,
|
|
277
|
+
/^Agent:/,
|
|
278
|
+
/^Duration:/,
|
|
279
|
+
/^Labels:/,
|
|
280
|
+
/^Progress:/,
|
|
281
|
+
/^Session status:/,
|
|
282
|
+
/^Completed at/,
|
|
283
|
+
/^Final state:/,
|
|
284
|
+
/^Related:/,
|
|
285
|
+
/^When working on:/,
|
|
286
|
+
/^\d+\.\s+.+\(\d+%,\s*\+\d+%\)/,
|
|
287
|
+
/^Last updated:/,
|
|
288
|
+
/^Recurring pattern:/,
|
|
289
|
+
/^Consolidated from/
|
|
290
|
+
];
|
|
291
|
+
const seenLines = new Set;
|
|
292
|
+
const knowledgeLines = [];
|
|
293
|
+
for (const entity of cluster) {
|
|
294
|
+
const lines = entity.content.split(`
|
|
295
|
+
`).map((l) => l.trim());
|
|
296
|
+
for (const line of lines) {
|
|
297
|
+
if (!line || line.length < 20)
|
|
298
|
+
continue;
|
|
299
|
+
if (SKIP_PATTERNS.some((p) => p.test(line)))
|
|
300
|
+
continue;
|
|
301
|
+
const normalized = line.toLowerCase().replace(/[*_`#[\]]/g, "").trim();
|
|
302
|
+
if (seenLines.has(normalized))
|
|
303
|
+
continue;
|
|
304
|
+
seenLines.add(normalized);
|
|
305
|
+
knowledgeLines.push(line);
|
|
221
306
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
307
|
+
}
|
|
308
|
+
if (knowledgeLines.length === 0) {
|
|
309
|
+
return `${cluster.length} related ${type} entities consolidated. Original titles:
|
|
310
|
+
${cluster.map((e) => `- ${e.title}`).join(`
|
|
311
|
+
`)}`;
|
|
312
|
+
}
|
|
313
|
+
const MAX_CHARS = 1600;
|
|
314
|
+
const result = [
|
|
315
|
+
`Consolidated knowledge from ${cluster.length} ${type} entities:
|
|
316
|
+
`
|
|
317
|
+
];
|
|
318
|
+
let charCount = result[0].length;
|
|
319
|
+
for (const line of knowledgeLines) {
|
|
320
|
+
if (charCount + line.length + 3 > MAX_CHARS)
|
|
321
|
+
break;
|
|
322
|
+
result.push(`- ${line}`);
|
|
323
|
+
charCount += line.length + 3;
|
|
324
|
+
}
|
|
325
|
+
return result.join(`
|
|
326
|
+
`);
|
|
235
327
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Derive a cluster title from the most common meaningful words across member titles.
|
|
238
|
-
*/
|
|
239
328
|
function deriveClusterTitle(cluster, type) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
.filter((w) => w.length > 2 && !stopWords.has(w));
|
|
292
|
-
for (const word of words) {
|
|
293
|
-
wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
|
|
294
|
-
}
|
|
329
|
+
const stopWords = new Set([
|
|
330
|
+
"the",
|
|
331
|
+
"a",
|
|
332
|
+
"an",
|
|
333
|
+
"is",
|
|
334
|
+
"are",
|
|
335
|
+
"was",
|
|
336
|
+
"were",
|
|
337
|
+
"be",
|
|
338
|
+
"been",
|
|
339
|
+
"being",
|
|
340
|
+
"have",
|
|
341
|
+
"has",
|
|
342
|
+
"had",
|
|
343
|
+
"do",
|
|
344
|
+
"does",
|
|
345
|
+
"did",
|
|
346
|
+
"will",
|
|
347
|
+
"shall",
|
|
348
|
+
"would",
|
|
349
|
+
"should",
|
|
350
|
+
"may",
|
|
351
|
+
"might",
|
|
352
|
+
"can",
|
|
353
|
+
"could",
|
|
354
|
+
"of",
|
|
355
|
+
"in",
|
|
356
|
+
"to",
|
|
357
|
+
"for",
|
|
358
|
+
"with",
|
|
359
|
+
"on",
|
|
360
|
+
"at",
|
|
361
|
+
"from",
|
|
362
|
+
"by",
|
|
363
|
+
"and",
|
|
364
|
+
"or",
|
|
365
|
+
"but",
|
|
366
|
+
"not",
|
|
367
|
+
"session",
|
|
368
|
+
"blocker",
|
|
369
|
+
"pattern",
|
|
370
|
+
"solution",
|
|
371
|
+
"error",
|
|
372
|
+
"task",
|
|
373
|
+
"mid-session"
|
|
374
|
+
]);
|
|
375
|
+
const wordCounts = new Map;
|
|
376
|
+
for (const entity of cluster) {
|
|
377
|
+
const words = entity.title.toLowerCase().split(/\W+/).filter((w) => w.length > 2 && !stopWords.has(w));
|
|
378
|
+
for (const word of words) {
|
|
379
|
+
wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
|
|
295
380
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
.map(([word]) => word[0].toUpperCase() + word.slice(1));
|
|
301
|
-
const suffix = topWords.length > 0 ? topWords.join(" / ") : "Various";
|
|
302
|
-
return `${type[0].toUpperCase() + type.slice(1)}: ${suffix}`;
|
|
381
|
+
}
|
|
382
|
+
const topWords = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([word]) => word[0].toUpperCase() + word.slice(1));
|
|
383
|
+
const suffix = topWords.length > 0 ? topWords.join(" / ") : "Various";
|
|
384
|
+
return `${type[0].toUpperCase() + type.slice(1)}: ${suffix}`;
|
|
303
385
|
}
|
|
386
|
+
export {
|
|
387
|
+
consolidateMemories
|
|
388
|
+
};
|