@weave_protocol/domere 1.0.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/PLANNING.md +231 -0
- package/README.md +50 -0
- package/dist/anchoring/ethereum.d.ts +135 -0
- package/dist/anchoring/ethereum.d.ts.map +1 -0
- package/dist/anchoring/ethereum.js +474 -0
- package/dist/anchoring/ethereum.js.map +1 -0
- package/dist/anchoring/index.d.ts +93 -0
- package/dist/anchoring/index.d.ts.map +1 -0
- package/dist/anchoring/index.js +184 -0
- package/dist/anchoring/index.js.map +1 -0
- package/dist/anchoring/merkle.d.ts +91 -0
- package/dist/anchoring/merkle.d.ts.map +1 -0
- package/dist/anchoring/merkle.js +203 -0
- package/dist/anchoring/merkle.js.map +1 -0
- package/dist/anchoring/solana.d.ts +85 -0
- package/dist/anchoring/solana.d.ts.map +1 -0
- package/dist/anchoring/solana.js +301 -0
- package/dist/anchoring/solana.js.map +1 -0
- package/dist/constants.d.ts +130 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +536 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/language/code-analyzer.d.ts +80 -0
- package/dist/language/code-analyzer.d.ts.map +1 -0
- package/dist/language/code-analyzer.js +489 -0
- package/dist/language/code-analyzer.js.map +1 -0
- package/dist/language/detector.d.ts +53 -0
- package/dist/language/detector.d.ts.map +1 -0
- package/dist/language/detector.js +248 -0
- package/dist/language/detector.js.map +1 -0
- package/dist/language/index.d.ts +61 -0
- package/dist/language/index.d.ts.map +1 -0
- package/dist/language/index.js +109 -0
- package/dist/language/index.js.map +1 -0
- package/dist/language/nl-analyzer.d.ts +59 -0
- package/dist/language/nl-analyzer.d.ts.map +1 -0
- package/dist/language/nl-analyzer.js +350 -0
- package/dist/language/nl-analyzer.js.map +1 -0
- package/dist/language/semantic.d.ts +48 -0
- package/dist/language/semantic.d.ts.map +1 -0
- package/dist/language/semantic.js +329 -0
- package/dist/language/semantic.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory.d.ts +48 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +211 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/thread/drift.d.ts +43 -0
- package/dist/thread/drift.d.ts.map +1 -0
- package/dist/thread/drift.js +248 -0
- package/dist/thread/drift.js.map +1 -0
- package/dist/thread/index.d.ts +9 -0
- package/dist/thread/index.d.ts.map +1 -0
- package/dist/thread/index.js +9 -0
- package/dist/thread/index.js.map +1 -0
- package/dist/thread/intent.d.ts +68 -0
- package/dist/thread/intent.d.ts.map +1 -0
- package/dist/thread/intent.js +333 -0
- package/dist/thread/intent.js.map +1 -0
- package/dist/thread/manager.d.ts +85 -0
- package/dist/thread/manager.d.ts.map +1 -0
- package/dist/thread/manager.js +305 -0
- package/dist/thread/manager.js.map +1 -0
- package/dist/thread/weave.d.ts +61 -0
- package/dist/thread/weave.d.ts.map +1 -0
- package/dist/thread/weave.js +158 -0
- package/dist/thread/weave.js.map +1 -0
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +102 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +466 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +48 -0
- package/dist/types.js.map +1 -0
- package/package.json +24 -0
- package/src/anchoring/ethereum.ts +568 -0
- package/src/anchoring/index.ts +236 -0
- package/src/anchoring/merkle.ts +256 -0
- package/src/anchoring/solana.ts +370 -0
- package/src/constants.ts +566 -0
- package/src/index.ts +43 -0
- package/src/language/code-analyzer.ts +564 -0
- package/src/language/detector.ts +297 -0
- package/src/language/index.ts +129 -0
- package/src/language/nl-analyzer.ts +411 -0
- package/src/language/semantic.ts +385 -0
- package/src/storage/index.ts +6 -0
- package/src/storage/memory.ts +271 -0
- package/src/thread/drift.ts +319 -0
- package/src/thread/index.ts +9 -0
- package/src/thread/intent.ts +409 -0
- package/src/thread/manager.ts +414 -0
- package/src/thread/weave.ts +205 -0
- package/src/tools/index.ts +107 -0
- package/src/types.ts +736 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dōmere - The Judge Protocol
|
|
3
|
+
* Semantic Analysis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
SemanticAnalysis,
|
|
8
|
+
ExtractedEntity,
|
|
9
|
+
EntityType,
|
|
10
|
+
IntentClassification,
|
|
11
|
+
} from '../types.js';
|
|
12
|
+
import { INTENT_KEYWORDS, ENTITY_PATTERNS } from '../constants.js';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Semantic Analyzer
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
export class SemanticAnalyzer {
|
|
19
|
+
/**
|
|
20
|
+
* Perform semantic analysis on content
|
|
21
|
+
*/
|
|
22
|
+
analyze(content: string): SemanticAnalysis {
|
|
23
|
+
return {
|
|
24
|
+
intent_classification: this.classifyIntent(content),
|
|
25
|
+
entities: this.extractEntities(content),
|
|
26
|
+
actions_implied: this.extractImpliedActions(content),
|
|
27
|
+
topics: this.extractTopics(content),
|
|
28
|
+
sentiment: this.analyzeSentiment(content),
|
|
29
|
+
formality: this.analyzeFormality(content),
|
|
30
|
+
urgency: this.analyzeUrgency(content),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Classify the intent of the content
|
|
36
|
+
*/
|
|
37
|
+
classifyIntent(content: string): IntentClassification {
|
|
38
|
+
const contentLower = content.toLowerCase();
|
|
39
|
+
const scores: Record<IntentClassification, number> = {
|
|
40
|
+
query: 0,
|
|
41
|
+
mutation: 0,
|
|
42
|
+
deletion: 0,
|
|
43
|
+
execution: 0,
|
|
44
|
+
communication: 0,
|
|
45
|
+
analysis: 0,
|
|
46
|
+
generation: 0,
|
|
47
|
+
unknown: 0,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Score based on keywords
|
|
51
|
+
for (const [intent, keywords] of Object.entries(INTENT_KEYWORDS)) {
|
|
52
|
+
for (const keyword of keywords) {
|
|
53
|
+
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
|
|
54
|
+
const matches = contentLower.match(regex);
|
|
55
|
+
if (matches) {
|
|
56
|
+
scores[intent as IntentClassification] += matches.length;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Find highest scoring intent
|
|
62
|
+
let maxIntent: IntentClassification = 'unknown';
|
|
63
|
+
let maxScore = 0;
|
|
64
|
+
|
|
65
|
+
for (const [intent, score] of Object.entries(scores)) {
|
|
66
|
+
if (score > maxScore) {
|
|
67
|
+
maxScore = score;
|
|
68
|
+
maxIntent = intent as IntentClassification;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// If no strong signal, check question patterns
|
|
73
|
+
if (maxScore < 2) {
|
|
74
|
+
if (/^(what|where|who|when|how|why|which|is|are|do|does|can|could|would|should)\b/i.test(content.trim())) {
|
|
75
|
+
return 'query';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return maxIntent;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extract entities from content
|
|
84
|
+
*/
|
|
85
|
+
extractEntities(content: string): ExtractedEntity[] {
|
|
86
|
+
const entities: ExtractedEntity[] = [];
|
|
87
|
+
|
|
88
|
+
// Extract using patterns
|
|
89
|
+
for (const [type, pattern] of Object.entries(ENTITY_PATTERNS)) {
|
|
90
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
91
|
+
let match;
|
|
92
|
+
|
|
93
|
+
while ((match = regex.exec(content)) !== null) {
|
|
94
|
+
entities.push({
|
|
95
|
+
type: type as EntityType,
|
|
96
|
+
value: match[0],
|
|
97
|
+
confidence: 0.9,
|
|
98
|
+
position: { start: match.index, end: match.index + match[0].length },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Extract potential names (capitalized words not at start of sentence)
|
|
104
|
+
const namePattern = /(?<![.!?]\s)(?<!\n)\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\b/g;
|
|
105
|
+
let match;
|
|
106
|
+
while ((match = namePattern.exec(content)) !== null) {
|
|
107
|
+
const value = match[1];
|
|
108
|
+
if (!this.isCommonPhrase(value)) {
|
|
109
|
+
entities.push({
|
|
110
|
+
type: 'person',
|
|
111
|
+
value,
|
|
112
|
+
confidence: 0.6,
|
|
113
|
+
position: { start: match.index, end: match.index + match[0].length },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Extract database/table references
|
|
119
|
+
const dbPattern = /\b(database|table|collection|schema|db)\s*[`'"]?(\w+)[`'"]?/gi;
|
|
120
|
+
while ((match = dbPattern.exec(content)) !== null) {
|
|
121
|
+
entities.push({
|
|
122
|
+
type: match[1].toLowerCase() === 'table' ? 'table' : 'database',
|
|
123
|
+
value: match[2],
|
|
124
|
+
confidence: 0.85,
|
|
125
|
+
position: { start: match.index, end: match.index + match[0].length },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Extract API endpoints
|
|
130
|
+
const apiPattern = /(?:api|endpoint|route):\s*([^\s,]+)|\/api\/[\w/]+/gi;
|
|
131
|
+
while ((match = apiPattern.exec(content)) !== null) {
|
|
132
|
+
entities.push({
|
|
133
|
+
type: 'api_endpoint',
|
|
134
|
+
value: match[1] || match[0],
|
|
135
|
+
confidence: 0.8,
|
|
136
|
+
position: { start: match.index, end: match.index + match[0].length },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Remove duplicates
|
|
141
|
+
return this.deduplicateEntities(entities);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Extract implied actions from content
|
|
146
|
+
*/
|
|
147
|
+
extractImpliedActions(content: string): string[] {
|
|
148
|
+
const actions: string[] = [];
|
|
149
|
+
const contentLower = content.toLowerCase();
|
|
150
|
+
|
|
151
|
+
// Look for verb phrases
|
|
152
|
+
const verbPatterns = [
|
|
153
|
+
/\b(will|should|must|need to|want to|going to|have to)\s+(\w+(?:\s+\w+)?)/gi,
|
|
154
|
+
/\b(please|kindly)?\s*(get|fetch|create|update|delete|send|run|execute|analyze|generate)\s+/gi,
|
|
155
|
+
/\b(i want|i need|i'd like)\s+(?:to\s+)?(\w+)/gi,
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
for (const pattern of verbPatterns) {
|
|
159
|
+
let match;
|
|
160
|
+
while ((match = pattern.exec(contentLower)) !== null) {
|
|
161
|
+
const action = match[0].trim().replace(/^(please|kindly)\s*/i, '');
|
|
162
|
+
if (action.length > 3 && !actions.includes(action)) {
|
|
163
|
+
actions.push(action);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Infer from intent keywords
|
|
169
|
+
for (const [intent, keywords] of Object.entries(INTENT_KEYWORDS)) {
|
|
170
|
+
for (const keyword of keywords) {
|
|
171
|
+
if (contentLower.includes(keyword) && !actions.some(a => a.includes(keyword))) {
|
|
172
|
+
actions.push(`${keyword} data/content`);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return actions.slice(0, 10);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract topics from content
|
|
183
|
+
*/
|
|
184
|
+
extractTopics(content: string): string[] {
|
|
185
|
+
const topics: string[] = [];
|
|
186
|
+
const contentLower = content.toLowerCase();
|
|
187
|
+
|
|
188
|
+
// Domain-specific topic detection
|
|
189
|
+
const topicPatterns: Record<string, RegExp[]> = {
|
|
190
|
+
'sales': [/\b(sales|revenue|quota|deal|pipeline|forecast)\b/gi],
|
|
191
|
+
'customer': [/\b(customer|client|account|user|subscriber)\b/gi],
|
|
192
|
+
'finance': [/\b(finance|budget|expense|cost|profit|invoice|payment)\b/gi],
|
|
193
|
+
'hr': [/\b(employee|staff|hire|recruit|payroll|benefits|performance)\b/gi],
|
|
194
|
+
'marketing': [/\b(marketing|campaign|lead|conversion|engagement|brand)\b/gi],
|
|
195
|
+
'product': [/\b(product|feature|release|roadmap|backlog|sprint)\b/gi],
|
|
196
|
+
'engineering': [/\b(code|deploy|bug|fix|release|infrastructure|api)\b/gi],
|
|
197
|
+
'security': [/\b(security|password|auth|permission|access|credential)\b/gi],
|
|
198
|
+
'data': [/\b(data|database|query|report|analytics|metrics)\b/gi],
|
|
199
|
+
'support': [/\b(support|ticket|issue|help|resolve|escalat)\b/gi],
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
for (const [topic, patterns] of Object.entries(topicPatterns)) {
|
|
203
|
+
for (const pattern of patterns) {
|
|
204
|
+
if (pattern.test(contentLower)) {
|
|
205
|
+
if (!topics.includes(topic)) {
|
|
206
|
+
topics.push(topic);
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return topics;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Analyze sentiment (-1 to 1)
|
|
218
|
+
*/
|
|
219
|
+
analyzeSentiment(content: string): number {
|
|
220
|
+
const contentLower = content.toLowerCase();
|
|
221
|
+
|
|
222
|
+
const positiveWords = [
|
|
223
|
+
'good', 'great', 'excellent', 'amazing', 'wonderful', 'fantastic', 'love',
|
|
224
|
+
'like', 'happy', 'pleased', 'thank', 'thanks', 'appreciate', 'helpful',
|
|
225
|
+
'perfect', 'awesome', 'brilliant', 'success', 'successful', 'best',
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const negativeWords = [
|
|
229
|
+
'bad', 'terrible', 'awful', 'horrible', 'hate', 'dislike', 'angry',
|
|
230
|
+
'frustrated', 'annoyed', 'disappointed', 'fail', 'failed', 'failure',
|
|
231
|
+
'wrong', 'error', 'problem', 'issue', 'bug', 'broken', 'worst',
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
let positiveCount = 0;
|
|
235
|
+
let negativeCount = 0;
|
|
236
|
+
|
|
237
|
+
for (const word of positiveWords) {
|
|
238
|
+
const regex = new RegExp(`\\b${word}\\b`, 'gi');
|
|
239
|
+
const matches = contentLower.match(regex);
|
|
240
|
+
if (matches) positiveCount += matches.length;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (const word of negativeWords) {
|
|
244
|
+
const regex = new RegExp(`\\b${word}\\b`, 'gi');
|
|
245
|
+
const matches = contentLower.match(regex);
|
|
246
|
+
if (matches) negativeCount += matches.length;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check for negation
|
|
250
|
+
const negationPattern = /\b(not|no|never|don't|doesn't|didn't|won't|wouldn't|can't|couldn't)\s+\w+/gi;
|
|
251
|
+
const negations = contentLower.match(negationPattern)?.length || 0;
|
|
252
|
+
|
|
253
|
+
const total = positiveCount + negativeCount;
|
|
254
|
+
if (total === 0) return 0;
|
|
255
|
+
|
|
256
|
+
let sentiment = (positiveCount - negativeCount) / total;
|
|
257
|
+
|
|
258
|
+
// Adjust for negations
|
|
259
|
+
if (negations > 0) {
|
|
260
|
+
sentiment *= 0.5;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return Math.max(-1, Math.min(1, sentiment));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Analyze formality (0 to 1)
|
|
268
|
+
*/
|
|
269
|
+
analyzeFormality(content: string): number {
|
|
270
|
+
const contentLower = content.toLowerCase();
|
|
271
|
+
|
|
272
|
+
// Informal indicators
|
|
273
|
+
const informalPatterns = [
|
|
274
|
+
/\b(gonna|wanna|gotta|kinda|sorta|y'all|ain't)\b/gi,
|
|
275
|
+
/\b(lol|lmao|omg|wtf|btw|fyi|imo|imho)\b/gi,
|
|
276
|
+
/!{2,}/g,
|
|
277
|
+
/\?{2,}/g,
|
|
278
|
+
/\.{3,}/g,
|
|
279
|
+
/\b(hey|hi|yo|sup)\b/gi,
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
// Formal indicators
|
|
283
|
+
const formalPatterns = [
|
|
284
|
+
/\b(hereby|therefore|whereas|pursuant|accordingly)\b/gi,
|
|
285
|
+
/\b(please|kindly|respectfully|sincerely)\b/gi,
|
|
286
|
+
/\b(regarding|concerning|pertaining|with respect to)\b/gi,
|
|
287
|
+
/\b(Dear|Sir|Madam|Mr\.|Mrs\.|Ms\.|Dr\.)\b/g,
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
let informalCount = 0;
|
|
291
|
+
let formalCount = 0;
|
|
292
|
+
|
|
293
|
+
for (const pattern of informalPatterns) {
|
|
294
|
+
const matches = contentLower.match(pattern);
|
|
295
|
+
if (matches) informalCount += matches.length;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
for (const pattern of formalPatterns) {
|
|
299
|
+
const matches = content.match(pattern);
|
|
300
|
+
if (matches) formalCount += matches.length;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Calculate average sentence length (longer = more formal)
|
|
304
|
+
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
305
|
+
const avgSentenceLength = sentences.length > 0
|
|
306
|
+
? sentences.reduce((sum, s) => sum + s.split(/\s+/).length, 0) / sentences.length
|
|
307
|
+
: 0;
|
|
308
|
+
|
|
309
|
+
// Normalize
|
|
310
|
+
let formality = 0.5;
|
|
311
|
+
|
|
312
|
+
if (formalCount > 0 || informalCount > 0) {
|
|
313
|
+
formality += (formalCount - informalCount) * 0.1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (avgSentenceLength > 15) {
|
|
317
|
+
formality += 0.1;
|
|
318
|
+
} else if (avgSentenceLength < 8) {
|
|
319
|
+
formality -= 0.1;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return Math.max(0, Math.min(1, formality));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Analyze urgency (0 to 1)
|
|
327
|
+
*/
|
|
328
|
+
analyzeUrgency(content: string): number {
|
|
329
|
+
const contentLower = content.toLowerCase();
|
|
330
|
+
|
|
331
|
+
const urgentPatterns = [
|
|
332
|
+
{ pattern: /\b(urgent|asap|immediately|right now|right away)\b/gi, weight: 0.3 },
|
|
333
|
+
{ pattern: /\b(critical|emergency|priority|important)\b/gi, weight: 0.2 },
|
|
334
|
+
{ pattern: /\b(deadline|due|by end of day|eod|cob)\b/gi, weight: 0.15 },
|
|
335
|
+
{ pattern: /\b(need|must|have to|required)\b/gi, weight: 0.1 },
|
|
336
|
+
{ pattern: /!+/g, weight: 0.05 },
|
|
337
|
+
{ pattern: /\b(please|help)\b/gi, weight: 0.05 },
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
let urgency = 0;
|
|
341
|
+
|
|
342
|
+
for (const { pattern, weight } of urgentPatterns) {
|
|
343
|
+
const matches = contentLower.match(pattern);
|
|
344
|
+
if (matches) {
|
|
345
|
+
urgency += matches.length * weight;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return Math.min(1, urgency);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Check if a phrase is a common non-name phrase
|
|
354
|
+
*/
|
|
355
|
+
private isCommonPhrase(phrase: string): boolean {
|
|
356
|
+
const commonPhrases = [
|
|
357
|
+
'The', 'This', 'That', 'These', 'Those', 'What', 'When', 'Where', 'How',
|
|
358
|
+
'New York', 'Los Angeles', 'San Francisco', 'United States', 'United Kingdom',
|
|
359
|
+
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',
|
|
360
|
+
'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
|
|
361
|
+
'September', 'October', 'November', 'December',
|
|
362
|
+
'First', 'Second', 'Third', 'Last', 'Next', 'Previous',
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
return commonPhrases.some(p => phrase.toLowerCase() === p.toLowerCase());
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Deduplicate entities
|
|
370
|
+
*/
|
|
371
|
+
private deduplicateEntities(entities: ExtractedEntity[]): ExtractedEntity[] {
|
|
372
|
+
const seen = new Set<string>();
|
|
373
|
+
const unique: ExtractedEntity[] = [];
|
|
374
|
+
|
|
375
|
+
for (const entity of entities) {
|
|
376
|
+
const key = `${entity.type}:${entity.value.toLowerCase()}`;
|
|
377
|
+
if (!seen.has(key)) {
|
|
378
|
+
seen.add(key);
|
|
379
|
+
unique.push(entity);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return unique;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dōmere - The Judge Protocol
|
|
3
|
+
* Memory Storage Implementation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
IDomereStorage,
|
|
8
|
+
Thread,
|
|
9
|
+
ThreadHop,
|
|
10
|
+
ThreadFilters,
|
|
11
|
+
ComplianceResult,
|
|
12
|
+
CompliancePolicy,
|
|
13
|
+
AnchorReference,
|
|
14
|
+
ArbitrationCase,
|
|
15
|
+
ArbitrationEvidence,
|
|
16
|
+
CaseFilters,
|
|
17
|
+
} from '../types.js';
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Memory Storage
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export class MemoryStorage implements IDomereStorage {
|
|
24
|
+
private threads: Map<string, Thread> = new Map();
|
|
25
|
+
private hops: Map<string, ThreadHop[]> = new Map();
|
|
26
|
+
private complianceResults: Map<string, ComplianceResult[]> = new Map();
|
|
27
|
+
private policies: Map<string, CompliancePolicy> = new Map();
|
|
28
|
+
private anchors: Map<string, AnchorReference[]> = new Map();
|
|
29
|
+
private cases: Map<string, ArbitrationCase> = new Map();
|
|
30
|
+
private evidence: Map<string, ArbitrationEvidence[]> = new Map();
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Thread Operations
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
async saveThread(thread: Thread): Promise<void> {
|
|
37
|
+
this.threads.set(thread.id, { ...thread });
|
|
38
|
+
this.hops.set(thread.id, [...thread.hops]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getThread(id: string): Promise<Thread | null> {
|
|
42
|
+
const thread = this.threads.get(id);
|
|
43
|
+
if (!thread) return null;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...thread,
|
|
47
|
+
hops: this.hops.get(id) || [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async updateThread(thread: Thread): Promise<void> {
|
|
52
|
+
this.threads.set(thread.id, { ...thread });
|
|
53
|
+
this.hops.set(thread.id, [...thread.hops]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async listThreads(filters?: ThreadFilters): Promise<Thread[]> {
|
|
57
|
+
let threads = Array.from(this.threads.values());
|
|
58
|
+
|
|
59
|
+
if (filters) {
|
|
60
|
+
if (filters.status) {
|
|
61
|
+
threads = threads.filter(t => t.status === filters.status);
|
|
62
|
+
}
|
|
63
|
+
if (filters.origin_type) {
|
|
64
|
+
threads = threads.filter(t => t.origin.type === filters.origin_type);
|
|
65
|
+
}
|
|
66
|
+
if (filters.origin_identity) {
|
|
67
|
+
threads = threads.filter(t => t.origin.identity === filters.origin_identity);
|
|
68
|
+
}
|
|
69
|
+
if (filters.since) {
|
|
70
|
+
threads = threads.filter(t => t.created_at >= filters.since!);
|
|
71
|
+
}
|
|
72
|
+
if (filters.until) {
|
|
73
|
+
threads = threads.filter(t => t.created_at <= filters.until!);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Sort by creation date (newest first)
|
|
78
|
+
threads.sort((a, b) => b.created_at.getTime() - a.created_at.getTime());
|
|
79
|
+
|
|
80
|
+
// Apply pagination
|
|
81
|
+
const offset = filters?.offset ?? 0;
|
|
82
|
+
const limit = filters?.limit ?? 100;
|
|
83
|
+
|
|
84
|
+
return threads.slice(offset, offset + limit).map(t => ({
|
|
85
|
+
...t,
|
|
86
|
+
hops: this.hops.get(t.id) || [],
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Hop Operations
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
async addHop(threadId: string, hop: ThreadHop): Promise<void> {
|
|
95
|
+
const hops = this.hops.get(threadId) || [];
|
|
96
|
+
hops.push({ ...hop });
|
|
97
|
+
this.hops.set(threadId, hops);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getHops(threadId: string): Promise<ThreadHop[]> {
|
|
101
|
+
return [...(this.hops.get(threadId) || [])];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Compliance Operations
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
async saveComplianceResult(result: ComplianceResult): Promise<void> {
|
|
109
|
+
const results = this.complianceResults.get(result.thread_id) || [];
|
|
110
|
+
results.push({ ...result });
|
|
111
|
+
this.complianceResults.set(result.thread_id, results);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getComplianceResults(threadId: string): Promise<ComplianceResult[]> {
|
|
115
|
+
return [...(this.complianceResults.get(threadId) || [])];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Policy Operations
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
async savePolicy(policy: CompliancePolicy): Promise<void> {
|
|
123
|
+
this.policies.set(policy.id, { ...policy });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getPolicy(id: string): Promise<CompliancePolicy | null> {
|
|
127
|
+
const policy = this.policies.get(id);
|
|
128
|
+
return policy ? { ...policy } : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async listPolicies(): Promise<CompliancePolicy[]> {
|
|
132
|
+
return Array.from(this.policies.values()).map(p => ({ ...p }));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// Anchor Operations
|
|
137
|
+
// ============================================================================
|
|
138
|
+
|
|
139
|
+
async saveAnchor(threadId: string, anchor: AnchorReference): Promise<void> {
|
|
140
|
+
const anchors = this.anchors.get(threadId) || [];
|
|
141
|
+
anchors.push({ ...anchor });
|
|
142
|
+
this.anchors.set(threadId, anchors);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getAnchors(threadId: string): Promise<AnchorReference[]> {
|
|
146
|
+
return [...(this.anchors.get(threadId) || [])];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Arbitration Operations
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
async saveCase(case_: ArbitrationCase): Promise<void> {
|
|
154
|
+
this.cases.set(case_.id, { ...case_ });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getCase(id: string): Promise<ArbitrationCase | null> {
|
|
158
|
+
const case_ = this.cases.get(id);
|
|
159
|
+
if (!case_) return null;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
...case_,
|
|
163
|
+
evidence: this.evidence.get(id) || [],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async listCases(filters?: CaseFilters): Promise<ArbitrationCase[]> {
|
|
168
|
+
let cases = Array.from(this.cases.values());
|
|
169
|
+
|
|
170
|
+
if (filters) {
|
|
171
|
+
if (filters.status) {
|
|
172
|
+
cases = cases.filter(c => c.status === filters.status);
|
|
173
|
+
}
|
|
174
|
+
if (filters.dispute_type) {
|
|
175
|
+
cases = cases.filter(c => c.dispute.type === filters.dispute_type);
|
|
176
|
+
}
|
|
177
|
+
if (filters.thread_id) {
|
|
178
|
+
cases = cases.filter(c => c.thread_id === filters.thread_id);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Sort by creation date (newest first)
|
|
183
|
+
cases.sort((a, b) => b.created_at.getTime() - a.created_at.getTime());
|
|
184
|
+
|
|
185
|
+
// Apply pagination
|
|
186
|
+
const offset = filters?.offset ?? 0;
|
|
187
|
+
const limit = filters?.limit ?? 100;
|
|
188
|
+
|
|
189
|
+
return cases.slice(offset, offset + limit).map(c => ({
|
|
190
|
+
...c,
|
|
191
|
+
evidence: this.evidence.get(c.id) || [],
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// Evidence Operations
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
async saveEvidence(evidence: ArbitrationEvidence): Promise<void> {
|
|
200
|
+
const evidenceList = this.evidence.get(evidence.case_id) || [];
|
|
201
|
+
evidenceList.push({ ...evidence });
|
|
202
|
+
this.evidence.set(evidence.case_id, evidenceList);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async getEvidence(caseId: string): Promise<ArbitrationEvidence[]> {
|
|
206
|
+
return [...(this.evidence.get(caseId) || [])];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// Utility Operations
|
|
211
|
+
// ============================================================================
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Clear all data
|
|
215
|
+
*/
|
|
216
|
+
clear(): void {
|
|
217
|
+
this.threads.clear();
|
|
218
|
+
this.hops.clear();
|
|
219
|
+
this.complianceResults.clear();
|
|
220
|
+
this.policies.clear();
|
|
221
|
+
this.anchors.clear();
|
|
222
|
+
this.cases.clear();
|
|
223
|
+
this.evidence.clear();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get statistics
|
|
228
|
+
*/
|
|
229
|
+
getStats(): {
|
|
230
|
+
threads: number;
|
|
231
|
+
hops: number;
|
|
232
|
+
policies: number;
|
|
233
|
+
anchors: number;
|
|
234
|
+
cases: number;
|
|
235
|
+
} {
|
|
236
|
+
let totalHops = 0;
|
|
237
|
+
for (const hops of this.hops.values()) {
|
|
238
|
+
totalHops += hops.length;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let totalAnchors = 0;
|
|
242
|
+
for (const anchors of this.anchors.values()) {
|
|
243
|
+
totalAnchors += anchors.length;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
threads: this.threads.size,
|
|
248
|
+
hops: totalHops,
|
|
249
|
+
policies: this.policies.size,
|
|
250
|
+
anchors: totalAnchors,
|
|
251
|
+
cases: this.cases.size,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ============================================================================
|
|
257
|
+
// Storage Factory
|
|
258
|
+
// ============================================================================
|
|
259
|
+
|
|
260
|
+
export function createStorage(type: 'memory' | 'sqlite' = 'memory'): IDomereStorage {
|
|
261
|
+
switch (type) {
|
|
262
|
+
case 'memory':
|
|
263
|
+
return new MemoryStorage();
|
|
264
|
+
case 'sqlite':
|
|
265
|
+
// TODO: Implement SQLite storage
|
|
266
|
+
console.warn('SQLite storage not implemented, falling back to memory');
|
|
267
|
+
return new MemoryStorage();
|
|
268
|
+
default:
|
|
269
|
+
return new MemoryStorage();
|
|
270
|
+
}
|
|
271
|
+
}
|