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.
- package/README.md +161 -0
- package/dist/cypher/feedback_batch.cypher +42 -0
- package/dist/cypher/feedback_co_used_with_batch.cypher +34 -0
- package/dist/cypher/index.ts +23 -0
- package/dist/cypher/list_memories.cypher +39 -0
- package/dist/cypher/relate_concepts.cypher +13 -0
- package/dist/cypher/retrieve_context_bundle.cypher +555 -0
- package/dist/cypher/schema.cypher +20 -0
- package/dist/cypher/upsert_case.cypher +61 -0
- package/dist/cypher/upsert_memory.cypher +38 -0
- package/dist/index.cjs +789 -0
- package/dist/index.d.ts +320 -0
- package/dist/index.js +738 -0
- package/package.json +49 -0
|
@@ -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;
|