@ryuenn3123/agentic-senior-core 2.0.25 → 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/review-checklists/frontend-excellence-rubric.md +54 -0
- package/.agent-context/review-checklists/frontend-skill-parity.md +1 -0
- package/.agent-context/review-checklists/frontend-usability.md +1 -0
- package/.agent-context/rules/docker-runtime.md +29 -0
- package/.agent-context/skills/frontend/README.md +1 -0
- package/.agent-context/skills/frontend.md +4 -0
- 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 +29 -0
- package/lib/cli/commands/init.mjs +358 -16
- package/lib/cli/commands/optimize.mjs +12 -0
- package/lib/cli/commands/upgrade.mjs +30 -1
- package/lib/cli/compiler.mjs +55 -1
- package/lib/cli/constants.mjs +83 -0
- package/lib/cli/detector.mjs +11 -1
- package/lib/cli/memory-continuity.mjs +266 -0
- package/lib/cli/project-scaffolder.mjs +174 -1
- package/lib/cli/skill-selector.mjs +60 -38
- package/lib/cli/templates/architecture-decision-record.md.tmpl +39 -0
- package/lib/cli/templates/flow-overview.md.tmpl +12 -0
- package/lib/cli/templates/project-brief.md.id.tmpl +2 -0
- package/lib/cli/templates/project-brief.md.tmpl +26 -0
- package/lib/cli/utils.mjs +2 -1
- package/package.json +2 -1
- package/scripts/benchmark-evidence-bundle.mjs +493 -16
- package/scripts/frontend-usability-audit.mjs +21 -0
- package/scripts/memory-continuity-benchmark.mjs +322 -0
- package/scripts/release-gate.mjs +30 -0
- package/scripts/validate.mjs +5 -0
package/lib/cli/compiler.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
CLI_VERSION,
|
|
10
10
|
POLICY_FILE_NAME,
|
|
11
11
|
SKILL_PLATFORM_INDEX_PATH,
|
|
12
|
+
BLUEPRINT_RECOMMENDATIONS,
|
|
12
13
|
} from './constants.mjs';
|
|
13
14
|
|
|
14
15
|
import {
|
|
@@ -38,12 +39,15 @@ export async function writeOnboardingReport({
|
|
|
38
39
|
selectedProfilePack,
|
|
39
40
|
selectedPreset,
|
|
40
41
|
selectedStackFileName,
|
|
42
|
+
selectedAdditionalStackFileNames = [],
|
|
41
43
|
selectedBlueprintFileName,
|
|
44
|
+
selectedAdditionalBlueprintFileNames = [],
|
|
42
45
|
includeCiGuardrails,
|
|
43
46
|
setupDurationMs,
|
|
44
47
|
projectDetection,
|
|
45
48
|
selectedSkillDomains = [],
|
|
46
49
|
compatibilityWarnings = [],
|
|
50
|
+
runtimeEnvironment = null,
|
|
47
51
|
operationMode = 'init',
|
|
48
52
|
}) {
|
|
49
53
|
const onboardingReportPath = path.join(targetDirectoryPath, '.agent-context', 'state', 'onboarding-report.json');
|
|
@@ -60,14 +64,23 @@ export async function writeOnboardingReport({
|
|
|
60
64
|
: null,
|
|
61
65
|
selectedPreset,
|
|
62
66
|
selectedStack: selectedStackFileName,
|
|
67
|
+
selectedAdditionalStacks: selectedAdditionalStackFileNames,
|
|
63
68
|
selectedBlueprint: selectedBlueprintFileName,
|
|
69
|
+
selectedAdditionalBlueprints: selectedAdditionalBlueprintFileNames,
|
|
64
70
|
ciGuardrailsEnabled: includeCiGuardrails,
|
|
65
71
|
setupDurationMs,
|
|
66
72
|
selectedSkillDomains,
|
|
67
73
|
compatibilityWarnings,
|
|
74
|
+
runtimeEnvironment,
|
|
68
75
|
autoDetection: {
|
|
69
76
|
recommendedStack: projectDetection.recommendedStackFileName,
|
|
77
|
+
recommendedAdditionalStacks: projectDetection.secondaryStackFileNames || [],
|
|
70
78
|
recommendedBlueprint: projectDetection.recommendedBlueprintFileName,
|
|
79
|
+
recommendedAdditionalBlueprints: Array.isArray(projectDetection.secondaryStackFileNames)
|
|
80
|
+
? projectDetection.secondaryStackFileNames
|
|
81
|
+
.map((secondaryStackFileName) => BLUEPRINT_RECOMMENDATIONS[secondaryStackFileName] || null)
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
: [],
|
|
71
84
|
confidenceLabel: projectDetection.confidenceLabel,
|
|
72
85
|
confidenceScore: projectDetection.confidenceScore,
|
|
73
86
|
confidenceGap: projectDetection.confidenceGap,
|
|
@@ -94,7 +107,9 @@ export async function buildCompiledRulesContent({
|
|
|
94
107
|
targetDirectoryPath,
|
|
95
108
|
selectedProfileName,
|
|
96
109
|
selectedStackFileName,
|
|
110
|
+
selectedAdditionalStackFileNames = [],
|
|
97
111
|
selectedBlueprintFileName,
|
|
112
|
+
selectedAdditionalBlueprintFileNames = [],
|
|
98
113
|
includeCiGuardrails,
|
|
99
114
|
}) {
|
|
100
115
|
const resolvedTargetDirectoryPath = path.resolve(targetDirectoryPath);
|
|
@@ -102,7 +117,20 @@ export async function buildCompiledRulesContent({
|
|
|
102
117
|
const selectedStacksDirectoryPath = path.join(resolvedTargetDirectoryPath, '.agent-context', 'stacks');
|
|
103
118
|
const selectedBlueprintsDirectoryPath = path.join(resolvedTargetDirectoryPath, '.agent-context', 'blueprints');
|
|
104
119
|
const skillPlatformIndex = JSON.parse(await fs.readFile(SKILL_PLATFORM_INDEX_PATH, 'utf8'));
|
|
105
|
-
const
|
|
120
|
+
const normalizedAdditionalStackFileNames = Array.isArray(selectedAdditionalStackFileNames)
|
|
121
|
+
? Array.from(new Set(selectedAdditionalStackFileNames.filter((stackFileName) => stackFileName && stackFileName !== selectedStackFileName)))
|
|
122
|
+
: [];
|
|
123
|
+
const normalizedAdditionalBlueprintFileNames = Array.isArray(selectedAdditionalBlueprintFileNames)
|
|
124
|
+
? Array.from(new Set(selectedAdditionalBlueprintFileNames.filter(
|
|
125
|
+
(blueprintFileName) => blueprintFileName && blueprintFileName !== selectedBlueprintFileName
|
|
126
|
+
)))
|
|
127
|
+
: [];
|
|
128
|
+
const selectedSkillDomainNames = inferSkillDomainNamesFromSelection(
|
|
129
|
+
selectedStackFileName,
|
|
130
|
+
selectedBlueprintFileName,
|
|
131
|
+
normalizedAdditionalStackFileNames,
|
|
132
|
+
normalizedAdditionalBlueprintFileNames
|
|
133
|
+
);
|
|
106
134
|
|
|
107
135
|
const universalRuleFileNames = await collectFileNames(selectedRulesDirectoryPath);
|
|
108
136
|
const contextBlocks = [];
|
|
@@ -166,6 +194,16 @@ export async function buildCompiledRulesContent({
|
|
|
166
194
|
].join('\n')
|
|
167
195
|
);
|
|
168
196
|
|
|
197
|
+
if (normalizedAdditionalStackFileNames.length > 0) {
|
|
198
|
+
contextBlocks.push(
|
|
199
|
+
[
|
|
200
|
+
'## LAYER 2B: ADDITIONAL STACK PROFILES',
|
|
201
|
+
'This project uses multiple stacks. Load all additional stack profiles below:',
|
|
202
|
+
...normalizedAdditionalStackFileNames.map((stackFileName, stackIndex) => `${stackIndex + 1}. .agent-context/stacks/${stackFileName}`),
|
|
203
|
+
].join('\n')
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
169
207
|
const blueprintFilePath = path.join(selectedBlueprintsDirectoryPath, selectedBlueprintFileName);
|
|
170
208
|
const blueprintContent = await fs.readFile(blueprintFilePath, 'utf8');
|
|
171
209
|
const blueprintSummary = firstMarkdownHeading(blueprintContent, selectedBlueprintFileName);
|
|
@@ -178,6 +216,18 @@ export async function buildCompiledRulesContent({
|
|
|
178
216
|
].join('\n')
|
|
179
217
|
);
|
|
180
218
|
|
|
219
|
+
if (normalizedAdditionalBlueprintFileNames.length > 0) {
|
|
220
|
+
contextBlocks.push(
|
|
221
|
+
[
|
|
222
|
+
'## LAYER 3A: ADDITIONAL BLUEPRINT PROFILES',
|
|
223
|
+
'This project uses multiple architecture blueprints. Load all additional blueprint profiles below:',
|
|
224
|
+
...normalizedAdditionalBlueprintFileNames.map(
|
|
225
|
+
(blueprintFileName, blueprintIndex) => `${blueprintIndex + 1}. .agent-context/blueprints/${blueprintFileName}`
|
|
226
|
+
),
|
|
227
|
+
].join('\n')
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
181
231
|
if (includeCiGuardrails) {
|
|
182
232
|
contextBlocks.push(
|
|
183
233
|
[
|
|
@@ -300,7 +350,9 @@ export async function compileDynamicContext({
|
|
|
300
350
|
targetDirectoryPath,
|
|
301
351
|
selectedProfileName,
|
|
302
352
|
selectedStackFileName,
|
|
353
|
+
selectedAdditionalStackFileNames = [],
|
|
303
354
|
selectedBlueprintFileName,
|
|
355
|
+
selectedAdditionalBlueprintFileNames = [],
|
|
304
356
|
includeCiGuardrails,
|
|
305
357
|
}) {
|
|
306
358
|
const resolvedTargetDirectoryPath = path.resolve(targetDirectoryPath);
|
|
@@ -308,7 +360,9 @@ export async function compileDynamicContext({
|
|
|
308
360
|
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
309
361
|
selectedProfileName,
|
|
310
362
|
selectedStackFileName,
|
|
363
|
+
selectedAdditionalStackFileNames,
|
|
311
364
|
selectedBlueprintFileName,
|
|
365
|
+
selectedAdditionalBlueprintFileNames,
|
|
312
366
|
includeCiGuardrails,
|
|
313
367
|
});
|
|
314
368
|
|
package/lib/cli/constants.mjs
CHANGED
|
@@ -160,6 +160,89 @@ export const PROFILE_PRESETS = {
|
|
|
160
160
|
},
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
+
export const PROJECT_SCOPE_CHOICES = [
|
|
164
|
+
{
|
|
165
|
+
key: 'api-service',
|
|
166
|
+
label: 'API service',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: 'web-application',
|
|
170
|
+
label: 'Web application',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
key: 'mobile-app',
|
|
174
|
+
label: 'Mobile app',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
key: 'cli-tool',
|
|
178
|
+
label: 'CLI tool',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
key: 'library-sdk',
|
|
182
|
+
label: 'Library / SDK',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
key: 'other',
|
|
186
|
+
label: 'Other',
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
export const PROJECT_SCOPE_STACK_FILTERS = {
|
|
191
|
+
'api-service': ['typescript.md', 'python.md', 'go.md', 'java.md', 'php.md', 'csharp.md', 'ruby.md', 'rust.md'],
|
|
192
|
+
'web-application': ['typescript.md', 'python.md', 'go.md', 'java.md', 'php.md', 'csharp.md', 'ruby.md', 'rust.md'],
|
|
193
|
+
'mobile-app': ['react-native.md', 'flutter.md'],
|
|
194
|
+
'cli-tool': ['typescript.md', 'python.md', 'go.md', 'rust.md', 'ruby.md', 'java.md', 'csharp.md'],
|
|
195
|
+
'library-sdk': ['typescript.md', 'python.md', 'go.md', 'rust.md', 'java.md', 'csharp.md', 'php.md', 'ruby.md'],
|
|
196
|
+
other: null,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const WEB_FRONTEND_STACK_CANDIDATES = ['typescript.md'];
|
|
200
|
+
|
|
201
|
+
export const WEB_BACKEND_STACK_CANDIDATES = [
|
|
202
|
+
'typescript.md',
|
|
203
|
+
'python.md',
|
|
204
|
+
'go.md',
|
|
205
|
+
'java.md',
|
|
206
|
+
'php.md',
|
|
207
|
+
'csharp.md',
|
|
208
|
+
'ruby.md',
|
|
209
|
+
'rust.md',
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
export const WEB_FRONTEND_BLUEPRINT_CANDIDATES = [
|
|
213
|
+
'api-nextjs.md',
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
export const WEB_BACKEND_BLUEPRINT_CANDIDATES = [
|
|
217
|
+
'nestjs-logic.md',
|
|
218
|
+
'fastapi-service.md',
|
|
219
|
+
'go-service.md',
|
|
220
|
+
'spring-boot-api.md',
|
|
221
|
+
'laravel-api.md',
|
|
222
|
+
'aspnet-api.md',
|
|
223
|
+
'graphql-grpc-api.md',
|
|
224
|
+
'api-nextjs.md',
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
export const RUNTIME_ENVIRONMENT_CHOICES = [
|
|
228
|
+
{
|
|
229
|
+
key: 'linux-wsl',
|
|
230
|
+
label: 'Linux / WSL',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
key: 'windows',
|
|
234
|
+
label: 'Windows',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
key: 'linux',
|
|
238
|
+
label: 'Linux',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
key: 'macos',
|
|
242
|
+
label: 'macOS',
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
|
|
163
246
|
export const entryPointFiles = [
|
|
164
247
|
'.cursorrules',
|
|
165
248
|
'.windsurfrules',
|
package/lib/cli/detector.mjs
CHANGED
|
@@ -133,6 +133,7 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
133
133
|
return {
|
|
134
134
|
hasExistingProjectFiles,
|
|
135
135
|
recommendedStackFileName: null,
|
|
136
|
+
secondaryStackFileNames: [],
|
|
136
137
|
recommendedBlueprintFileName: null,
|
|
137
138
|
confidenceLabel: null,
|
|
138
139
|
confidenceScore: 0,
|
|
@@ -164,6 +165,10 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
164
165
|
confidenceScore: Number(detectionCandidate.confidenceScore.toFixed(2)),
|
|
165
166
|
evidence: detectionCandidate.evidence,
|
|
166
167
|
}));
|
|
168
|
+
const secondaryStackFileNames = rankedCandidates
|
|
169
|
+
.slice(1)
|
|
170
|
+
.filter((rankedCandidate) => (strongestCandidate.confidenceScore - rankedCandidate.confidenceScore) < 0.08)
|
|
171
|
+
.map((rankedCandidate) => rankedCandidate.stackFileName);
|
|
167
172
|
const detectionReasoning = isAmbiguous
|
|
168
173
|
? `Top signal ${toTitleCase(strongestCandidate.stackFileName)} is close to ${toTitleCase(secondStrongestCandidate.stackFileName)} (confidence gap ${confidenceGap}).`
|
|
169
174
|
: `Top signal ${toTitleCase(strongestCandidate.stackFileName)} won with confidence ${strongestCandidate.confidenceScore.toFixed(2)} from markers: ${strongestCandidate.evidence.join(', ') || 'none'}.`;
|
|
@@ -171,6 +176,7 @@ export async function detectProjectContext(targetDirectoryPath) {
|
|
|
171
176
|
return {
|
|
172
177
|
hasExistingProjectFiles,
|
|
173
178
|
recommendedStackFileName: strongestCandidate.stackFileName,
|
|
179
|
+
secondaryStackFileNames,
|
|
174
180
|
recommendedBlueprintFileName: BLUEPRINT_RECOMMENDATIONS[strongestCandidate.stackFileName] || null,
|
|
175
181
|
confidenceLabel,
|
|
176
182
|
confidenceScore: strongestCandidate.confidenceScore,
|
|
@@ -194,7 +200,11 @@ export function buildDetectionSummary(projectDetection) {
|
|
|
194
200
|
? ` Confidence gap: ${projectDetection.confidenceGap}.`
|
|
195
201
|
: '';
|
|
196
202
|
|
|
197
|
-
|
|
203
|
+
const secondaryStacksSummary = projectDetection.secondaryStackFileNames?.length
|
|
204
|
+
? ` Secondary stack signals: ${projectDetection.secondaryStackFileNames.map((stackFileName) => toTitleCase(stackFileName)).join(', ')}.`
|
|
205
|
+
: '';
|
|
206
|
+
|
|
207
|
+
return `This folder looks like ${toTitleCase(projectDetection.recommendedStackFileName)} with ${projectDetection.confidenceLabel} confidence based on ${readableEvidence}.${confidenceGapSummary}${secondaryStacksSummary}`;
|
|
198
208
|
}
|
|
199
209
|
|
|
200
210
|
export function formatDetectionCandidates(rankedCandidates) {
|
|
@@ -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
|
+
}
|