neo4j-agent-memory 0.3.17

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.
@@ -0,0 +1,555 @@
1
+ // Parameters expected at runtime (driver):
2
+ // $nowIso, $symptoms, $tags, $env, $agentId, $caseLimit, $fixLimit, $dontLimit, $halfLifeSeconds
3
+ WITH
4
+ datetime(coalesce($nowIso, toString(datetime()))) AS now,
5
+ coalesce($symptoms, []) AS qSymptoms,
6
+ coalesce($tags, []) AS qTags,
7
+ coalesce($env, {}) AS qEnv,
8
+ coalesce($agentId, "") AS agentId,
9
+ coalesce($caseLimit, 10) AS caseLimit,
10
+ coalesce($fixLimit, 8) AS fixLimit,
11
+ coalesce($dontLimit, 5) AS dontLimit,
12
+ coalesce($halfLifeSeconds, 86400.0) AS halfLifeSeconds,
13
+ 0.001 AS aMin,
14
+ 0.001 AS bMin,
15
+ 0.15 AS affK
16
+
17
+ // 1) Rank cases by symptom match + environment fingerprint similarity.
18
+ CALL (now, qSymptoms, qTags, qEnv, caseLimit) {
19
+ WITH now, qSymptoms, qTags, qEnv, caseLimit
20
+ OPTIONAL MATCH (c:Case)
21
+ WITH now, qSymptoms, qTags, qEnv, caseLimit, collect(c) AS cases
22
+ WITH
23
+ now,
24
+ qSymptoms,
25
+ qTags,
26
+ qEnv,
27
+ caseLimit,
28
+ CASE
29
+ WHEN size(cases) = 0 THEN [null]
30
+ ELSE cases
31
+ END AS cases2
32
+ UNWIND cases2 AS c
33
+
34
+ OPTIONAL MATCH (c)-[:HAS_SYMPTOM]->(sAll:Symptom)
35
+ WITH
36
+ now,
37
+ qSymptoms,
38
+ qEnv,
39
+ caseLimit,
40
+ c,
41
+ collect(DISTINCT sAll.text) AS caseSymptoms
42
+ WITH
43
+ now,
44
+ qSymptoms,
45
+ qEnv,
46
+ caseLimit,
47
+ c,
48
+ caseSymptoms,
49
+ [t IN qSymptoms WHERE t IN caseSymptoms] AS matched
50
+ WITH
51
+ now,
52
+ qEnv,
53
+ caseLimit,
54
+ c,
55
+ matched,
56
+ caseSymptoms,
57
+ qSymptoms,
58
+ CASE
59
+ WHEN size(qSymptoms) = 0 OR size(caseSymptoms) = 0 THEN 0.0
60
+ ELSE
61
+ (1.0 * size(matched)) /
62
+ toFloat(size(qSymptoms) + size(caseSymptoms) - size(matched))
63
+ END AS symptomScore
64
+
65
+ OPTIONAL MATCH (c)-[:IN_ENV]->(e:EnvironmentFingerprint)
66
+ WITH
67
+ c,
68
+ symptomScore,
69
+ qEnv,
70
+ caseLimit,
71
+ e,
72
+ (CASE
73
+ WHEN e IS NULL OR qEnv.os IS NULL OR e.os IS NULL THEN 0.5
74
+ WHEN e.os = qEnv.os THEN 1.0
75
+ ELSE 0.0
76
+ END) AS sOs,
77
+ (CASE
78
+ WHEN e IS NULL OR qEnv.ci IS NULL OR e.ci IS NULL THEN 0.5
79
+ WHEN e.ci = qEnv.ci THEN 1.0
80
+ ELSE 0.0
81
+ END) AS sCi,
82
+ (CASE
83
+ WHEN e IS NULL OR qEnv.container IS NULL OR e.container IS NULL THEN 0.5
84
+ WHEN e.container = qEnv.container THEN 1.0
85
+ ELSE 0.0
86
+ END) AS sContainer,
87
+ (CASE
88
+ WHEN
89
+ e IS NULL OR qEnv.filesystem IS NULL OR e.filesystem IS NULL
90
+ THEN 0.5
91
+ WHEN e.filesystem = qEnv.filesystem THEN 1.0
92
+ ELSE 0.0
93
+ END) AS sFs,
94
+ (CASE
95
+ WHEN
96
+ e IS NULL OR qEnv.workspaceMount IS NULL OR e.workspaceMount IS NULL
97
+ THEN 0.5
98
+ WHEN e.workspaceMount = qEnv.workspaceMount THEN 1.0
99
+ ELSE 0.0
100
+ END) AS sMount
101
+
102
+ WITH
103
+ c,
104
+ symptomScore,
105
+ caseLimit,
106
+ (sOs + sCi + sContainer + sFs + sMount) / 5.0 AS envScore
107
+ WITH c, (0.75 * symptomScore + 0.25 * envScore) AS caseScore, caseLimit
108
+
109
+ WITH collect({c: c, score: caseScore}) AS rows, caseLimit
110
+ WITH [r IN rows WHERE r.c IS NOT NULL] AS rows, caseLimit
111
+ WITH rows[0..caseLimit] AS topRows
112
+ RETURN [r IN topRows | r.c] AS topCases, [r IN topRows | r.score] AS topScores
113
+ }
114
+
115
+ // 2) Retrieve FIX memories from top cases.
116
+ CALL (
117
+ now,
118
+ qTags,
119
+ qEnv,
120
+ topCases,
121
+ topScores,
122
+ aMin,
123
+ bMin,
124
+ affK,
125
+ agentId,
126
+ halfLifeSeconds,
127
+ fixLimit
128
+ ) {
129
+ WITH
130
+ now,
131
+ qTags,
132
+ qEnv,
133
+ topCases,
134
+ topScores,
135
+ aMin,
136
+ bMin,
137
+ affK,
138
+ agentId,
139
+ halfLifeSeconds,
140
+ fixLimit
141
+ WITH
142
+ now,
143
+ qTags,
144
+ qEnv,
145
+ topCases,
146
+ topScores,
147
+ aMin,
148
+ bMin,
149
+ affK,
150
+ agentId,
151
+ halfLifeSeconds,
152
+ fixLimit,
153
+ CASE
154
+ WHEN size(topCases) = 0 THEN [null]
155
+ ELSE range(0, size(topCases) - 1)
156
+ END AS idxs
157
+ UNWIND idxs AS i
158
+ WITH
159
+ now,
160
+ qTags,
161
+ topCases,
162
+ topScores,
163
+ aMin,
164
+ bMin,
165
+ affK,
166
+ agentId,
167
+ halfLifeSeconds,
168
+ fixLimit,
169
+ CASE
170
+ WHEN i IS NULL THEN null
171
+ ELSE topCases[i]
172
+ END AS c,
173
+ CASE
174
+ WHEN i IS NULL THEN 0.0
175
+ ELSE topScores[i]
176
+ END AS cs
177
+
178
+ OPTIONAL MATCH (c)-[:RESOLVED_BY]->(m:Memory)
179
+ WHERE coalesce(m.polarity, "positive") = "positive"
180
+ WITH
181
+ now,
182
+ qTags,
183
+ aMin,
184
+ bMin,
185
+ affK,
186
+ agentId,
187
+ halfLifeSeconds,
188
+ fixLimit,
189
+ m,
190
+ max(cs) AS caseContribution
191
+
192
+ OPTIONAL MATCH (a:Agent {id: agentId})-[r:RECALLS]->(m)
193
+ WITH
194
+ now,
195
+ qTags,
196
+ aMin,
197
+ bMin,
198
+ affK,
199
+ halfLifeSeconds,
200
+ fixLimit,
201
+ m,
202
+ caseContribution,
203
+ CASE
204
+ WHEN
205
+ duration.inSeconds(coalesce(r.updatedAt, now), now).seconds < 0
206
+ THEN 0
207
+ ELSE duration.inSeconds(coalesce(r.updatedAt, now), now).seconds
208
+ END AS dt,
209
+ coalesce(
210
+ r.a,
211
+ CASE
212
+ WHEN aMin > coalesce(r.strength, 0.5) * 2.0 THEN aMin
213
+ ELSE coalesce(r.strength, 0.5) * 2.0
214
+ END
215
+ ) AS aPrev,
216
+ coalesce(
217
+ r.b,
218
+ CASE
219
+ WHEN bMin > (1.0 - coalesce(r.strength, 0.5)) * 2.0 THEN bMin
220
+ ELSE (1.0 - coalesce(r.strength, 0.5)) * 2.0
221
+ END
222
+ ) AS bPrev
223
+
224
+ WITH
225
+ now,
226
+ qTags,
227
+ aMin,
228
+ bMin,
229
+ affK,
230
+ fixLimit,
231
+ m,
232
+ caseContribution,
233
+ aPrev,
234
+ bPrev,
235
+ 0.5 ^ (dt / halfLifeSeconds) AS gamma
236
+
237
+ WITH
238
+ now,
239
+ qTags,
240
+ aMin,
241
+ bMin,
242
+ affK,
243
+ fixLimit,
244
+ m,
245
+ caseContribution,
246
+ (aMin + gamma * (aPrev - aMin)) AS a0,
247
+ (bMin + gamma * (bPrev - bMin)) AS b0
248
+
249
+ WITH
250
+ now,
251
+ qTags,
252
+ affK,
253
+ fixLimit,
254
+ m,
255
+ caseContribution,
256
+ (CASE
257
+ WHEN (a0 + b0) <= 0 THEN 0.0
258
+ ELSE a0 / (a0 + b0)
259
+ END) AS affinityMean,
260
+ (a0 + b0) AS affinityEvidence
261
+
262
+ WITH
263
+ now,
264
+ qTags,
265
+ fixLimit,
266
+ m,
267
+ caseContribution,
268
+ affinityMean * (1 - exp(- affK * affinityEvidence)) AS affinity,
269
+ coalesce(m.utility, 0.2) AS utility,
270
+ coalesce(m.confidence, 0.7) AS confidence,
271
+ exp(
272
+ -
273
+ (CASE
274
+ WHEN
275
+ duration.inSeconds(coalesce(m.updatedAt, now), now).seconds < 0
276
+ THEN 0
277
+ ELSE duration.inSeconds(coalesce(m.updatedAt, now), now).seconds
278
+ END) /
279
+ (14.0 * 24 * 3600.0)) AS recency,
280
+ coalesce(m.tags, []) AS mTags,
281
+ [t IN qTags WHERE t IN coalesce(m.tags, [])] AS tMatched
282
+
283
+ // --- split tagScore and fixScore into separate WITH clauses (Cypher rule) ---
284
+ WITH
285
+ m,
286
+ caseContribution,
287
+ affinity,
288
+ utility,
289
+ confidence,
290
+ recency,
291
+ qTags,
292
+ mTags,
293
+ tMatched,
294
+ fixLimit,
295
+ CASE
296
+ WHEN size(qTags) = 0 OR size(mTags) = 0 THEN 0.0
297
+ ELSE
298
+ (1.0 * size(tMatched)) /
299
+ toFloat(size(qTags) + size(mTags) - size(tMatched))
300
+ END AS tagScore
301
+
302
+ WITH
303
+ m,
304
+ fixLimit,
305
+ (0.42 * caseContribution +
306
+ 0.20 * affinity +
307
+ 0.13 * utility +
308
+ 0.13 * confidence +
309
+ 0.05 * recency +
310
+ 0.07 * tagScore) AS fixScore
311
+ ORDER BY fixScore DESC
312
+ WITH
313
+ collect(
314
+ DISTINCT m {
315
+ .id,
316
+ .kind,
317
+ .polarity,
318
+ .title,
319
+ .content,
320
+ .utility,
321
+ .confidence,
322
+ .updatedAt,
323
+ tags: coalesce(m.tags, [])
324
+ }) AS collected,
325
+ fixLimit
326
+ WITH [x IN collected WHERE x IS NOT NULL][0..fixLimit] AS fixes
327
+ RETURN fixes AS fixes
328
+ }
329
+
330
+ // 3) Retrieve DO-NOT-DO memories from top cases.
331
+ CALL (
332
+ now,
333
+ qTags,
334
+ topCases,
335
+ topScores,
336
+ aMin,
337
+ bMin,
338
+ affK,
339
+ agentId,
340
+ halfLifeSeconds,
341
+ dontLimit,
342
+ fixes
343
+ ) {
344
+ WITH
345
+ now,
346
+ qTags,
347
+ topCases,
348
+ topScores,
349
+ aMin,
350
+ bMin,
351
+ affK,
352
+ agentId,
353
+ halfLifeSeconds,
354
+ dontLimit,
355
+ fixes
356
+ WITH
357
+ now,
358
+ qTags,
359
+ topCases,
360
+ topScores,
361
+ aMin,
362
+ bMin,
363
+ affK,
364
+ agentId,
365
+ halfLifeSeconds,
366
+ dontLimit,
367
+ fixes,
368
+ CASE
369
+ WHEN size(topCases) = 0 THEN [null]
370
+ ELSE range(0, size(topCases) - 1)
371
+ END AS idxs
372
+ UNWIND idxs AS j
373
+ WITH
374
+ now,
375
+ qTags,
376
+ aMin,
377
+ bMin,
378
+ affK,
379
+ agentId,
380
+ halfLifeSeconds,
381
+ dontLimit,
382
+ fixes,
383
+ CASE
384
+ WHEN j IS NULL THEN null
385
+ ELSE topCases[j]
386
+ END AS c2,
387
+ CASE
388
+ WHEN j IS NULL THEN 0.0
389
+ ELSE topScores[j]
390
+ END AS cs2
391
+
392
+ OPTIONAL MATCH (c2)-[:HAS_NEGATIVE]->(n:Memory)
393
+ WHERE coalesce(n.polarity, "negative") = "negative"
394
+ WITH
395
+ now,
396
+ qTags,
397
+ aMin,
398
+ bMin,
399
+ affK,
400
+ agentId,
401
+ halfLifeSeconds,
402
+ dontLimit,
403
+ fixes,
404
+ n,
405
+ max(cs2) AS caseContribution2
406
+
407
+ OPTIONAL MATCH (a:Agent {id: agentId})-[r2:RECALLS]->(n)
408
+ WITH
409
+ now,
410
+ qTags,
411
+ aMin,
412
+ bMin,
413
+ affK,
414
+ halfLifeSeconds,
415
+ dontLimit,
416
+ fixes,
417
+ n,
418
+ caseContribution2,
419
+ CASE
420
+ WHEN
421
+ duration.inSeconds(coalesce(r2.updatedAt, now), now).seconds < 0
422
+ THEN 0
423
+ ELSE duration.inSeconds(coalesce(r2.updatedAt, now), now).seconds
424
+ END AS dt2,
425
+ coalesce(
426
+ r2.a,
427
+ CASE
428
+ WHEN aMin > coalesce(r2.strength, 0.5) * 2.0 THEN aMin
429
+ ELSE coalesce(r2.strength, 0.5) * 2.0
430
+ END
431
+ ) AS aPrev2,
432
+ coalesce(
433
+ r2.b,
434
+ CASE
435
+ WHEN bMin > (1.0 - coalesce(r2.strength, 0.5)) * 2.0 THEN bMin
436
+ ELSE (1.0 - coalesce(r2.strength, 0.5)) * 2.0
437
+ END
438
+ ) AS bPrev2
439
+
440
+ WITH
441
+ now,
442
+ qTags,
443
+ aMin,
444
+ bMin,
445
+ affK,
446
+ dontLimit,
447
+ fixes,
448
+ n,
449
+ caseContribution2,
450
+ aPrev2,
451
+ bPrev2,
452
+ 0.5 ^ (dt2 / halfLifeSeconds) AS gamma2
453
+
454
+ WITH
455
+ now,
456
+ qTags,
457
+ aMin,
458
+ bMin,
459
+ affK,
460
+ dontLimit,
461
+ fixes,
462
+ n,
463
+ caseContribution2,
464
+ (aMin + gamma2 * (aPrev2 - aMin)) AS a02,
465
+ (bMin + gamma2 * (bPrev2 - bMin)) AS b02
466
+
467
+ WITH
468
+ now,
469
+ qTags,
470
+ affK,
471
+ dontLimit,
472
+ fixes,
473
+ n,
474
+ caseContribution2,
475
+ (CASE
476
+ WHEN (a02 + b02) <= 0 THEN 0.0
477
+ ELSE a02 / (a02 + b02)
478
+ END) AS affinityMean2,
479
+ (a02 + b02) AS affinityEvidence2
480
+
481
+ WITH
482
+ now,
483
+ qTags,
484
+ dontLimit,
485
+ fixes,
486
+ n,
487
+ caseContribution2,
488
+ affinityMean2 * (1 - exp(- affK * affinityEvidence2)) AS affinity2,
489
+ coalesce(n.utility, 0.2) AS utility2,
490
+ coalesce(n.confidence, 0.7) AS confidence2,
491
+ exp(
492
+ -
493
+ (CASE
494
+ WHEN
495
+ duration.inSeconds(coalesce(n.updatedAt, now), now).seconds < 0
496
+ THEN 0
497
+ ELSE duration.inSeconds(coalesce(n.updatedAt, now), now).seconds
498
+ END) /
499
+ (21.0 * 24 * 3600.0)) AS recency2,
500
+ coalesce(n.tags, []) AS nTags,
501
+ [t IN qTags WHERE t IN coalesce(n.tags, [])] AS tMatched2
502
+
503
+ // --- split tagScore2 and dontScore into separate WITH clauses (Cypher rule) ---
504
+ WITH
505
+ fixes,
506
+ n,
507
+ caseContribution2,
508
+ affinity2,
509
+ utility2,
510
+ confidence2,
511
+ recency2,
512
+ qTags,
513
+ nTags,
514
+ tMatched2,
515
+ dontLimit,
516
+ CASE
517
+ WHEN size(qTags) = 0 OR size(nTags) = 0 THEN 0.0
518
+ ELSE
519
+ (1.0 * size(tMatched2)) /
520
+ toFloat(size(qTags) + size(nTags) - size(tMatched2))
521
+ END AS tagScore2
522
+
523
+ WITH
524
+ fixes,
525
+ n,
526
+ dontLimit,
527
+ (0.48 * caseContribution2 +
528
+ 0.15 * affinity2 +
529
+ 0.10 * utility2 +
530
+ 0.15 * confidence2 +
531
+ 0.05 * recency2 +
532
+ 0.07 * tagScore2) AS dontScore
533
+ ORDER BY dontScore DESC
534
+ WITH
535
+ collect(
536
+ DISTINCT n {
537
+ .id,
538
+ .kind,
539
+ .polarity,
540
+ .title,
541
+ .content,
542
+ .utility,
543
+ .confidence,
544
+ .updatedAt,
545
+ tags: coalesce(n.tags, [])
546
+ }) AS collected,
547
+ dontLimit,
548
+ fixes
549
+
550
+ WITH fixes, [x IN collected WHERE x IS NOT NULL][0..dontLimit] AS doNot
551
+ RETURN doNot AS doNot
552
+
553
+ }
554
+
555
+ RETURN {fixes: fixes, doNot: doNot} AS sections;
@@ -0,0 +1,20 @@
1
+ CREATE CONSTRAINT case_id_unique IF NOT EXISTS
2
+ FOR (c:Case) REQUIRE c.id IS UNIQUE;
3
+
4
+ CREATE CONSTRAINT symptom_text_unique IF NOT EXISTS
5
+ FOR (s:Symptom) REQUIRE s.text IS UNIQUE;
6
+
7
+ CREATE CONSTRAINT env_hash_unique IF NOT EXISTS
8
+ FOR (e:EnvironmentFingerprint) REQUIRE e.hash IS UNIQUE;
9
+
10
+ CREATE CONSTRAINT memory_id_unique IF NOT EXISTS
11
+ FOR (m:Memory) REQUIRE m.id IS UNIQUE;
12
+
13
+ CREATE INDEX memory_polarity IF NOT EXISTS
14
+ FOR (m:Memory) ON (m.polarity);
15
+
16
+ CREATE INDEX memory_kind IF NOT EXISTS
17
+ FOR (m:Memory) ON (m.kind);
18
+
19
+ CREATE CONSTRAINT agent_id_unique IF NOT EXISTS
20
+ FOR (a:Agent) REQUIRE a.id IS UNIQUE;
@@ -0,0 +1,61 @@
1
+ // Parameters:
2
+ // - $caseId: Unique case identifier string
3
+ // - $title: Case title string
4
+ // - $summary: Case summary/description string
5
+ // - $outcome: Case outcome/resolution string
6
+ // - $symptoms: Array of symptom strings
7
+ // - $env: Environment fingerprint object {hash, os, distro, ci, container, filesystem, workspaceMount, nodeVersion, packageManager, pmVersion}
8
+ // - $resolvedByMemoryIds: Array of memory IDs that resolved this case
9
+ // - $negativeMemoryIds: Array of negative memory IDs (anti-patterns) associated with this case
10
+ // - $resolvedAtIso: ISO timestamp string when case was resolved (or null)
11
+ MERGE (c:Case { id: $caseId })
12
+ ON CREATE SET
13
+ c.title = $title,
14
+ c.summary = $summary,
15
+ c.outcome = $outcome,
16
+ c.createdAt = datetime(),
17
+ c.resolvedAt = CASE WHEN $resolvedAtIso IS NULL THEN null ELSE datetime($resolvedAtIso) END
18
+ ON MATCH SET
19
+ c.title = $title,
20
+ c.summary = $summary,
21
+ c.outcome = $outcome,
22
+ c.resolvedAt = CASE WHEN $resolvedAtIso IS NULL THEN c.resolvedAt ELSE datetime($resolvedAtIso) END
23
+ WITH c
24
+ FOREACH (symptomText IN $symptoms |
25
+ MERGE (s:Symptom { text: symptomText })
26
+ MERGE (c)-[:HAS_SYMPTOM]->(s)
27
+ )
28
+ WITH c
29
+ MERGE (e:EnvironmentFingerprint { hash: $env.hash })
30
+ ON CREATE SET
31
+ e.os = $env.os,
32
+ e.distro = $env.distro,
33
+ e.ci = $env.ci,
34
+ e.container = $env.container,
35
+ e.filesystem = $env.filesystem,
36
+ e.workspaceMount = $env.workspaceMount,
37
+ e.nodeVersion = $env.nodeVersion,
38
+ e.packageManager = $env.packageManager,
39
+ e.pmVersion = $env.pmVersion
40
+ ON MATCH SET
41
+ e.os = coalesce($env.os, e.os),
42
+ e.distro = coalesce($env.distro, e.distro),
43
+ e.ci = coalesce($env.ci, e.ci),
44
+ e.container = coalesce($env.container, e.container),
45
+ e.filesystem = coalesce($env.filesystem, e.filesystem),
46
+ e.workspaceMount = coalesce($env.workspaceMount, e.workspaceMount),
47
+ e.nodeVersion = coalesce($env.nodeVersion, e.nodeVersion),
48
+ e.packageManager = coalesce($env.packageManager, e.packageManager),
49
+ e.pmVersion = coalesce($env.pmVersion, e.pmVersion)
50
+ MERGE (c)-[:IN_ENV]->(e)
51
+ WITH c
52
+ FOREACH (mid IN $resolvedByMemoryIds |
53
+ MERGE (m:Memory { id: mid })
54
+ MERGE (c)-[:RESOLVED_BY]->(m)
55
+ )
56
+ WITH c
57
+ FOREACH (nid IN $negativeMemoryIds |
58
+ MERGE (n:Memory { id: nid })
59
+ MERGE (c)-[:HAS_NEGATIVE]->(n)
60
+ )
61
+ RETURN c.id AS caseId;
@@ -0,0 +1,38 @@
1
+ // Parameters:
2
+ // - $id: Unique memory identifier string
3
+ // - $kind: Memory type ("semantic", "procedural", or "episodic")
4
+ // - $polarity: Memory polarity ("positive" or "negative")
5
+ // - $title: Memory title string
6
+ // - $content: Memory content/description string
7
+ // - $contentHash: SHA-256 hash of canonical content for deduplication
8
+ // - $tags: Array of tag strings
9
+ // - $confidence: Confidence score (0.0 to 1.0)
10
+ // - $utility: Utility score (0.0 to 1.0)
11
+ // - $triage: Optional triage object (JSON)
12
+ // - $antiPattern: Optional antiPattern object (JSON)
13
+ MERGE (m:Memory { id: $id })
14
+ ON CREATE SET
15
+ m.kind = $kind,
16
+ m.polarity = $polarity,
17
+ m.title = $title,
18
+ m.content = $content,
19
+ m.contentHash = $contentHash,
20
+ m.tags = $tags,
21
+ m.confidence = $confidence,
22
+ m.utility = $utility,
23
+ m.triage = $triage,
24
+ m.antiPattern = $antiPattern,
25
+ m.createdAt = datetime(),
26
+ m.updatedAt = datetime()
27
+ ON MATCH SET
28
+ m.kind = $kind,
29
+ m.polarity = $polarity,
30
+ m.title = $title,
31
+ m.content = $content,
32
+ m.tags = $tags,
33
+ m.confidence = $confidence,
34
+ m.utility = $utility,
35
+ m.triage = $triage,
36
+ m.antiPattern = $antiPattern,
37
+ m.updatedAt = datetime()
38
+ RETURN m.id AS id;