shieldcortex 3.0.3 → 3.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 (135) hide show
  1. package/README.md +5 -2
  2. package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
  3. package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
  4. package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
  5. package/dashboard/.next/standalone/dashboard/.next/required-server-files.json +4 -4
  6. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
  7. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
  8. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
  15. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +2 -2
  16. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  17. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  19. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
  23. package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +3 -3
  24. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  25. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +3 -3
  26. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
  27. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +2 -2
  28. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  29. package/dashboard/.next/standalone/dashboard/.next/server/app/page/react-loadable-manifest.json +1 -1
  30. package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  31. package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
  32. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
  33. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  34. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
  35. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
  36. package/dashboard/.next/standalone/dashboard/.next/static/chunks/0a69eb25d08447ee.js +1 -0
  37. package/dashboard/.next/standalone/dashboard/.next/static/chunks/9232a2d99b47b21f.js +3 -0
  38. package/dashboard/.next/standalone/dashboard/.next/static/chunks/97537d3db46c8467.css +3 -0
  39. package/dashboard/.next/standalone/dashboard/.next/static/chunks/aa6e9b8a52353969.js +9 -0
  40. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
  41. package/dashboard/.next/standalone/{node_modules/@img/sharp-linux-x64 → dashboard/node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
  42. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/README.md +2 -2
  43. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/lib/glib-2.0/include/glibconfig.h +8 -9
  44. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 → sharp-libvips-darwin-arm64/lib/libvips-cpp.8.17.3.dylib} +0 -0
  45. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
  46. package/dashboard/.next/standalone/dashboard/server.js +1 -1
  47. package/dashboard/.next/standalone/{dashboard/node_modules/@img/sharp-linux-x64 → node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
  48. package/dashboard/.next/standalone/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
  49. package/dist/api/routes/admin.d.ts +12 -0
  50. package/dist/api/routes/admin.js +502 -0
  51. package/dist/api/routes/graph.d.ts +4 -0
  52. package/dist/api/routes/graph.js +333 -0
  53. package/dist/api/routes/incidents.d.ts +2 -0
  54. package/dist/api/routes/incidents.js +32 -0
  55. package/dist/api/routes/memories.d.ts +4 -0
  56. package/dist/api/routes/memories.js +659 -0
  57. package/dist/api/routes/recall.d.ts +4 -0
  58. package/dist/api/routes/recall.js +36 -0
  59. package/dist/api/routes/system.d.ts +9 -0
  60. package/dist/api/routes/system.js +266 -0
  61. package/dist/api/visualization-server.js +31 -1913
  62. package/dist/cloud/cli.d.ts +1 -0
  63. package/dist/cloud/cli.js +40 -0
  64. package/dist/cloud/config.d.ts +10 -0
  65. package/dist/cloud/config.js +54 -0
  66. package/dist/cloud/graph-sync.d.ts +45 -0
  67. package/dist/cloud/graph-sync.js +257 -0
  68. package/dist/cloud/memory-sync.d.ts +36 -0
  69. package/dist/cloud/memory-sync.js +183 -0
  70. package/dist/cloud/sync-queue.d.ts +24 -0
  71. package/dist/cloud/sync-queue.js +126 -7
  72. package/dist/database/init.js +24 -0
  73. package/dist/graph/backfill.js +3 -5
  74. package/dist/graph/resolve.d.ts +10 -0
  75. package/dist/graph/resolve.js +63 -1
  76. package/dist/index.d.ts +2 -0
  77. package/dist/index.js +61 -4
  78. package/dist/memory/search.d.ts +37 -0
  79. package/dist/memory/search.js +143 -0
  80. package/dist/memory/store.js +47 -171
  81. package/dist/memory/types.d.ts +2 -0
  82. package/dist/service/install.d.ts +1 -0
  83. package/dist/service/install.js +43 -1
  84. package/dist/tools/recall.d.ts +1 -1
  85. package/hooks/openclaw/cortex-memory/handler.ts +5 -141
  86. package/hooks/openclaw/cortex-memory/runtime.mjs +129 -0
  87. package/package.json +8 -4
  88. package/plugins/openclaw/dist/index.js +5 -39
  89. package/scripts/run-jest.mjs +25 -1
  90. package/dashboard/.next/standalone/dashboard/.next/static/chunks/be6970da20a17c0b.js +0 -9
  91. package/dashboard/.next/standalone/dashboard/.next/static/chunks/e63d2228780629dd.css +0 -3
  92. package/dashboard/.next/standalone/dashboard/.next/static/chunks/f69fd1c5e71fbbfd.js +0 -1
  93. package/dashboard/.next/standalone/dashboard/.next/static/chunks/fa5217550a8ab9a6.js +0 -3
  94. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  95. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  96. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  97. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  98. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  99. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  100. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  101. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  102. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  103. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsc.js +0 -133818
  104. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsserver.js +0 -659
  105. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_typingsInstaller.js +0 -222
  106. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
  107. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
  108. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
  109. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
  110. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
  111. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
  112. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
  113. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
  114. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
  115. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
  116. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
  117. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsc.js +0 -8
  118. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserver.js +0 -8
  119. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserverlibrary.js +0 -21
  120. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typesMap.json +0 -497
  121. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typescript.js +0 -200276
  122. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typingsInstaller.js +0 -8
  123. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/watchGuard.js +0 -53
  124. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
  125. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
  126. package/dashboard/.next/standalone/dashboard/node_modules/typescript/package.json +0 -120
  127. package/dashboard/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  128. package/dashboard/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  129. package/scripts/start-dashboard.sh +0 -41
  130. package/scripts/stop-dashboard.sh +0 -21
  131. /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_buildManifest.js +0 -0
  132. /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_clientMiddlewareManifest.json +0 -0
  133. /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_ssgManifest.js +0 -0
  134. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/lib/index.js +0 -0
  135. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/versions.json +0 -0
@@ -0,0 +1,143 @@
1
+ import { cosineSimilarity } from '../embeddings/index.js';
2
+ export function detectQueryCategory(query) {
3
+ const lower = query.toLowerCase();
4
+ if (/architect|design|structure|pattern|system|schema|model/.test(lower)) {
5
+ return 'architecture';
6
+ }
7
+ if (/error|bug|fix|issue|crash|exception|fail|problem/.test(lower)) {
8
+ return 'error';
9
+ }
10
+ if (/prefer|always|never|style|convention|like|want/.test(lower)) {
11
+ return 'preference';
12
+ }
13
+ if (/learn|discover|realiz|found\s+out|turns?\s+out/.test(lower)) {
14
+ return 'learning';
15
+ }
16
+ if (/todo|task|pending|need\s+to|should\s+do/.test(lower)) {
17
+ return 'todo';
18
+ }
19
+ if (/relation|depend|connect|link|reference/.test(lower)) {
20
+ return 'relationship';
21
+ }
22
+ return null;
23
+ }
24
+ export function calculateLinkBoost(memoryId, db) {
25
+ try {
26
+ const linked = db.prepare(`
27
+ SELECT m.salience, ml.strength
28
+ FROM memory_links ml
29
+ JOIN memories m ON (m.id = ml.target_id OR m.id = ml.source_id)
30
+ WHERE (ml.source_id = ? OR ml.target_id = ?)
31
+ AND m.id != ?
32
+ `).all(memoryId, memoryId, memoryId);
33
+ if (linked.length === 0)
34
+ return 0;
35
+ const totalWeight = linked.reduce((sum, link) => sum + link.strength, 0);
36
+ if (totalWeight === 0)
37
+ return 0;
38
+ const weightedSalience = linked.reduce((sum, link) => sum + link.salience * link.strength, 0) / totalWeight;
39
+ return Math.min(0.15, weightedSalience * 0.2);
40
+ }
41
+ catch {
42
+ return 0;
43
+ }
44
+ }
45
+ export function calculateTagScore(queryTags, memoryTags) {
46
+ if (queryTags.length === 0 || memoryTags.length === 0)
47
+ return 0;
48
+ let matches = 0;
49
+ for (const queryTag of queryTags) {
50
+ const lowerQueryTag = queryTag.toLowerCase();
51
+ if (memoryTags.some((memoryTag) => {
52
+ const lowerMemoryTag = memoryTag.toLowerCase();
53
+ return lowerMemoryTag.includes(lowerQueryTag) || lowerQueryTag.includes(lowerMemoryTag);
54
+ })) {
55
+ matches++;
56
+ }
57
+ }
58
+ return (matches / queryTags.length) * 0.1;
59
+ }
60
+ export function extractQueryTags(query) {
61
+ const words = query.toLowerCase().split(/\s+/);
62
+ return words.filter((word) => word.length > 2 &&
63
+ /^[a-z][a-z0-9-]*$/.test(word) &&
64
+ !['the', 'and', 'for', 'with', 'how', 'what', 'when', 'where', 'why'].includes(word));
65
+ }
66
+ export function vectorSearch(db, rowToMemory, queryEmbedding, limit, project, includeGlobal = true) {
67
+ let query = `
68
+ SELECT * FROM memories
69
+ WHERE embedding IS NOT NULL
70
+ `;
71
+ const params = [];
72
+ if (project && includeGlobal) {
73
+ query += ` AND (project = ? OR scope = 'global')`;
74
+ params.push(project);
75
+ }
76
+ else if (project) {
77
+ query += ` AND project = ?`;
78
+ params.push(project);
79
+ }
80
+ const rows = db.prepare(query).all(...params);
81
+ return rows
82
+ .map((row) => {
83
+ const embeddingBuffer = row.embedding;
84
+ const embedding = new Float32Array(embeddingBuffer.buffer, embeddingBuffer.byteOffset, embeddingBuffer.length / 4);
85
+ return {
86
+ memory: rowToMemory(row),
87
+ similarity: cosineSimilarity(queryEmbedding, embedding),
88
+ };
89
+ })
90
+ .filter((result) => result.similarity > 0.3)
91
+ .sort((a, b) => b.similarity - a.similarity)
92
+ .slice(0, limit);
93
+ }
94
+ export function buildSearchExplanation(memory, context, values) {
95
+ const matchedTags = context.queryTags.filter((queryTag) => memory.tags.some((memoryTag) => {
96
+ const lowerMemoryTag = memoryTag.toLowerCase();
97
+ return lowerMemoryTag.includes(queryTag) || queryTag.includes(lowerMemoryTag);
98
+ }));
99
+ const reasons = [];
100
+ if (values.vectorSimilarity > 0) {
101
+ reasons.push(`Semantic similarity ${(values.vectorSimilarity * 100).toFixed(0)}%`);
102
+ }
103
+ if (values.ftsScore > 0.3) {
104
+ reasons.push('Strong keyword match');
105
+ }
106
+ if (values.categoryBoost > 0 && context.detectedCategory) {
107
+ reasons.push(`Matches ${context.detectedCategory} category intent`);
108
+ }
109
+ if (matchedTags.length > 0) {
110
+ reasons.push(`Shared tags: ${matchedTags.slice(0, 3).join(', ')}`);
111
+ }
112
+ if (values.recencyBoost > 0) {
113
+ reasons.push('Recently accessed');
114
+ }
115
+ if (values.linkBoost > 0) {
116
+ reasons.push('Connected to related memories');
117
+ }
118
+ if (values.activationBoost > 0) {
119
+ reasons.push('Activated by recent recall activity');
120
+ }
121
+ if (reasons.length === 0) {
122
+ reasons.push('Ranked by salience and base recall heuristics');
123
+ }
124
+ return {
125
+ query: context.query,
126
+ reasons,
127
+ breakdown: {
128
+ ftsScore: values.ftsScore,
129
+ vectorSimilarity: values.vectorSimilarity,
130
+ vectorBoost: values.vectorBoost,
131
+ decayedScore: values.decayedScore,
132
+ priorityBoost: values.priorityBoost,
133
+ recencyBoost: values.recencyBoost,
134
+ categoryBoost: values.categoryBoost,
135
+ linkBoost: values.linkBoost,
136
+ tagBoost: values.tagBoost,
137
+ activationBoost: values.activationBoost,
138
+ finalScore: values.finalScore,
139
+ matchedTags,
140
+ matchedCategory: values.categoryBoost > 0 ? context.detectedCategory : null,
141
+ },
142
+ };
143
+ }
@@ -4,6 +4,7 @@
4
4
  * Core CRUD operations for the memory database.
5
5
  * Handles storage, retrieval, and management of memories.
6
6
  */
7
+ import { randomUUID } from 'crypto';
7
8
  import { getDatabase, isDatabaseInitialized } from '../database/init.js';
8
9
  import { DEFAULT_CONFIG, } from './types.js';
9
10
  import { calculateSalience, suggestCategory, extractTags, } from './salience.js';
@@ -14,15 +15,18 @@ import { emitMemoryCreated, emitMemoryAccessed, emitMemoryDeleted, emitMemoryUpd
14
15
  import { generateEmbedding, cosineSimilarity } from '../embeddings/index.js';
15
16
  import { isPaused } from '../api/control.js';
16
17
  import { extractFromMemory } from '../graph/extract.js';
17
- import { processExtractionResult } from '../graph/resolve.js';
18
+ import { processExtractionResult, removeMemoryGraph, replaceMemoryGraph } from '../graph/resolve.js';
18
19
  import { runDefencePipeline, storeFragmentationData } from '../defence/index.js';
19
20
  import { syncQuarantineToCloud } from '../cloud/quarantine-sync.js';
21
+ import { syncGraphDeleteForMemoryToCloud, syncGraphForMemoryToCloud } from '../cloud/graph-sync.js';
22
+ import { syncMemoryDeleteToCloud, syncMemoryUpsertToCloud } from '../cloud/memory-sync.js';
20
23
  import { isFeatureEnabled } from '../license/gate.js';
21
24
  import { checkAccess } from '../defence/trust/access-control.js';
22
25
  import { scoreSource } from '../defence/trust/source-scorer.js';
23
26
  import { logAudit } from '../defence/audit/logger.js';
24
27
  import { dispatchWebhook } from '../events/webhooks.js';
25
28
  import { getCachedQueryEmbedding, findSimilarMemories } from './embedding.js';
29
+ import { buildSearchExplanation, calculateLinkBoost, calculateTagScore, detectQueryCategory, extractQueryTags, vectorSearch, } from './search.js';
26
30
  // Anti-bloat: Maximum content size per memory (10KB)
27
31
  const MAX_CONTENT_SIZE = 10 * 1024;
28
32
  // Track truncation info globally for the last addMemory call
@@ -124,6 +128,7 @@ function safeJsonParse(value, fallback) {
124
128
  export function rowToMemory(row) {
125
129
  return {
126
130
  id: row.id,
131
+ uuid: row.uuid,
127
132
  type: row.type,
128
133
  category: row.category,
129
134
  title: row.title,
@@ -134,6 +139,7 @@ export function rowToMemory(row) {
134
139
  accessCount: row.access_count,
135
140
  lastAccessed: new Date(row.last_accessed),
136
141
  createdAt: new Date(row.created_at),
142
+ updatedAt: new Date(row.updated_at ?? row.created_at),
137
143
  decayedScore: row.decayed_score ?? row.salience,
138
144
  metadata: safeJsonParse(row.metadata, {}),
139
145
  embedding: row.embedding,
@@ -352,8 +358,8 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
352
358
  (detectGlobalPattern(input.content, category, tags) ? 'global' : 'project');
353
359
  const transferable = input.transferable ?? (scope === 'global' ? 1 : 0);
354
360
  const stmt = db.prepare(`
355
- INSERT INTO memories (type, category, title, content, project, tags, salience, metadata, scope, transferable)
356
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
361
+ INSERT INTO memories (uuid, type, category, title, content, project, tags, salience, metadata, scope, transferable, updated_at)
362
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
357
363
  `);
358
364
  // Anti-bloat: Truncate content if too large
359
365
  const truncationResult = truncateContent(input.content);
@@ -365,7 +371,8 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
365
371
  };
366
372
  // Transaction: INSERT + defence UPDATE must be atomic to prevent wrong trust scores
367
373
  const insertedId = db.transaction(() => {
368
- const result = stmt.run(type, category, input.title, truncationResult.content, input.project || null, JSON.stringify(tags), salience, JSON.stringify(input.metadata || {}), scope, transferable);
374
+ const memoryUuid = randomUUID();
375
+ const result = stmt.run(memoryUuid, type, category, input.title, truncationResult.content, input.project || null, JSON.stringify(tags), salience, JSON.stringify(input.metadata || {}), scope, transferable);
369
376
  if (defenceResult) {
370
377
  db.prepare(`UPDATE memories SET trust_score = ?, sensitivity_level = ?, source = ? WHERE id = ?`)
371
378
  .run(defenceResult.trust.score, defenceResult.sensitivity.level, `${source.type}:${source.identifier}`, result.lastInsertRowid);
@@ -379,6 +386,9 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
379
386
  persistEvent('memory_created', { memory });
380
387
  // Webhook notification (fire-and-forget)
381
388
  dispatchWebhook('memory_created', { id: memory.id, title: memory.title, category: memory.category });
389
+ if (isFeatureEnabled('cloud_sync')) {
390
+ syncMemoryUpsertToCloud(memory);
391
+ }
382
392
  // ORGANIC FEATURE: Auto-link to related memories
383
393
  // This builds the knowledge graph automatically as memories are created
384
394
  try {
@@ -396,6 +406,9 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
396
406
  const extraction = extractFromMemory(input.title, truncationResult.content, category);
397
407
  if (extraction.entities.length > 0) {
398
408
  processExtractionResult(extraction, memory.id);
409
+ if (isFeatureEnabled('cloud_sync')) {
410
+ syncGraphForMemoryToCloud(memory.id);
411
+ }
399
412
  }
400
413
  }
401
414
  catch (e) {
@@ -531,12 +544,28 @@ export function updateMemory(id, updates) {
531
544
  if (fields.length === 0)
532
545
  return existing;
533
546
  values.push(id);
534
- db.prepare(`UPDATE memories SET ${fields.join(', ')} WHERE id = ?`).run(...values);
547
+ db.prepare(`UPDATE memories SET ${fields.join(', ')}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(...values);
535
548
  const updatedMemory = getMemoryById(id);
549
+ const shouldRefreshGraph = updates.title !== undefined ||
550
+ updates.content !== undefined ||
551
+ updates.category !== undefined;
552
+ if (shouldRefreshGraph) {
553
+ try {
554
+ const extraction = extractFromMemory(updatedMemory.title, updatedMemory.content, updatedMemory.category);
555
+ replaceMemoryGraph(updatedMemory.id, extraction);
556
+ }
557
+ catch (e) {
558
+ console.error('[shieldcortex] Entity extraction refresh failed:', e);
559
+ }
560
+ }
536
561
  // Emit event for real-time dashboard (in-process)
537
562
  emitMemoryUpdated(updatedMemory);
538
563
  // Persist event for cross-process IPC (MCP → Dashboard)
539
564
  persistEvent('memory_updated', { memory: updatedMemory });
565
+ if (isFeatureEnabled('cloud_sync')) {
566
+ syncMemoryUpsertToCloud(updatedMemory);
567
+ syncGraphForMemoryToCloud(updatedMemory.id);
568
+ }
540
569
  return updatedMemory;
541
570
  }
542
571
  /**
@@ -557,9 +586,21 @@ export function deleteMemory(id, source) {
557
586
  }
558
587
  // Get memory before deletion for event
559
588
  const memory = getMemoryById(id);
589
+ if (memory) {
590
+ try {
591
+ removeMemoryGraph(id);
592
+ }
593
+ catch (e) {
594
+ console.error('[shieldcortex] Graph cleanup before delete failed:', e);
595
+ }
596
+ }
560
597
  const result = db.prepare('DELETE FROM memories WHERE id = ?').run(id);
561
598
  // Emit event for real-time dashboard (in-process)
562
599
  if (result.changes > 0 && memory) {
600
+ if (isFeatureEnabled('cloud_sync')) {
601
+ syncMemoryDeleteToCloud(memory);
602
+ syncGraphDeleteForMemoryToCloud(memory);
603
+ }
563
604
  emitMemoryDeleted(id, memory.title);
564
605
  // Persist event for cross-process IPC (MCP → Dashboard)
565
606
  persistEvent('memory_deleted', { memoryId: id, title: memory.title });
@@ -776,171 +817,6 @@ export function updateDecayScores() {
776
817
  }
777
818
  return updated;
778
819
  }
779
- /**
780
- * Detect the likely category a query is asking about
781
- */
782
- function detectQueryCategory(query) {
783
- const lower = query.toLowerCase();
784
- if (/architect|design|structure|pattern|system|schema|model/.test(lower)) {
785
- return 'architecture';
786
- }
787
- if (/error|bug|fix|issue|crash|exception|fail|problem/.test(lower)) {
788
- return 'error';
789
- }
790
- if (/prefer|always|never|style|convention|like|want/.test(lower)) {
791
- return 'preference';
792
- }
793
- if (/learn|discover|realiz|found\s+out|turns?\s+out/.test(lower)) {
794
- return 'learning';
795
- }
796
- if (/todo|task|pending|need\s+to|should\s+do/.test(lower)) {
797
- return 'todo';
798
- }
799
- if (/relation|depend|connect|link|reference/.test(lower)) {
800
- return 'relationship';
801
- }
802
- return null;
803
- }
804
- /**
805
- * Calculate a boost for memories linked to high-salience memories
806
- */
807
- function calculateLinkBoost(memoryId, db) {
808
- try {
809
- // Get linked memories and their salience
810
- const linked = db.prepare(`
811
- SELECT m.salience, ml.strength
812
- FROM memory_links ml
813
- JOIN memories m ON (m.id = ml.target_id OR m.id = ml.source_id)
814
- WHERE (ml.source_id = ? OR ml.target_id = ?)
815
- AND m.id != ?
816
- `).all(memoryId, memoryId, memoryId);
817
- if (linked.length === 0)
818
- return 0;
819
- // Calculate weighted average of linked memory salience
820
- const totalWeight = linked.reduce((sum, l) => sum + l.strength, 0);
821
- if (totalWeight === 0)
822
- return 0;
823
- const weightedSalience = linked.reduce((sum, l) => sum + l.salience * l.strength, 0) / totalWeight;
824
- // Cap boost at 0.15
825
- return Math.min(0.15, weightedSalience * 0.2);
826
- }
827
- catch {
828
- return 0;
829
- }
830
- }
831
- /**
832
- * Calculate partial tag match score
833
- */
834
- function calculateTagScore(queryTags, memoryTags) {
835
- if (queryTags.length === 0 || memoryTags.length === 0)
836
- return 0;
837
- // Count partial matches (substring matching)
838
- let matches = 0;
839
- for (const qt of queryTags) {
840
- const qtLower = qt.toLowerCase();
841
- if (memoryTags.some(mt => mt.toLowerCase().includes(qtLower) || qtLower.includes(mt.toLowerCase()))) {
842
- matches++;
843
- }
844
- }
845
- return (matches / queryTags.length) * 0.1;
846
- }
847
- /**
848
- * Extract potential tags from a query string
849
- */
850
- function extractQueryTags(query) {
851
- // Extract words that might be tags (tech terms, project-specific terms)
852
- const words = query.toLowerCase().split(/\s+/);
853
- return words.filter(w => w.length > 2 &&
854
- /^[a-z][a-z0-9-]*$/.test(w) &&
855
- !['the', 'and', 'for', 'with', 'how', 'what', 'when', 'where', 'why'].includes(w));
856
- }
857
- /**
858
- * Search memories by vector similarity
859
- * Returns memories sorted by cosine similarity to the query embedding
860
- */
861
- function vectorSearch(queryEmbedding, limit, project, includeGlobal = true) {
862
- const db = getDatabase();
863
- // Get memories with embeddings
864
- let query = `
865
- SELECT * FROM memories
866
- WHERE embedding IS NOT NULL
867
- `;
868
- const params = [];
869
- if (project && includeGlobal) {
870
- query += ` AND (project = ? OR scope = 'global')`;
871
- params.push(project);
872
- }
873
- else if (project) {
874
- query += ` AND project = ?`;
875
- params.push(project);
876
- }
877
- const rows = db.prepare(query).all(...params);
878
- // Calculate similarities
879
- const results = rows
880
- .map(row => {
881
- const embeddingBuffer = row.embedding;
882
- const embedding = new Float32Array(embeddingBuffer.buffer, embeddingBuffer.byteOffset, embeddingBuffer.length / 4);
883
- const similarity = cosineSimilarity(queryEmbedding, embedding);
884
- return {
885
- memory: rowToMemory(row),
886
- similarity,
887
- };
888
- })
889
- .filter(r => r.similarity > 0.3) // Threshold for relevance
890
- .sort((a, b) => b.similarity - a.similarity)
891
- .slice(0, limit);
892
- return results;
893
- }
894
- function buildSearchExplanation(memory, context, values) {
895
- const matchedTags = context.queryTags.filter((queryTag) => memory.tags.some((memoryTag) => {
896
- const lowerMemoryTag = memoryTag.toLowerCase();
897
- return lowerMemoryTag.includes(queryTag) || queryTag.includes(lowerMemoryTag);
898
- }));
899
- const reasons = [];
900
- if (values.vectorSimilarity > 0) {
901
- reasons.push(`Semantic similarity ${(values.vectorSimilarity * 100).toFixed(0)}%`);
902
- }
903
- if (values.ftsScore > 0.3) {
904
- reasons.push('Strong keyword match');
905
- }
906
- if (values.categoryBoost > 0 && context.detectedCategory) {
907
- reasons.push(`Matches ${context.detectedCategory} category intent`);
908
- }
909
- if (matchedTags.length > 0) {
910
- reasons.push(`Shared tags: ${matchedTags.slice(0, 3).join(', ')}`);
911
- }
912
- if (values.recencyBoost > 0) {
913
- reasons.push('Recently accessed');
914
- }
915
- if (values.linkBoost > 0) {
916
- reasons.push('Connected to related memories');
917
- }
918
- if (values.activationBoost > 0) {
919
- reasons.push('Activated by recent recall activity');
920
- }
921
- if (reasons.length === 0) {
922
- reasons.push('Ranked by salience and base recall heuristics');
923
- }
924
- return {
925
- query: context.query,
926
- reasons,
927
- breakdown: {
928
- ftsScore: values.ftsScore,
929
- vectorSimilarity: values.vectorSimilarity,
930
- vectorBoost: values.vectorBoost,
931
- decayedScore: values.decayedScore,
932
- priorityBoost: values.priorityBoost,
933
- recencyBoost: values.recencyBoost,
934
- categoryBoost: values.categoryBoost,
935
- linkBoost: values.linkBoost,
936
- tagBoost: values.tagBoost,
937
- activationBoost: values.activationBoost,
938
- finalScore: values.finalScore,
939
- matchedTags,
940
- matchedCategory: values.categoryBoost > 0 ? context.detectedCategory : null,
941
- },
942
- };
943
- }
944
820
  async function searchMemoriesInternal(options, config, source, execution) {
945
821
  if (++searchCount % 100 === 0) {
946
822
  pruneActivationCache();
@@ -958,7 +834,7 @@ async function searchMemoriesInternal(options, config, source, execution) {
958
834
  if (!queryEmbedding) {
959
835
  throw new Error('query embedding unavailable');
960
836
  }
961
- const vectorHits = vectorSearch(queryEmbedding, limit * 2, options.project, includeGlobal);
837
+ const vectorHits = vectorSearch(db, rowToMemory, queryEmbedding, limit * 2, options.project, includeGlobal);
962
838
  for (const hit of vectorHits) {
963
839
  vectorResults.set(hit.memory.id, hit.similarity);
964
840
  }
@@ -5,6 +5,7 @@ export type MemoryType = 'short_term' | 'long_term' | 'episodic';
5
5
  export type MemoryCategory = 'architecture' | 'pattern' | 'preference' | 'error' | 'context' | 'learning' | 'todo' | 'note' | 'relationship' | 'custom';
6
6
  export interface Memory {
7
7
  id: number;
8
+ uuid: string;
8
9
  type: MemoryType;
9
10
  category: MemoryCategory;
10
11
  title: string;
@@ -15,6 +16,7 @@ export interface Memory {
15
16
  accessCount: number;
16
17
  lastAccessed: Date;
17
18
  createdAt: Date;
19
+ updatedAt: Date;
18
20
  decayedScore: number;
19
21
  metadata: Record<string, unknown>;
20
22
  embedding?: Buffer;
@@ -7,6 +7,7 @@
7
7
  * - Windows: VBS script in Startup folder
8
8
  */
9
9
  export declare function installService(): Promise<void>;
10
+ export declare function repairService(): Promise<void>;
10
11
  export declare function uninstallService(options?: {
11
12
  cleanLogs?: boolean;
12
13
  }): Promise<void>;
@@ -31,6 +31,33 @@ function getServiceConfig() {
31
31
  logsDir,
32
32
  };
33
33
  }
34
+ function inspectServiceEntryPoint(platform, servicePath) {
35
+ if (!fs.existsSync(servicePath)) {
36
+ return { entryPoint: null, stale: false };
37
+ }
38
+ try {
39
+ const content = fs.readFileSync(servicePath, 'utf-8');
40
+ let entryPoint = null;
41
+ if (platform === 'macos') {
42
+ const matches = [...content.matchAll(/<string>([^<]+)<\/string>/g)].map((match) => match[1]);
43
+ entryPoint = matches.find((value) => value.endsWith('index.js')) ?? null;
44
+ }
45
+ else if (platform === 'linux') {
46
+ const match = content.match(/ExecStart=\S+\s+(\S+index\.js)/);
47
+ entryPoint = match?.[1] ?? null;
48
+ }
49
+ else {
50
+ const match = content.match(/Run\s+\"\"[^\"]+\"\"\s+\"\"([^\"]+index\.js)\"\"/);
51
+ entryPoint = match?.[1] ?? null;
52
+ }
53
+ const currentEntryPoint = getServiceConfig().entryPoint;
54
+ const stale = !!entryPoint && (entryPoint.includes('/.npm/_npx/') || entryPoint !== currentEntryPoint);
55
+ return { entryPoint, stale };
56
+ }
57
+ catch {
58
+ return { entryPoint: null, stale: false };
59
+ }
60
+ }
34
61
  function getServicePath(platform) {
35
62
  switch (platform) {
36
63
  case 'macos':
@@ -91,6 +118,10 @@ export async function installService() {
91
118
  console.log(` API: http://localhost:3001`);
92
119
  console.log(` Dashboard: http://localhost:3030`);
93
120
  }
121
+ export async function repairService() {
122
+ await uninstallService();
123
+ await installService();
124
+ }
94
125
  function cleanLogsDirectory() {
95
126
  const logsDir = path.join(os.homedir(), '.shieldcortex', 'logs');
96
127
  if (fs.existsSync(logsDir)) {
@@ -131,9 +162,14 @@ export async function serviceStatus() {
131
162
  const platform = detectPlatform();
132
163
  const servicePath = getServicePath(platform);
133
164
  const installed = fs.existsSync(servicePath);
165
+ const inspection = inspectServiceEntryPoint(platform, servicePath);
134
166
  console.log(`Platform: ${platform}`);
135
167
  console.log(`Installed: ${installed ? 'yes' : 'no'}`);
136
168
  console.log(`Path: ${servicePath}`);
169
+ if (inspection.entryPoint) {
170
+ console.log(`Entry: ${inspection.entryPoint}`);
171
+ console.log(`Healthy: ${inspection.stale ? 'no (repair recommended)' : 'yes'}`);
172
+ }
137
173
  if (!installed)
138
174
  return;
139
175
  // Check if running
@@ -158,12 +194,18 @@ export async function serviceStatus() {
158
194
  catch {
159
195
  console.log('Running: no');
160
196
  }
197
+ if (inspection.stale) {
198
+ console.log('Repair: shieldcortex service repair');
199
+ }
161
200
  }
162
201
  export async function handleServiceCommand(subcommand) {
163
202
  switch (subcommand) {
164
203
  case 'install':
165
204
  await installService();
166
205
  break;
206
+ case 'repair':
207
+ await repairService();
208
+ break;
167
209
  case 'uninstall':
168
210
  await uninstallService({ cleanLogs: process.argv.includes('--clean-logs') });
169
211
  break;
@@ -171,7 +213,7 @@ export async function handleServiceCommand(subcommand) {
171
213
  await serviceStatus();
172
214
  break;
173
215
  default:
174
- console.log('Usage: shieldcortex service <install|uninstall|status>');
216
+ console.log('Usage: shieldcortex service <install|repair|uninstall|status>');
175
217
  process.exit(1);
176
218
  }
177
219
  }
@@ -48,9 +48,9 @@ export declare const recallSchema: z.ZodObject<{
48
48
  project?: string | undefined;
49
49
  mode?: "search" | "important" | "recent" | undefined;
50
50
  type?: "short_term" | "long_term" | "episodic" | undefined;
51
- limit?: number | undefined;
52
51
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
53
52
  tags?: string[] | undefined;
53
+ limit?: number | undefined;
54
54
  query?: string | undefined;
55
55
  includeDecayed?: boolean | undefined;
56
56
  includeGlobal?: boolean | undefined;