@mknrt/autotests-overkill 1.1.4 → 1.1.5
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.
|
@@ -2,11 +2,15 @@ import { queryDocuments } from '../knowledge/repositories.js';
|
|
|
2
2
|
import { ensureIndexed, evidenceFile, output } from './shared.js';
|
|
3
3
|
export async function findBackendTestContext(input, context) {
|
|
4
4
|
await ensureIndexed(context);
|
|
5
|
+
const queryTokens = normalizeTokens(input.query);
|
|
6
|
+
const queryPaths = extractQueryPaths(input.query);
|
|
5
7
|
const interactionHits = highConfidence(queryDocuments(context.database, input.query, {
|
|
6
8
|
repoKind: 'autotests2',
|
|
7
9
|
sourceKinds: ['api-interaction'],
|
|
8
10
|
limit: 6,
|
|
9
|
-
}))
|
|
11
|
+
}))
|
|
12
|
+
.filter((item) => isRelevantInteraction(item, queryTokens, queryPaths))
|
|
13
|
+
.slice(0, 3);
|
|
10
14
|
const interactionBridgeQuery = interactionHits
|
|
11
15
|
.flatMap((item) => {
|
|
12
16
|
const metadata = parseMetadata(item.metadata_json);
|
|
@@ -21,20 +25,23 @@ export async function findBackendTestContext(input, context) {
|
|
|
21
25
|
repoKind: 'fis-platform',
|
|
22
26
|
sourceKinds: ['backend-endpoint'],
|
|
23
27
|
limit: 8,
|
|
24
|
-
}))
|
|
25
|
-
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
: []
|
|
28
|
+
}))
|
|
29
|
+
.filter((item) => isRelevantBackendEndpoint(item, interactionHits, queryTokens, queryPaths))
|
|
30
|
+
.slice(0, 6);
|
|
31
|
+
const evidencePaths = [...interactionHits, ...backendHits]
|
|
32
|
+
.flatMap((item) => parseMetadata(item.metadata_json).evidencePaths ?? []);
|
|
33
|
+
const exactLinkedAssets = findDocumentsByPaths(context, evidencePaths);
|
|
34
|
+
const linkedSpecQuery = [
|
|
35
|
+
input.query,
|
|
36
|
+
...interactionHits.map((item) => item.title),
|
|
37
|
+
...interactionHits.flatMap((item) => parseMetadata(item.metadata_json).normalizedPaths ?? []),
|
|
38
|
+
].join(' ');
|
|
39
|
+
const linkedSpecAssets = queryDocuments(context.database, linkedSpecQuery, {
|
|
40
|
+
repoKind: 'autotests2',
|
|
41
|
+
sourceKinds: ['spec', 'command', 'helper'],
|
|
42
|
+
limit: 8,
|
|
43
|
+
}).filter((item) => isRelevantLinkedAsset(item, queryTokens));
|
|
44
|
+
const linkedAssets = uniqueDocumentHits([...exactLinkedAssets, ...linkedSpecAssets]).slice(0, 8);
|
|
38
45
|
const evidence = [
|
|
39
46
|
...interactionHits.map((item) => evidenceFile('api interaction', item.path, truncate(item.body, 260), {
|
|
40
47
|
retrieval: 'knowledge-store',
|
|
@@ -67,6 +74,84 @@ export async function findBackendTestContext(input, context) {
|
|
|
67
74
|
function highConfidence(items) {
|
|
68
75
|
return items.filter((item) => (parseMetadata(item.metadata_json).confidence ?? 0) >= 0.7);
|
|
69
76
|
}
|
|
77
|
+
function findDocumentsByPaths(context, paths) {
|
|
78
|
+
const uniquePaths = [...new Set(paths)];
|
|
79
|
+
if (uniquePaths.length === 0) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const statement = context.database.prepare(`
|
|
83
|
+
SELECT id, path, title, body, metadata_json, 0 AS rank
|
|
84
|
+
FROM documents
|
|
85
|
+
WHERE repo_kind = 'autotests2'
|
|
86
|
+
AND source_kind IN ('spec', 'command', 'helper')
|
|
87
|
+
AND path = ?
|
|
88
|
+
`);
|
|
89
|
+
return uniquePaths.flatMap((filePath) => statement.all(filePath));
|
|
90
|
+
}
|
|
91
|
+
function uniqueDocumentHits(items) {
|
|
92
|
+
const seen = new Map();
|
|
93
|
+
for (const item of items) {
|
|
94
|
+
seen.set(item.id, item);
|
|
95
|
+
}
|
|
96
|
+
return [...seen.values()];
|
|
97
|
+
}
|
|
98
|
+
function isRelevantLinkedAsset(item, queryTokens) {
|
|
99
|
+
const searchable = normalizeTokens(`${item.title} ${item.path} ${item.body.slice(0, 1200)}`);
|
|
100
|
+
return queryTokens.some((token) => searchable.includes(token));
|
|
101
|
+
}
|
|
102
|
+
function isRelevantInteraction(item, queryTokens, queryPaths) {
|
|
103
|
+
const metadata = parseMetadata(item.metadata_json);
|
|
104
|
+
const normalizedPaths = metadata.normalizedPaths ?? [];
|
|
105
|
+
if (queryPaths.length > 0) {
|
|
106
|
+
return normalizedPaths.some((indexedPath) => queryPaths.some((queryPath) => pathsOverlap(indexedPath, queryPath)));
|
|
107
|
+
}
|
|
108
|
+
const searchable = normalizeTokens(`${item.title} ${item.path} ${item.body} ${normalizedPaths.join(' ')}`);
|
|
109
|
+
return queryTokens.some((token) => searchable.includes(token));
|
|
110
|
+
}
|
|
111
|
+
function isRelevantBackendEndpoint(item, interactions, queryTokens, queryPaths) {
|
|
112
|
+
const metadata = parseMetadata(item.metadata_json);
|
|
113
|
+
const endpointPath = metadata.endpointPath ?? item.title;
|
|
114
|
+
const interactionPaths = interactions.flatMap((interaction) => parseMetadata(interaction.metadata_json).normalizedPaths ?? []);
|
|
115
|
+
const endpointTokens = normalizeTokens(endpointPath);
|
|
116
|
+
if (queryPaths.length > 0 || interactionPaths.length > 0) {
|
|
117
|
+
return queryPaths.some((candidate) => pathsOverlap(endpointPath, candidate))
|
|
118
|
+
|| queryTokens.some((token) => endpointTokens.includes(token))
|
|
119
|
+
|| (queryPaths.length === 0 && interactionPaths.some((candidate) => pathsOverlap(endpointPath, candidate)));
|
|
120
|
+
}
|
|
121
|
+
const searchable = normalizeTokens(`${item.title} ${item.path} ${item.body} ${endpointPath}`);
|
|
122
|
+
return queryTokens.some((token) => searchable.includes(token));
|
|
123
|
+
}
|
|
124
|
+
function extractQueryPaths(query) {
|
|
125
|
+
return [...query.matchAll(/(?:https?:\/\/[^/\s]+)?(\/[A-Za-z0-9_/${}?.=&*-]+)+/g)]
|
|
126
|
+
.map((match) => match[0])
|
|
127
|
+
.map((value) => normalizePath(value))
|
|
128
|
+
.filter((value) => normalizeTokens(value).length >= 2)
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
}
|
|
131
|
+
function pathsOverlap(left, right) {
|
|
132
|
+
const leftTokens = normalizeTokens(normalizePath(left));
|
|
133
|
+
const rightTokens = normalizeTokens(normalizePath(right));
|
|
134
|
+
const sharedTokens = leftTokens.filter((token) => rightTokens.includes(token));
|
|
135
|
+
return normalizePath(left).includes(normalizePath(right))
|
|
136
|
+
|| normalizePath(right).includes(normalizePath(left))
|
|
137
|
+
|| (leftTokens.length > 0 && rightTokens.length > 0 && sharedTokens.length >= Math.min(2, rightTokens.length));
|
|
138
|
+
}
|
|
139
|
+
function normalizePath(value) {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = new URL(value);
|
|
142
|
+
return parsed.pathname;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return (value.split('?')[0] ?? value).replace(/^\$\{[^}]+\}/, '');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function normalizeTokens(value) {
|
|
149
|
+
return value
|
|
150
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
151
|
+
.toLowerCase()
|
|
152
|
+
.split(/[^a-z0-9]+/)
|
|
153
|
+
.filter((token) => token.length > 1 && !/^(platform|rs2|api|rest|sec)$/.test(token));
|
|
154
|
+
}
|
|
70
155
|
function parseMetadata(metadataJson) {
|
|
71
156
|
try {
|
|
72
157
|
return JSON.parse(metadataJson);
|
|
@@ -4,6 +4,7 @@ import { unique } from '../../utils/pathUtils.js';
|
|
|
4
4
|
export async function extractFrontendContract(input) {
|
|
5
5
|
const connector = new CaseplatformConnector(input.root);
|
|
6
6
|
const files = await connector.resolveFeatureFiles(input.widgetHint);
|
|
7
|
+
const focusedFiles = focusFrontendFiles(files.allFiles, input.widgetHint);
|
|
7
8
|
const warnings = [];
|
|
8
9
|
const controlBody = files.controlFile ? connector.read(files.controlFile) : '';
|
|
9
10
|
const singletonBody = files.singletonFile ? connector.read(files.singletonFile) : '';
|
|
@@ -12,7 +13,7 @@ export async function extractFrontendContract(input) {
|
|
|
12
13
|
if (files.mode === 'widget' && !files.controlFile) {
|
|
13
14
|
warnings.push(`No control file found for widget '${input.widgetHint}'.`);
|
|
14
15
|
}
|
|
15
|
-
const sources =
|
|
16
|
+
const sources = focusedFiles.map((filePath) => ({
|
|
16
17
|
path: filePath,
|
|
17
18
|
body: connector.read(filePath),
|
|
18
19
|
kind: connector.classifyFileKind(filePath),
|
|
@@ -52,11 +53,18 @@ export async function extractFrontendContract(input) {
|
|
|
52
53
|
events: [...new Set(events)],
|
|
53
54
|
files: files.mode === 'widget'
|
|
54
55
|
? unique([files.controlFile, files.singletonFile, files.handlersFile, files.templateFile, files.dataControlFile, ...files.supportFiles].filter(Boolean))
|
|
55
|
-
:
|
|
56
|
+
: focusedFiles,
|
|
56
57
|
snippets,
|
|
57
58
|
warnings,
|
|
58
59
|
};
|
|
59
60
|
}
|
|
61
|
+
function focusFrontendFiles(filePaths, widgetHint) {
|
|
62
|
+
if (!/auth|login/i.test(widgetHint)) {
|
|
63
|
+
return filePaths;
|
|
64
|
+
}
|
|
65
|
+
const focused = filePaths.filter((filePath) => /(?:^|[\\/])auth(?:[\\/]|$)|Auth|login/i.test(filePath));
|
|
66
|
+
return focused.length > 0 ? focused : filePaths;
|
|
67
|
+
}
|
|
60
68
|
function extractNamedObjectKeys(source, objectName) {
|
|
61
69
|
const startMatch = new RegExp(`${objectName}\\s*:\\s*\\{`).exec(source);
|
|
62
70
|
if (!startMatch || startMatch.index === undefined) {
|