@ryuenn3123/agentic-senior-core 2.0.26 → 2.0.27
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/.agent-context/state/benchmark-evidence-bundle.json +672 -22
- package/.agent-context/state/benchmark-history.json +75 -0
- package/.agent-context/state/benchmark-trend-report.csv +5 -0
- package/.agent-context/state/benchmark-trend-report.json +140 -0
- package/.agent-context/state/benchmark-watchlist.json +3 -3
- package/.agent-context/state/memory-adapter-contract.json +52 -0
- package/.agent-context/state/memory-continuity-benchmark.json +132 -0
- package/.agent-context/state/memory-schema-v1.json +88 -0
- package/.cursorrules +1 -1
- package/.windsurfrules +1 -1
- package/README.md +22 -0
- package/lib/cli/memory-continuity.mjs +266 -0
- package/package.json +2 -1
- package/scripts/benchmark-evidence-bundle.mjs +493 -16
- package/scripts/memory-continuity-benchmark.mjs +322 -0
- package/scripts/validate.mjs +3 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-agent memory continuity utilities.
|
|
3
|
+
* Provides provider-agnostic observation normalization, privacy redaction,
|
|
4
|
+
* lightweight indexing, and selective hydration helpers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const PRIVATE_BLOCK_PATTERN = /<private>[\s\S]*?<\/private>/gi;
|
|
8
|
+
|
|
9
|
+
const INLINE_SENSITIVE_PATTERNS = [
|
|
10
|
+
{
|
|
11
|
+
reason: 'api-key-like-value',
|
|
12
|
+
pattern: /\b(api[_-]?key)\b\s*[:=]\s*[^\s,;]+/gi,
|
|
13
|
+
replacer: (_match, fieldName) => `${fieldName}=[REDACTED]`,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
reason: 'token-like-value',
|
|
17
|
+
pattern: /\b(token)\b\s*[:=]\s*[^\s,;]+/gi,
|
|
18
|
+
replacer: (_match, fieldName) => `${fieldName}=[REDACTED]`,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
reason: 'password-like-value',
|
|
22
|
+
pattern: /\b(password|passwd|pwd)\b\s*[:=]\s*[^\s,;]+/gi,
|
|
23
|
+
replacer: (_match, fieldName) => `${fieldName}=[REDACTED]`,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
reason: 'bearer-token',
|
|
27
|
+
pattern: /\bBearer\s+[A-Za-z0-9._-]+/g,
|
|
28
|
+
replacer: () => 'Bearer [REDACTED]',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const MEMORY_SCHEMA_VERSION = '1.0.0';
|
|
33
|
+
|
|
34
|
+
export const SUPPORTED_MEMORY_ADAPTER_IDS = Object.freeze([
|
|
35
|
+
'claude-code',
|
|
36
|
+
'gemini-cli',
|
|
37
|
+
'vscode-chat',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export const SUPPORTED_MEMORY_EVENT_TYPES = Object.freeze([
|
|
41
|
+
'prompt',
|
|
42
|
+
'tool-use',
|
|
43
|
+
'decision',
|
|
44
|
+
'summary',
|
|
45
|
+
'issue',
|
|
46
|
+
'context',
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
function toIsoTimestamp(rawValue) {
|
|
50
|
+
if (typeof rawValue !== 'string' || rawValue.trim().length === 0) {
|
|
51
|
+
return new Date().toISOString();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parsedDate = new Date(rawValue);
|
|
55
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
56
|
+
return new Date().toISOString();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return parsedDate.toISOString();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toNonEmptyString(rawValue, fallbackValue = '') {
|
|
63
|
+
if (typeof rawValue !== 'string') {
|
|
64
|
+
return fallbackValue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const normalizedValue = rawValue.trim();
|
|
68
|
+
return normalizedValue.length > 0 ? normalizedValue : fallbackValue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeTags(rawTags) {
|
|
72
|
+
if (!Array.isArray(rawTags)) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const tagSet = new Set();
|
|
77
|
+
for (const rawTag of rawTags) {
|
|
78
|
+
const normalizedTag = toNonEmptyString(String(rawTag || '')).toLowerCase();
|
|
79
|
+
if (normalizedTag) {
|
|
80
|
+
tagSet.add(normalizedTag);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return Array.from(tagSet);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function estimateTokenUsage(rawText = '') {
|
|
88
|
+
const normalizedText = String(rawText || '');
|
|
89
|
+
return Math.max(1, Math.ceil(normalizedText.length / 4));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function redactSensitiveMemoryText(rawText = '') {
|
|
93
|
+
let normalizedText = String(rawText || '');
|
|
94
|
+
const redactionReasons = new Set();
|
|
95
|
+
let privateTagRedactionCount = 0;
|
|
96
|
+
let inlineRedactionCount = 0;
|
|
97
|
+
|
|
98
|
+
normalizedText = normalizedText.replace(PRIVATE_BLOCK_PATTERN, () => {
|
|
99
|
+
privateTagRedactionCount += 1;
|
|
100
|
+
redactionReasons.add('private-tag');
|
|
101
|
+
return '[REDACTED_PRIVATE_BLOCK]';
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
for (const sensitivePattern of INLINE_SENSITIVE_PATTERNS) {
|
|
105
|
+
normalizedText = normalizedText.replace(sensitivePattern.pattern, (...replacerArguments) => {
|
|
106
|
+
inlineRedactionCount += 1;
|
|
107
|
+
redactionReasons.add(sensitivePattern.reason);
|
|
108
|
+
return sensitivePattern.replacer(...replacerArguments);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
redactedText: normalizedText,
|
|
114
|
+
wasRedacted: privateTagRedactionCount > 0 || inlineRedactionCount > 0,
|
|
115
|
+
privateTagRedactionCount,
|
|
116
|
+
inlineRedactionCount,
|
|
117
|
+
redactionReasons: Array.from(redactionReasons),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function normalizeMemoryObservation(rawObservation, options = {}) {
|
|
122
|
+
const fallbackAdapterId = options.fallbackAdapterId || 'unknown-adapter';
|
|
123
|
+
const observationId = toNonEmptyString(rawObservation?.id, `${fallbackAdapterId}-${Date.now()}`);
|
|
124
|
+
const adapterId = toNonEmptyString(rawObservation?.adapterId, fallbackAdapterId);
|
|
125
|
+
|
|
126
|
+
const eventTypeCandidate = toNonEmptyString(rawObservation?.eventType, 'context').toLowerCase();
|
|
127
|
+
const eventType = SUPPORTED_MEMORY_EVENT_TYPES.includes(eventTypeCandidate)
|
|
128
|
+
? eventTypeCandidate
|
|
129
|
+
: 'context';
|
|
130
|
+
|
|
131
|
+
const rawDetail = toNonEmptyString(rawObservation?.detail, '');
|
|
132
|
+
const detailRedaction = redactSensitiveMemoryText(rawDetail);
|
|
133
|
+
|
|
134
|
+
const rawSummary = toNonEmptyString(rawObservation?.summary, detailRedaction.redactedText.slice(0, 220));
|
|
135
|
+
const summaryRedaction = redactSensitiveMemoryText(rawSummary);
|
|
136
|
+
|
|
137
|
+
const title = toNonEmptyString(rawObservation?.title, `${eventType} from ${adapterId}`);
|
|
138
|
+
const tags = normalizeTags(rawObservation?.tags);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
id: observationId,
|
|
142
|
+
projectId: toNonEmptyString(rawObservation?.projectId, 'default-project'),
|
|
143
|
+
sessionId: toNonEmptyString(rawObservation?.sessionId, 'default-session'),
|
|
144
|
+
adapterId,
|
|
145
|
+
eventType,
|
|
146
|
+
timestamp: toIsoTimestamp(rawObservation?.timestamp),
|
|
147
|
+
title,
|
|
148
|
+
summary: summaryRedaction.redactedText,
|
|
149
|
+
detail: detailRedaction.redactedText,
|
|
150
|
+
tags,
|
|
151
|
+
privacy: {
|
|
152
|
+
level: toNonEmptyString(rawObservation?.privacyLevel, 'internal'),
|
|
153
|
+
redactionApplied: detailRedaction.wasRedacted || summaryRedaction.wasRedacted,
|
|
154
|
+
redactionReasons: Array.from(new Set([
|
|
155
|
+
...detailRedaction.redactionReasons,
|
|
156
|
+
...summaryRedaction.redactionReasons,
|
|
157
|
+
])),
|
|
158
|
+
privateTagRedactionCount: detailRedaction.privateTagRedactionCount + summaryRedaction.privateTagRedactionCount,
|
|
159
|
+
inlineRedactionCount: detailRedaction.inlineRedactionCount + summaryRedaction.inlineRedactionCount,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function scoreObservationRelevance(queryText, normalizedObservation) {
|
|
165
|
+
const normalizedQuery = toNonEmptyString(queryText, '').toLowerCase();
|
|
166
|
+
if (!normalizedQuery) {
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const queryTerms = normalizedQuery
|
|
171
|
+
.split(/\s+/)
|
|
172
|
+
.map((queryTerm) => queryTerm.trim())
|
|
173
|
+
.filter((queryTerm) => queryTerm.length > 2);
|
|
174
|
+
|
|
175
|
+
if (queryTerms.length === 0) {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const searchableContent = [
|
|
180
|
+
normalizedObservation.title,
|
|
181
|
+
normalizedObservation.summary,
|
|
182
|
+
normalizedObservation.detail,
|
|
183
|
+
normalizedObservation.tags.join(' '),
|
|
184
|
+
normalizedObservation.eventType,
|
|
185
|
+
normalizedObservation.adapterId,
|
|
186
|
+
].join(' ').toLowerCase();
|
|
187
|
+
|
|
188
|
+
let matchCount = 0;
|
|
189
|
+
for (const queryTerm of queryTerms) {
|
|
190
|
+
if (searchableContent.includes(queryTerm)) {
|
|
191
|
+
matchCount += 1;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return Number((matchCount / queryTerms.length).toFixed(4));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function buildSessionStartIndex(normalizedObservations, options = {}) {
|
|
199
|
+
const queryText = toNonEmptyString(options.queryText, '');
|
|
200
|
+
const maxIndexEntries = Number.isFinite(Number(options.limit)) ? Math.max(1, Number(options.limit)) : 8;
|
|
201
|
+
|
|
202
|
+
const rankedEntries = normalizedObservations
|
|
203
|
+
.map((normalizedObservation) => {
|
|
204
|
+
const relevanceScore = scoreObservationRelevance(queryText, normalizedObservation);
|
|
205
|
+
const indexLine = `${normalizedObservation.id}|${normalizedObservation.adapterId}|${normalizedObservation.eventType}|${normalizedObservation.title}`;
|
|
206
|
+
const indexTokenEstimate = estimateTokenUsage(indexLine);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
id: normalizedObservation.id,
|
|
210
|
+
adapterId: normalizedObservation.adapterId,
|
|
211
|
+
eventType: normalizedObservation.eventType,
|
|
212
|
+
timestamp: normalizedObservation.timestamp,
|
|
213
|
+
title: normalizedObservation.title,
|
|
214
|
+
summarySnippet: normalizedObservation.summary.slice(0, 120),
|
|
215
|
+
tags: normalizedObservation.tags,
|
|
216
|
+
relevanceScore,
|
|
217
|
+
indexTokenEstimate,
|
|
218
|
+
};
|
|
219
|
+
})
|
|
220
|
+
.sort((leftEntry, rightEntry) => {
|
|
221
|
+
if (rightEntry.relevanceScore !== leftEntry.relevanceScore) {
|
|
222
|
+
return rightEntry.relevanceScore - leftEntry.relevanceScore;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return rightEntry.timestamp.localeCompare(leftEntry.timestamp);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const indexEntries = rankedEntries.slice(0, maxIndexEntries);
|
|
229
|
+
const totalTokenEstimate = indexEntries.reduce(
|
|
230
|
+
(tokenAccumulator, indexEntry) => tokenAccumulator + indexEntry.indexTokenEstimate,
|
|
231
|
+
0
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
indexEntries,
|
|
236
|
+
totalTokenEstimate,
|
|
237
|
+
totalCandidateCount: rankedEntries.length,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function hydrateIndexedObservations(indexEntries, normalizedObservations, options = {}) {
|
|
242
|
+
const fullFetchLimit = Number.isFinite(Number(options.fullFetchLimit))
|
|
243
|
+
? Math.max(1, Number(options.fullFetchLimit))
|
|
244
|
+
: 2;
|
|
245
|
+
|
|
246
|
+
const observationLookup = new Map(normalizedObservations.map((normalizedObservation) => [
|
|
247
|
+
normalizedObservation.id,
|
|
248
|
+
normalizedObservation,
|
|
249
|
+
]));
|
|
250
|
+
|
|
251
|
+
const selectedIds = indexEntries.slice(0, fullFetchLimit).map((indexEntry) => indexEntry.id);
|
|
252
|
+
const hydratedObservations = selectedIds
|
|
253
|
+
.map((selectedId) => observationLookup.get(selectedId))
|
|
254
|
+
.filter(Boolean);
|
|
255
|
+
|
|
256
|
+
const hydrationTokenEstimate = hydratedObservations.reduce(
|
|
257
|
+
(tokenAccumulator, hydratedObservation) => tokenAccumulator + estimateTokenUsage(hydratedObservation.detail),
|
|
258
|
+
0
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
selectedIds,
|
|
263
|
+
hydratedObservations,
|
|
264
|
+
hydrationTokenEstimate,
|
|
265
|
+
};
|
|
266
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuenn3123/agentic-senior-core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.27",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Force your AI Agent to code like a Staff Engineer, not a Junior.",
|
|
6
6
|
"bin": {
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"benchmark:writer-judge": "node ./scripts/benchmark-writer-judge-matrix.mjs",
|
|
53
53
|
"benchmark:gate": "node ./scripts/benchmark-gate.mjs",
|
|
54
54
|
"benchmark:intelligence": "node ./scripts/benchmark-intelligence.mjs",
|
|
55
|
+
"benchmark:continuity": "node ./scripts/memory-continuity-benchmark.mjs",
|
|
55
56
|
"report:quality-trend": "node ./scripts/quality-trend-report.mjs",
|
|
56
57
|
"report:docs-quality-drift": "node ./scripts/docs-quality-drift-report.mjs",
|
|
57
58
|
"report:governance-weekly": "node ./scripts/governance-weekly-report.mjs",
|