atlas-mcp 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.
Files changed (54) hide show
  1. package/.env.example +32 -0
  2. package/README.md +282 -0
  3. package/package.json +72 -0
  4. package/public/app/assets/app-CxbS1w9p.js +3981 -0
  5. package/public/app/assets/index-BA6nxCuI.css +1 -0
  6. package/public/app/assets/index-BXmIRrQH.js +177 -0
  7. package/public/app/index.html +27 -0
  8. package/public/assets/brain-atlas.LICENSE.txt +16 -0
  9. package/public/assets/brain-atlas.glb +0 -0
  10. package/public/assets/brain.obj +27282 -0
  11. package/public/fonts/DepartureMono-Regular.woff +0 -0
  12. package/public/fonts/DepartureMono-Regular.woff2 +0 -0
  13. package/scripts/sync-memory-vectors.js +46 -0
  14. package/src/audit.js +9 -0
  15. package/src/cli/args.js +87 -0
  16. package/src/cli/commands/add.js +103 -0
  17. package/src/cli/commands/config.js +228 -0
  18. package/src/cli/commands/delete.js +75 -0
  19. package/src/cli/commands/entities.js +39 -0
  20. package/src/cli/commands/entity.js +47 -0
  21. package/src/cli/commands/get.js +46 -0
  22. package/src/cli/commands/list.js +53 -0
  23. package/src/cli/commands/related.js +56 -0
  24. package/src/cli/commands/search.js +68 -0
  25. package/src/cli/commands/update.js +58 -0
  26. package/src/cli/deps.js +114 -0
  27. package/src/cli/env-file.js +44 -0
  28. package/src/cli/format.js +246 -0
  29. package/src/cli.js +187 -0
  30. package/src/cognitive-worker.js +381 -0
  31. package/src/db.js +2674 -0
  32. package/src/extraction-context.js +31 -0
  33. package/src/ingestion-service.js +387 -0
  34. package/src/ingestion-worker.js +225 -0
  35. package/src/llm-config.js +31 -0
  36. package/src/llm.js +789 -0
  37. package/src/logger.js +51 -0
  38. package/src/mcp-server.js +577 -0
  39. package/src/memory-comparison.js +421 -0
  40. package/src/related-memories.js +232 -0
  41. package/src/run-cognitive-worker.js +12 -0
  42. package/src/run-ingestion-worker.js +13 -0
  43. package/src/run-vector-worker.js +12 -0
  44. package/src/schemas.js +413 -0
  45. package/src/semantic-validation.js +430 -0
  46. package/src/server.js +827 -0
  47. package/src/shared/brain-regions.js +61 -0
  48. package/src/shared/entity-lens.js +249 -0
  49. package/src/shared/memory-placement.js +171 -0
  50. package/src/shared/memory-search.js +55 -0
  51. package/src/shared/region-anchors.js +112 -0
  52. package/src/shared/region-mapper.js +247 -0
  53. package/src/vector-store.js +546 -0
  54. package/src/vector-worker.js +71 -0
@@ -0,0 +1,430 @@
1
+ import { SemanticExtractionSchema } from "./schemas.js";
2
+
3
+ export const SEMANTIC_VALIDATION_CODES = Object.freeze({
4
+ SCHEMA_INVALID: "schema_invalid",
5
+ EVIDENCE_OUT_OF_BOUNDS: "evidence_out_of_bounds",
6
+ EVIDENCE_TEXT_MISMATCH: "evidence_text_mismatch",
7
+ DURABILITY_REJECTED: "durability_rejected",
8
+ USER_FACING_INTERNAL_SUBJECT: "user_facing_internal_subject",
9
+ USER_FACING_IMPLEMENTATION_DATE: "user_facing_implementation_date",
10
+ OCCURRED_AT_TEXT_MISSING: "occurred_at_text_missing",
11
+ OCCURRED_AT_TEXT_MISMATCH: "occurred_at_text_mismatch",
12
+ EVIDENCE_INDEX_OUT_OF_BOUNDS: "evidence_index_out_of_bounds",
13
+ ENTITY_MENTION_UNSUPPORTED: "entity_mention_unsupported",
14
+ DUPLICATE_ENTITY: "duplicate_entity",
15
+ RELATIONSHIP_ENDPOINT_UNSUPPORTED: "relationship_endpoint_unsupported",
16
+ DUPLICATE_RELATIONSHIP: "duplicate_relationship",
17
+ DUPLICATE_ATOM: "duplicate_atom",
18
+ BOUNDARY_MULTIPLE_SENTENCES: "boundary_multiple_sentences",
19
+ BOUNDARY_MULTIPLE_SUBJECTS: "boundary_multiple_subjects",
20
+ BOUNDARY_BROAD_SOURCE_COVERAGE: "boundary_broad_source_coverage",
21
+ BOUNDARY_INDEPENDENT_AND_CLAIMS: "boundary_independent_and_claims",
22
+ });
23
+
24
+ export const SEMANTIC_ACCEPTANCE_THRESHOLDS = Object.freeze({
25
+ durability: 0.70,
26
+ entity: 0.70,
27
+ relationship: 0.75,
28
+ secondaryType: 0.15,
29
+ });
30
+
31
+ const EMPTY_DROP_COUNTS = Object.freeze({
32
+ entities: 0,
33
+ relationships: 0,
34
+ types: 0,
35
+ actions: 0,
36
+ topics: 0,
37
+ });
38
+
39
+ export class SemanticValidationError extends Error {
40
+ constructor(result) {
41
+ const details = result.issues
42
+ .map(({ code, path, message }) =>
43
+ `${code}${path.length ? ` at ${path.join(".")}` : ""}: ${message}`)
44
+ .join("; ");
45
+ super(`Semantic extraction failed deterministic validation: ${details}`);
46
+ this.name = "SemanticValidationError";
47
+ this.code = "SEMANTIC_EXTRACTION_INVALID";
48
+ this.issues = result.issues;
49
+ this.dropCounts = result.dropCounts;
50
+ this.extraction = result.extraction;
51
+ }
52
+ }
53
+
54
+ export function validateSemanticExtraction(
55
+ sourceText,
56
+ candidate,
57
+ { boundaryAudit = true } = {},
58
+ ) {
59
+ if (typeof sourceText !== "string") {
60
+ throw new TypeError("sourceText must be a string");
61
+ }
62
+
63
+ const parsed = SemanticExtractionSchema.safeParse(candidate);
64
+ if (!parsed.success) {
65
+ return {
66
+ success: false,
67
+ extraction: null,
68
+ issues: parsed.error.issues.map((issue) => ({
69
+ code: SEMANTIC_VALIDATION_CODES.SCHEMA_INVALID,
70
+ path: issue.path,
71
+ message: issue.message,
72
+ })),
73
+ dropCounts: { ...EMPTY_DROP_COUNTS },
74
+ };
75
+ }
76
+
77
+ const issues = [];
78
+ const dropCounts = { ...EMPTY_DROP_COUNTS };
79
+ const memories = parsed.data.memories.map((memory, atomIndex) => {
80
+ const normalized = applyAcceptancePolicy(memory, dropCounts);
81
+ validateAtom(sourceText, normalized, atomIndex, issues, boundaryAudit);
82
+ return normalized;
83
+ });
84
+ validateDuplicateAtoms(memories, issues);
85
+
86
+ return {
87
+ success: issues.length === 0,
88
+ extraction: { memories },
89
+ issues,
90
+ dropCounts,
91
+ };
92
+ }
93
+
94
+ export function assertValidSemanticExtraction(sourceText, candidate, options) {
95
+ const result = validateSemanticExtraction(sourceText, candidate, options);
96
+ if (!result.success) throw new SemanticValidationError(result);
97
+ return result;
98
+ }
99
+
100
+ function applyAcceptancePolicy(memory, dropCounts) {
101
+ const allEntityNames = new Set(
102
+ memory.entities.flatMap((entity) => [entity.mention, entity.canonicalName])
103
+ .filter(Boolean)
104
+ .map(normalizeKey),
105
+ );
106
+ const entities = memory.entities.filter((entity) => {
107
+ const accepted = entity.confidence >= SEMANTIC_ACCEPTANCE_THRESHOLDS.entity;
108
+ if (!accepted) dropCounts.entities += 1;
109
+ return accepted;
110
+ });
111
+ const entityNames = new Set(
112
+ entities.flatMap((entity) => [entity.mention, entity.canonicalName])
113
+ .filter(Boolean)
114
+ .map(normalizeKey),
115
+ );
116
+ const relationships = memory.relationships.filter((relationship) => {
117
+ const accepted = relationship.confidence
118
+ >= SEMANTIC_ACCEPTANCE_THRESHOLDS.relationship;
119
+ if (!accepted) dropCounts.relationships += 1;
120
+ return accepted;
121
+ }).filter((relationship) => {
122
+ const accepted = !endpointReferencesDroppedEntity(
123
+ relationship.subject,
124
+ allEntityNames,
125
+ entityNames,
126
+ ) && !endpointReferencesDroppedEntity(
127
+ relationship.object,
128
+ allEntityNames,
129
+ entityNames,
130
+ );
131
+ if (!accepted) dropCounts.relationships += 1;
132
+ return accepted;
133
+ });
134
+ const dominantTypeIndex = memory.types.reduce(
135
+ (dominant, type, index, types) =>
136
+ dominant === -1 || type.weight > types[dominant].weight ? index : dominant,
137
+ -1,
138
+ );
139
+ const types = memory.types.filter((type, index) => {
140
+ const accepted = index === dominantTypeIndex
141
+ || type.weight >= SEMANTIC_ACCEPTANCE_THRESHOLDS.secondaryType;
142
+ if (!accepted) dropCounts.types += 1;
143
+ return accepted;
144
+ });
145
+ const actions = normalizeUniqueStrings(memory.actions, "actions", dropCounts);
146
+ const topics = normalizeUniqueStrings(memory.topics, "topics", dropCounts);
147
+
148
+ return { ...memory, entities, relationships, types, actions, topics };
149
+ }
150
+
151
+ function endpointReferencesDroppedEntity(endpoint, allEntityNames, acceptedEntityNames) {
152
+ const key = normalizeKey(endpoint);
153
+ return allEntityNames.has(key) && !acceptedEntityNames.has(key);
154
+ }
155
+
156
+ function normalizeUniqueStrings(values, field, dropCounts) {
157
+ const seen = new Set();
158
+ const result = [];
159
+ for (const value of values) {
160
+ const trimmed = value.trim();
161
+ const key = normalizeKey(trimmed);
162
+ if (seen.has(key)) {
163
+ dropCounts[field] += 1;
164
+ continue;
165
+ }
166
+ seen.add(key);
167
+ result.push(trimmed);
168
+ }
169
+ return result;
170
+ }
171
+
172
+ function validateAtom(sourceText, memory, atomIndex, issues, boundaryAudit) {
173
+ const atomPath = ["memories", atomIndex];
174
+ const spans = memory.evidenceSpans;
175
+ for (const [spanIndex, span] of spans.entries()) {
176
+ const path = [...atomPath, "evidenceSpans", spanIndex];
177
+ if (span.end > sourceText.length) {
178
+ addIssue(
179
+ issues,
180
+ SEMANTIC_VALIDATION_CODES.EVIDENCE_OUT_OF_BOUNDS,
181
+ path,
182
+ `span end ${span.end} exceeds source length ${sourceText.length}`,
183
+ );
184
+ }
185
+ if (sourceText.slice(span.start, span.end) !== span.text) {
186
+ addIssue(
187
+ issues,
188
+ SEMANTIC_VALIDATION_CODES.EVIDENCE_TEXT_MISMATCH,
189
+ [...path, "text"],
190
+ "span text must exactly equal the source slice",
191
+ );
192
+ }
193
+ }
194
+
195
+ if (!memory.durability.durable
196
+ || memory.durability.confidence < SEMANTIC_ACCEPTANCE_THRESHOLDS.durability) {
197
+ addIssue(
198
+ issues,
199
+ SEMANTIC_VALIDATION_CODES.DURABILITY_REJECTED,
200
+ [...atomPath, "durability"],
201
+ "atom does not meet the durable-memory acceptance threshold",
202
+ );
203
+ }
204
+
205
+ validateUserFacingProse(memory, atomPath, issues);
206
+ validateOccurredAt(sourceText, memory.occurredAt, atomPath, issues);
207
+ validateEntities(memory, atomPath, issues);
208
+ validateRelationships(memory, atomPath, issues);
209
+ if (boundaryAudit) auditBoundaries(sourceText, memory, atomPath, issues);
210
+ }
211
+
212
+ function validateUserFacingProse(memory, atomPath, issues) {
213
+ for (const field of ["text", "summary"]) {
214
+ const value = memory[field];
215
+ if (/\b(?:self|the speaker|the user)\b/i.test(value)) {
216
+ addIssue(
217
+ issues,
218
+ SEMANTIC_VALIDATION_CODES.USER_FACING_INTERNAL_SUBJECT,
219
+ [...atomPath, field],
220
+ `${field} must use first-person language; self is reserved for relationships`,
221
+ );
222
+ }
223
+ if (/\b(?:the )?(?:ingestion|source|current|today'?s) date\b/i.test(value)) {
224
+ addIssue(
225
+ issues,
226
+ SEMANTIC_VALIDATION_CODES.USER_FACING_IMPLEMENTATION_DATE,
227
+ [...atomPath, field],
228
+ `${field} must use the source time phrase or resolved calendar date`,
229
+ );
230
+ }
231
+ }
232
+ }
233
+
234
+ function validateOccurredAt(sourceText, occurredAt, atomPath, issues) {
235
+ if (!occurredAt.text) {
236
+ if (occurredAt.normalized !== null) {
237
+ addIssue(
238
+ issues,
239
+ SEMANTIC_VALIDATION_CODES.OCCURRED_AT_TEXT_MISSING,
240
+ [...atomPath, "occurredAt", "text"],
241
+ "a normalized occurrence date requires an exact source time phrase",
242
+ );
243
+ }
244
+ return;
245
+ }
246
+ if (!sourceText.includes(occurredAt.text)) {
247
+ addIssue(
248
+ issues,
249
+ SEMANTIC_VALIDATION_CODES.OCCURRED_AT_TEXT_MISMATCH,
250
+ [...atomPath, "occurredAt", "text"],
251
+ "occurredAt.text must be an exact source substring",
252
+ );
253
+ }
254
+ }
255
+
256
+ function validateEntities(memory, atomPath, issues) {
257
+ const seen = new Set();
258
+ for (const [entityIndex, entity] of memory.entities.entries()) {
259
+ const path = [...atomPath, "entities", entityIndex];
260
+ const evidence = resolveEvidence(memory, entity.evidenceSpanIndexes, path, issues);
261
+ if (!evidence.some((span) => span.text.includes(entity.mention))) {
262
+ addIssue(
263
+ issues,
264
+ SEMANTIC_VALIDATION_CODES.ENTITY_MENTION_UNSUPPORTED,
265
+ [...path, "mention"],
266
+ "entity mention must occur exactly inside cited evidence",
267
+ );
268
+ }
269
+ const key = [entity.mention, entity.kind, entity.canonicalName || ""]
270
+ .map(normalizeKey)
271
+ .join("\u0000");
272
+ if (seen.has(key)) {
273
+ addIssue(
274
+ issues,
275
+ SEMANTIC_VALIDATION_CODES.DUPLICATE_ENTITY,
276
+ path,
277
+ "duplicate entity after normalization",
278
+ );
279
+ }
280
+ seen.add(key);
281
+ }
282
+ }
283
+
284
+ function validateRelationships(memory, atomPath, issues) {
285
+ const entityNames = new Set(
286
+ memory.entities.flatMap((entity) => [entity.mention, entity.canonicalName])
287
+ .filter(Boolean)
288
+ .map(normalizeKey),
289
+ );
290
+ const seen = new Set();
291
+ for (const [relationshipIndex, relationship] of memory.relationships.entries()) {
292
+ const path = [...atomPath, "relationships", relationshipIndex];
293
+ const evidence = resolveEvidence(
294
+ memory,
295
+ relationship.evidenceSpanIndexes,
296
+ path,
297
+ issues,
298
+ );
299
+ for (const field of ["subject", "object"]) {
300
+ const endpoint = relationship[field];
301
+ const resolved = normalizeKey(endpoint) === "self"
302
+ || entityNames.has(normalizeKey(endpoint))
303
+ || evidence.some((span) => span.text.includes(endpoint));
304
+ if (!resolved) {
305
+ addIssue(
306
+ issues,
307
+ SEMANTIC_VALIDATION_CODES.RELATIONSHIP_ENDPOINT_UNSUPPORTED,
308
+ [...path, field],
309
+ `${field} must resolve to self, an extracted entity, or an exact evidence literal`,
310
+ );
311
+ }
312
+ }
313
+ const key = [relationship.subject, relationship.predicate, relationship.object]
314
+ .map(normalizeKey)
315
+ .join("\u0000");
316
+ if (seen.has(key)) {
317
+ addIssue(
318
+ issues,
319
+ SEMANTIC_VALIDATION_CODES.DUPLICATE_RELATIONSHIP,
320
+ path,
321
+ "duplicate relationship after normalization",
322
+ );
323
+ }
324
+ seen.add(key);
325
+ }
326
+ }
327
+
328
+ function resolveEvidence(memory, indexes, path, issues) {
329
+ const evidence = [];
330
+ for (const [position, index] of indexes.entries()) {
331
+ if (index >= memory.evidenceSpans.length) {
332
+ addIssue(
333
+ issues,
334
+ SEMANTIC_VALIDATION_CODES.EVIDENCE_INDEX_OUT_OF_BOUNDS,
335
+ [...path, "evidenceSpanIndexes", position],
336
+ `evidence span index ${index} does not exist`,
337
+ );
338
+ continue;
339
+ }
340
+ evidence.push(memory.evidenceSpans[index]);
341
+ }
342
+ return evidence;
343
+ }
344
+
345
+ function validateDuplicateAtoms(memories, issues) {
346
+ const seen = new Map();
347
+ for (const [index, memory] of memories.entries()) {
348
+ const key = normalizeKey(memory.text);
349
+ if (seen.has(key)) {
350
+ addIssue(
351
+ issues,
352
+ SEMANTIC_VALIDATION_CODES.DUPLICATE_ATOM,
353
+ ["memories", index, "text"],
354
+ `duplicates atom ${seen.get(key)}`,
355
+ );
356
+ } else {
357
+ seen.set(key, index);
358
+ }
359
+ }
360
+ }
361
+
362
+ function auditBoundaries(sourceText, memory, atomPath, issues) {
363
+ if (sentenceCount(memory.text) > 1) {
364
+ addIssue(
365
+ issues,
366
+ SEMANTIC_VALIDATION_CODES.BOUNDARY_MULTIPLE_SENTENCES,
367
+ [...atomPath, "text"],
368
+ "atom contains multiple complete sentences",
369
+ );
370
+ }
371
+
372
+ const subjects = new Set(memory.relationships.map((item) => normalizeKey(item.subject)));
373
+ if (subjects.size > 1) {
374
+ addIssue(
375
+ issues,
376
+ SEMANTIC_VALIDATION_CODES.BOUNDARY_MULTIPLE_SUBJECTS,
377
+ [...atomPath, "relationships"],
378
+ "atom contains relationship claims about multiple subjects",
379
+ );
380
+ }
381
+
382
+ if (sentenceCount(sourceText) > 1 && sourceText.length > 0) {
383
+ const covered = memory.evidenceSpans.reduce(
384
+ (total, span) => total + Math.max(0, span.end - span.start),
385
+ 0,
386
+ );
387
+ if (covered / sourceText.length >= 0.8) {
388
+ addIssue(
389
+ issues,
390
+ SEMANTIC_VALIDATION_CODES.BOUNDARY_BROAD_SOURCE_COVERAGE,
391
+ [...atomPath, "evidenceSpans"],
392
+ "one atom covers most of a multi-sentence source",
393
+ );
394
+ }
395
+ }
396
+
397
+ const statePredicates = new Set([
398
+ "lives_in",
399
+ "works_at",
400
+ "prefers",
401
+ "related_to",
402
+ "uses",
403
+ "scheduled_for",
404
+ ]);
405
+ const predicates = new Set(
406
+ memory.relationships
407
+ .map((item) => normalizeKey(item.predicate).replaceAll(" ", "_"))
408
+ .filter((predicate) => statePredicates.has(predicate)),
409
+ );
410
+ if (/\band\b/i.test(memory.text) && predicates.size > 1) {
411
+ addIssue(
412
+ issues,
413
+ SEMANTIC_VALIDATION_CODES.BOUNDARY_INDEPENDENT_AND_CLAIMS,
414
+ [...atomPath, "text"],
415
+ "atom joins multiple independently evolving state predicates with 'and'",
416
+ );
417
+ }
418
+ }
419
+
420
+ function sentenceCount(text) {
421
+ return (String(text).match(/[^.!?]+[.!?]+(?:\s+|$)/g) || []).length;
422
+ }
423
+
424
+ function normalizeKey(value) {
425
+ return String(value).normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase();
426
+ }
427
+
428
+ function addIssue(issues, code, path, message) {
429
+ issues.push({ code, path, message });
430
+ }