cogmem 3.6.4 → 3.7.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/MEMORY_ATLAS.md +47 -10
  3. package/README.md +36 -13
  4. package/RELEASE_CHECKLIST.md +7 -4
  5. package/dist/agent/AgentMemoryBackend.d.ts +19 -2
  6. package/dist/agent/AgentMemoryBackend.js +134 -3
  7. package/dist/agent/AgentRecallQueryCompiler.d.ts +1 -1
  8. package/dist/agent/AgentRecallQueryCompiler.js +12 -0
  9. package/dist/atlas/EpisodeTitleGenerator.d.ts +31 -0
  10. package/dist/atlas/EpisodeTitleGenerator.js +152 -0
  11. package/dist/atlas/FacetQueryPlanner.d.ts +31 -0
  12. package/dist/atlas/FacetQueryPlanner.js +188 -0
  13. package/dist/atlas/GraphCurator.d.ts +27 -0
  14. package/dist/atlas/GraphCurator.js +397 -0
  15. package/dist/atlas/MemoryAtlasIndexer.d.ts +3 -0
  16. package/dist/atlas/MemoryAtlasIndexer.js +19 -6
  17. package/dist/atlas/MemoryAtlasService.d.ts +5 -1
  18. package/dist/atlas/MemoryAtlasService.js +165 -12
  19. package/dist/atlas/MemoryAtlasTypes.d.ts +89 -11
  20. package/dist/bin/connect.js +85 -13
  21. package/dist/bin/memory.js +221 -18
  22. package/dist/factory.d.ts +2 -0
  23. package/dist/factory.js +36 -18
  24. package/dist/host/openclaw/AutoMemoryPluginInstaller.js +83 -10
  25. package/dist/mcp/server.js +1 -1
  26. package/dist/store/EventStore.d.ts +6 -0
  27. package/dist/store/EventStore.js +28 -2
  28. package/dist/store/MemoryAtlasStore.d.ts +10 -1
  29. package/dist/store/MemoryAtlasStore.js +228 -14
  30. package/dist/types/index.d.ts +3 -0
  31. package/examples/hermes-backend/AGENTS.md +11 -5
  32. package/examples/hermes-backend/README.md +5 -3
  33. package/examples/hermes-backend/SKILL.md +28 -5
  34. package/examples/hermes-backend/references/operations.md +16 -6
  35. package/examples/openclaw-backend/AGENTS.md +9 -3
  36. package/examples/openclaw-backend/README.md +9 -3
  37. package/examples/openclaw-backend/SKILL.md +23 -10
  38. package/examples/openclaw-backend/references/operations.md +21 -8
  39. package/package.json +1 -1
@@ -99,6 +99,9 @@ export function inferAgentRecallIntent(query) {
99
99
  if (/原话|怎么说的|完整对话|上一句|下一句|exact quote|verbatim/.test(text)) {
100
100
  return 'forensic_quote';
101
101
  }
102
+ if (/记得.{0,12}(聊过|讨论过|说过)|还记得|之前.{0,12}(聊过|讨论过|说过)|以前.{0,12}(聊过|讨论过|说过)|(过去|几个月前|半年前|上个月|前几天|昨天|上次|上个).{0,20}(聊过|讨论过|说过)|当时.{0,12}(聊|说|问)|那次.{0,12}(聊|说|问)|有没有.{0,12}(记录|聊过|讨论过)|have we discussed|did we talk about|previously discussed/.test(text)) {
103
+ return 'historical_discussion';
104
+ }
102
105
  return 'memory_recall';
103
106
  }
104
107
  export function extractRecallKeywords(text) {
@@ -168,10 +171,19 @@ function buildSemanticCuePhrases(keywords, query, anchorText) {
168
171
  const hasBlackBox = merged.includes('黑盒') || /黑盒|black\s*box/iu.test(text);
169
172
  if (hasMemory && hasBlackBox) {
170
173
  out.push('记忆 黑盒');
174
+ out.push('记忆黑盒');
175
+ out.push('CogMem Memory Context');
176
+ out.push('Memory Context');
177
+ out.push('上下文注入');
178
+ out.push('sourceContext 原文下钻');
171
179
  out.push('存档 黑盒');
172
180
  out.push('对话 存档 黑盒');
173
181
  out.push('上下文 黑盒');
174
182
  out.push('黑盒');
183
+ if (/graph|图|卡死|database locked|zombie|锁/iu.test(text)) {
184
+ out.push('memory graph 黑盒');
185
+ out.push('database locked zombie');
186
+ }
175
187
  }
176
188
  else if (hasBlackBox) {
177
189
  out.push('黑盒');
@@ -0,0 +1,31 @@
1
+ import type { MemoryEvent } from '../types/index.js';
2
+ export interface EpisodeTitleInput {
3
+ episodeId: string;
4
+ summary?: string | null;
5
+ topicPath?: string | null;
6
+ episodeType?: string | null;
7
+ startedAt?: number | null;
8
+ events: MemoryEvent[];
9
+ }
10
+ export interface EpisodeTitleResult {
11
+ displayTitle: string;
12
+ oneLineSummary: string;
13
+ topicHints: string[];
14
+ issueHints: string[];
15
+ eventKind: string;
16
+ userIntent?: string;
17
+ localDate?: string;
18
+ confidence: number;
19
+ reviewNeeded: boolean;
20
+ sourceEventIds: string[];
21
+ generatorTrace: {
22
+ usedUserText: boolean;
23
+ usedAssistantText: boolean;
24
+ fallback: boolean;
25
+ matchedRules: string[];
26
+ };
27
+ }
28
+ export declare class EpisodeTitleGenerator {
29
+ generate(input: EpisodeTitleInput): EpisodeTitleResult;
30
+ }
31
+ //# sourceMappingURL=EpisodeTitleGenerator.d.ts.map
@@ -0,0 +1,152 @@
1
+ import { eventTextForMemory } from '../episode/CogmemBlockStripper.js';
2
+ const TITLE_RULES = [
3
+ {
4
+ id: 'memory_context_blackbox',
5
+ test: (text) => /(cogmem memory context|memory context|sourcecontext|source context|原文下钻|上下文.*黑盒|记忆上下文|摘要.*原话|记忆黑盒)/i.test(text) &&
6
+ !/(database locked|zombie|卡死|atlas|节点|事件名称|manual recall.*before_prompt_build|手动.*recall.*自动注入)/i.test(text),
7
+ displayTitle: 'CogMem Memory Context 黑盒与原文下钻',
8
+ oneLineSummary: '用户指出注入的 CogMem Memory Context 像黑盒,需要能从摘要下钻到原始对话。',
9
+ topicHints: ['memory-blackbox', 'source-drilldown', 'context-injection'],
10
+ issueHints: ['memory-context-blackbox'],
11
+ eventKind: 'diagnostic',
12
+ userIntent: 'inspect_original_context',
13
+ confidence: 0.92,
14
+ },
15
+ {
16
+ id: 'graph_runtime_blackbox',
17
+ test: (text) => /(memory graph|graph.*卡死|卡死|database locked|sqlite.*lock|zombie|僵尸进程|锁冲突)/i.test(text),
18
+ displayTitle: 'memory graph 卡死与数据库锁',
19
+ oneLineSummary: '用户遇到 memory graph 卡死、SQLite 锁冲突或残留进程,导致图谱与召回命令不稳定。',
20
+ topicHints: ['memory-blackbox', 'graph-runtime', 'sqlite-locks'],
21
+ issueHints: ['graph-runtime-blackbox'],
22
+ eventKind: 'bug',
23
+ userIntent: 'debug_graph_runtime',
24
+ confidence: 0.9,
25
+ },
26
+ {
27
+ id: 'atlas_readability_blackbox',
28
+ test: (text) => /(atlas|图谱|节点|事件名称|父主题|多维|facet|canonical|可读性|label|标题)/i.test(text),
29
+ displayTitle: 'Atlas 节点命名与多维导航',
30
+ oneLineSummary: '用户要求 Atlas 节点拥有清晰事件标题,并通过时间、主题、issue、实体等 facet 导航到同一份 canonical episode。',
31
+ topicHints: ['memory-blackbox', 'atlas-readability', 'facet-navigation'],
32
+ issueHints: ['atlas-readability'],
33
+ eventKind: 'plan',
34
+ userIntent: 'improve_atlas_navigation',
35
+ confidence: 0.88,
36
+ },
37
+ {
38
+ id: 'auto_injection_mismatch',
39
+ test: (text) => /((manual recall|手动.*recall|CLI).*(自动注入|before_prompt_build|OpenClaw)|(自动注入|before_prompt_build|OpenClaw).*(manual recall|手动.*recall|CLI)|注入.*不一致|selected.*different|选择.*不同)/i.test(text),
40
+ displayTitle: '自动注入与手动 recall 不一致',
41
+ oneLineSummary: '用户发现 OpenClaw 自动注入与手动 recall 的记忆选择不一致,需要暴露选择依据和匹配 facet。',
42
+ topicHints: ['context-injection', 'memory-blackbox', 'openclaw'],
43
+ issueHints: ['auto-injection-mismatch'],
44
+ eventKind: 'diagnostic',
45
+ userIntent: 'align_auto_injection',
46
+ confidence: 0.89,
47
+ },
48
+ ];
49
+ export class EpisodeTitleGenerator {
50
+ generate(input) {
51
+ const userEvents = input.events.filter((event) => event.role === 'user');
52
+ const assistantEvents = input.events.filter((event) => event.role === 'assistant' || event.role === 'agent');
53
+ const userText = normalizeText(userEvents.map(eventTextForMemory).join('\n'));
54
+ const assistantText = normalizeText(assistantEvents.map(eventTextForMemory).join('\n'));
55
+ const summaryText = normalizeText([input.summary, input.topicPath, input.episodeType].filter(Boolean).join('\n'));
56
+ const preferredText = [userText, summaryText, assistantText].filter(Boolean).join('\n');
57
+ const matchedRule = TITLE_RULES.find((rule) => rule.test(preferredText));
58
+ const localDate = inferLocalDate(input.events, input.startedAt);
59
+ const sourceEventIds = sourceIdsFor(userEvents.length > 0 ? userEvents : input.events);
60
+ if (matchedRule) {
61
+ return {
62
+ displayTitle: boundTitle(matchedRule.displayTitle),
63
+ oneLineSummary: matchedRule.oneLineSummary,
64
+ topicHints: matchedRule.topicHints,
65
+ issueHints: matchedRule.issueHints,
66
+ eventKind: matchedRule.eventKind,
67
+ userIntent: matchedRule.userIntent,
68
+ localDate,
69
+ confidence: matchedRule.confidence,
70
+ reviewNeeded: matchedRule.confidence < 0.65,
71
+ sourceEventIds,
72
+ generatorTrace: {
73
+ usedUserText: userText.length > 0,
74
+ usedAssistantText: userText.length === 0 && assistantText.length > 0,
75
+ fallback: false,
76
+ matchedRules: [matchedRule.id],
77
+ },
78
+ };
79
+ }
80
+ const fallbackTitle = fallbackDisplayTitle({ userText, summaryText, topicPath: input.topicPath, episodeType: input.episodeType });
81
+ return {
82
+ displayTitle: fallbackTitle,
83
+ oneLineSummary: fallbackSummary({ userText, summaryText, topicPath: input.topicPath }),
84
+ topicHints: inferTopicHints(preferredText),
85
+ issueHints: [],
86
+ eventKind: normalizeKind(input.episodeType) ?? 'explanation',
87
+ localDate,
88
+ confidence: 0.55,
89
+ reviewNeeded: true,
90
+ sourceEventIds,
91
+ generatorTrace: {
92
+ usedUserText: userText.length > 0,
93
+ usedAssistantText: userText.length === 0 && assistantText.length > 0,
94
+ fallback: true,
95
+ matchedRules: [],
96
+ },
97
+ };
98
+ }
99
+ }
100
+ function normalizeText(text) {
101
+ return text.replace(/\s+/g, ' ').trim();
102
+ }
103
+ function sourceIdsFor(events) {
104
+ return events.map((event) => event.eventId).filter(Boolean).slice(0, 20);
105
+ }
106
+ function inferLocalDate(events, startedAt) {
107
+ for (const event of events) {
108
+ const candidate = event.localDate;
109
+ if (candidate && /^\d{4}-\d{2}-\d{2}$/.test(candidate))
110
+ return candidate;
111
+ }
112
+ const timestamp = events.find((event) => Number.isFinite(event.occurredAt))?.occurredAt ?? startedAt;
113
+ if (!timestamp || !Number.isFinite(timestamp))
114
+ return undefined;
115
+ return new Date(timestamp).toISOString().slice(0, 10);
116
+ }
117
+ function boundTitle(title) {
118
+ const clean = normalizeText(title).replace(/[0-9a-f]{8}-[0-9a-f-]{12,}/gi, '').trim();
119
+ if (/[^\x00-\x7F]/.test(clean))
120
+ return clean.length > 32 ? `${clean.slice(0, 31)}…` : clean;
121
+ const words = clean.split(/\s+/);
122
+ return words.length > 12 ? words.slice(0, 12).join(' ') : clean;
123
+ }
124
+ function fallbackDisplayTitle(input) {
125
+ const source = input.userText || input.summaryText || input.topicPath || input.episodeType || 'Untitled episode';
126
+ const firstSentence = source.split(/[。.!?\n]/).find(Boolean) ?? source;
127
+ const title = boundTitle(firstSentence);
128
+ if (!title || /^episode[:\s-]*[0-9a-f-]+$/i.test(title))
129
+ return '未命名记忆事件';
130
+ return title;
131
+ }
132
+ function fallbackSummary(input) {
133
+ const source = input.summaryText || input.userText || input.topicPath || '该 episode 缺少可读摘要,需要人工复核。';
134
+ const sentence = source.split(/[。.!?\n]/).find(Boolean) ?? source;
135
+ return normalizeText(sentence).slice(0, 140);
136
+ }
137
+ function inferTopicHints(text) {
138
+ const hints = new Set();
139
+ if (/cogmem|记忆|memory/i.test(text))
140
+ hints.add('memory');
141
+ if (/openclaw/i.test(text))
142
+ hints.add('openclaw');
143
+ if (/hermes/i.test(text))
144
+ hints.add('hermes');
145
+ return Array.from(hints);
146
+ }
147
+ function normalizeKind(kind) {
148
+ if (!kind)
149
+ return undefined;
150
+ const normalized = kind.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
151
+ return normalized || undefined;
152
+ }
@@ -0,0 +1,31 @@
1
+ export type FacetType = 'time' | 'topic' | 'issue' | 'entity' | 'session' | 'thread' | 'memoryKind' | 'actionKind';
2
+ export type FacetOperator = 'intersection' | 'union';
3
+ export interface PlannedFacet {
4
+ type: FacetType;
5
+ value: string;
6
+ label: string;
7
+ nodeId?: string;
8
+ relation?: string;
9
+ granularity?: 'year' | 'month' | 'day';
10
+ from?: number;
11
+ to?: number;
12
+ }
13
+ export interface FacetQueryPlan {
14
+ intent: string;
15
+ operator: FacetOperator;
16
+ facets: PlannedFacet[];
17
+ temporalIntent?: 'timeline';
18
+ groupBy?: 'topic' | 'time' | 'issue';
19
+ exactness: 'strict' | 'relaxed';
20
+ requiresIntersection: boolean;
21
+ keywords: string[];
22
+ query: string;
23
+ }
24
+ export interface FacetQueryPlannerOptions {
25
+ projectId: string;
26
+ now?: number;
27
+ }
28
+ export declare class FacetQueryPlanner {
29
+ plan(query: string, options: FacetQueryPlannerOptions): FacetQueryPlan;
30
+ }
31
+ //# sourceMappingURL=FacetQueryPlanner.d.ts.map
@@ -0,0 +1,188 @@
1
+ const ISSUE_RULES = [
2
+ {
3
+ value: 'memory-context-blackbox',
4
+ label: 'Memory Context 黑盒',
5
+ test: (text) => /(memory context|sourcecontext|source context|上下文.*黑盒|记忆上下文|原文下钻|摘要.*原话)/i.test(text),
6
+ },
7
+ {
8
+ value: 'graph-runtime-blackbox',
9
+ label: 'Graph Runtime 黑盒',
10
+ test: (text) => /(memory graph|graph.*卡死|database locked|sqlite.*lock|zombie|僵尸进程|锁冲突|卡死)/i.test(text),
11
+ },
12
+ {
13
+ value: 'atlas-readability',
14
+ label: 'Atlas 可读性黑盒',
15
+ test: (text) => /(atlas|图谱|节点|事件名称|父主题|多维|facet|canonical|可读性|标题)/i.test(text),
16
+ },
17
+ {
18
+ value: 'auto-injection-mismatch',
19
+ label: '自动注入不一致',
20
+ test: (text) => /(自动注入|before_prompt_build|manual recall|手动.*recall|注入.*不一致|COGMEM_RECALL_CONTEXT)/i.test(text),
21
+ },
22
+ ];
23
+ const TOPIC_RULES = [
24
+ {
25
+ value: 'memory-blackbox',
26
+ label: '记忆黑盒',
27
+ test: (text) => /(记忆黑盒|memory.*blackbox|memory context|sourcecontext|graph.*卡死|atlas|自动注入|上下文.*黑盒)/i.test(text),
28
+ },
29
+ {
30
+ value: 'source-drilldown',
31
+ label: '原文下钻',
32
+ test: (text) => /(sourcecontext|source context|原文下钻|原话|摘要.*原文)/i.test(text),
33
+ },
34
+ {
35
+ value: 'context-injection',
36
+ label: '上下文注入',
37
+ test: (text) => /(自动注入|context injection|before_prompt_build|COGMEM_RECALL_CONTEXT|注入)/i.test(text),
38
+ },
39
+ ];
40
+ export class FacetQueryPlanner {
41
+ plan(query, options) {
42
+ const normalized = query.trim();
43
+ const facets = [];
44
+ const timeFacet = parseTimeFacet(normalized, options.now ?? Date.now());
45
+ if (timeFacet)
46
+ facets.push(withNodeId(timeFacet, options.projectId));
47
+ const timeline = /(后来|继续|timeline|演化|发展|之后)/i.test(normalized);
48
+ const matchedIssueValues = new Set();
49
+ for (const rule of TOPIC_RULES) {
50
+ if (rule.test(normalized)) {
51
+ facets.push(withNodeId({ type: 'topic', value: `PROJECT/${options.projectId}/${rule.value}`, label: rule.label, relation: 'ABOUT_TOPIC' }, options.projectId));
52
+ }
53
+ }
54
+ for (const rule of ISSUE_RULES) {
55
+ if (rule.test(normalized)) {
56
+ facets.push(withNodeId({ type: 'issue', value: rule.value, label: rule.label, relation: 'PART_OF_ISSUE' }, options.projectId));
57
+ matchedIssueValues.add(rule.value);
58
+ }
59
+ }
60
+ if (!timeline && isBroadMemoryBlackbox(normalized) && matchedIssueValues.size === 0) {
61
+ facets.push(withNodeId({ type: 'issue', value: 'memory-context-blackbox', label: 'Memory Context 黑盒', relation: 'PART_OF_ISSUE' }, options.projectId));
62
+ }
63
+ for (const entity of ['Cogmem', 'OpenClaw', 'Hermes']) {
64
+ if (new RegExp(entity, 'i').test(normalized)) {
65
+ facets.push({ type: 'entity', value: `facet:${entity.toLowerCase()}`, label: entity, nodeId: `entity:facet:${entity.toLowerCase()}`, relation: 'INVOLVES_ENTITY' });
66
+ }
67
+ }
68
+ for (const facet of parseKindFacets(normalized, options.projectId))
69
+ facets.push(facet);
70
+ return {
71
+ intent: /聊过|记得|还记得|讨论|原话|那次/i.test(normalized) ? 'historical_discussion' : 'graph_search',
72
+ operator: 'intersection',
73
+ facets: dedupeFacets(facets),
74
+ temporalIntent: timeline ? 'timeline' : undefined,
75
+ groupBy: timeline ? 'time' : timeFacet && facets.length === 1 ? 'topic' : facets.some((facet) => facet.type === 'issue') ? 'issue' : undefined,
76
+ exactness: 'strict',
77
+ requiresIntersection: facets.length > 1,
78
+ keywords: extractKeywords(normalized),
79
+ query: normalized,
80
+ };
81
+ }
82
+ }
83
+ function isBroadMemoryBlackbox(query) {
84
+ return /(记忆黑盒|memory.*blackbox)/i.test(query) &&
85
+ !/(memory graph|graph|database locked|sqlite|zombie|僵尸|卡死|atlas|图谱|节点|事件名称|自动注入|before_prompt_build|manual recall|手动.*recall)/i.test(query);
86
+ }
87
+ function parseKindFacets(query, projectId) {
88
+ const facets = [];
89
+ const memoryKindRules = [
90
+ ['bug', /(bug|问题|卡死|failure|故障|错误|报错)/i],
91
+ ['decision', /(decision|决定|方案|结论)/i],
92
+ ['plan', /(计划|下一步|策略|plan)/i],
93
+ ];
94
+ const actionKindRules = [
95
+ ['implemented', /(修复|实现|implemented|合并|发布|升级)/i],
96
+ ['reviewed', /(检查|审查|review)/i],
97
+ ['debugged', /(debug|排查|卡死|locked|zombie)/i],
98
+ ];
99
+ for (const [value, test] of memoryKindRules) {
100
+ if (test.test(query))
101
+ facets.push(withNodeId({ type: 'memoryKind', value, label: value, relation: 'HAS_MEMORY_KIND' }, projectId));
102
+ }
103
+ for (const [value, test] of actionKindRules) {
104
+ if (test.test(query))
105
+ facets.push(withNodeId({ type: 'actionKind', value, label: value, relation: 'HAS_ACTION_KIND' }, projectId));
106
+ }
107
+ return facets;
108
+ }
109
+ function parseTimeFacet(query, now) {
110
+ const currentYear = new Date(now).getUTCFullYear();
111
+ const isoDay = query.match(/(20\d{2})[-年\/.](\d{1,2})[-月\/.](\d{1,2})日?/);
112
+ if (isoDay)
113
+ return dayFacet(Number(isoDay[1]), Number(isoDay[2]), Number(isoDay[3]));
114
+ const cnDay = query.match(/(?:(20\d{2})年)?(\d{1,2})月(\d{1,2})(?:号|日)?/);
115
+ if (cnDay)
116
+ return dayFacet(cnDay[1] ? Number(cnDay[1]) : currentYear, Number(cnDay[2]), Number(cnDay[3]));
117
+ const isoMonth = query.match(/(20\d{2})[-年\/.](\d{1,2})月?/);
118
+ if (isoMonth)
119
+ return monthFacet(Number(isoMonth[1]), Number(isoMonth[2]));
120
+ const year = query.match(/\b(20\d{2})\b|((20\d{2})年)/);
121
+ if (year)
122
+ return yearFacet(Number(year[1] ?? year[3]));
123
+ if (/去年/.test(query))
124
+ return yearFacet(currentYear - 1);
125
+ return undefined;
126
+ }
127
+ function dayFacet(year, month, day) {
128
+ const label = `${year}-${pad(month)}-${pad(day)}`;
129
+ return {
130
+ type: 'time',
131
+ value: label,
132
+ label,
133
+ relation: 'OCCURRED_ON',
134
+ granularity: 'day',
135
+ from: Date.UTC(year, month - 1, day),
136
+ to: Date.UTC(year, month - 1, day + 1),
137
+ };
138
+ }
139
+ function monthFacet(year, month) {
140
+ const label = `${year}-${pad(month)}`;
141
+ return {
142
+ type: 'time',
143
+ value: label,
144
+ label,
145
+ relation: 'OCCURRED_IN',
146
+ granularity: 'month',
147
+ from: Date.UTC(year, month - 1, 1),
148
+ to: Date.UTC(year, month, 1),
149
+ };
150
+ }
151
+ function yearFacet(year) {
152
+ const label = String(year);
153
+ return {
154
+ type: 'time',
155
+ value: label,
156
+ label,
157
+ relation: 'OCCURRED_IN',
158
+ granularity: 'year',
159
+ from: Date.UTC(year, 0, 1),
160
+ to: Date.UTC(year + 1, 0, 1),
161
+ };
162
+ }
163
+ function withNodeId(facet, projectId) {
164
+ return {
165
+ ...facet,
166
+ nodeId: `${facet.type}:${projectId}:${facet.value}`,
167
+ };
168
+ }
169
+ function dedupeFacets(facets) {
170
+ const seen = new Set();
171
+ return facets.filter((facet) => {
172
+ const key = `${facet.type}:${facet.value}`;
173
+ if (seen.has(key))
174
+ return false;
175
+ seen.add(key);
176
+ return true;
177
+ });
178
+ }
179
+ function extractKeywords(query) {
180
+ return query
181
+ .split(/[^\p{L}\p{N}_-]+/u)
182
+ .map((token) => token.trim())
183
+ .filter((token) => token.length >= 2)
184
+ .slice(0, 8);
185
+ }
186
+ function pad(value) {
187
+ return String(value).padStart(2, '0');
188
+ }
@@ -0,0 +1,27 @@
1
+ import type Database from 'bun:sqlite';
2
+ import type { EventStore } from '../store/EventStore.js';
3
+ import { MemoryAtlasStore } from '../store/MemoryAtlasStore.js';
4
+ export interface GraphCuratorResult {
5
+ episodeCount: number;
6
+ facetNodeCount: number;
7
+ facetEdgeCount: number;
8
+ reviewNeeded: number;
9
+ }
10
+ export declare class GraphCurator {
11
+ private readonly db;
12
+ private readonly eventStore;
13
+ private readonly atlasStore;
14
+ private readonly titleGenerator;
15
+ constructor(db: Database, eventStore: EventStore, atlasStore: MemoryAtlasStore);
16
+ rebuild(projectId: string, now?: number): GraphCuratorResult;
17
+ private episodeEventIds;
18
+ private facetTargetsFor;
19
+ private upsertFacetNode;
20
+ private upsertRawEventNode;
21
+ private projectEpisodeRelations;
22
+ private upsertEpisodeRelation;
23
+ private entityHintsFor;
24
+ private deleteFacetEdges;
25
+ private upsertEdge;
26
+ }
27
+ //# sourceMappingURL=GraphCurator.d.ts.map