@0xsarwagya/ontoly-core 0.1.0-alpha.1 → 0.1.0-alpha.13
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 +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1036 -8
- package/dist/index.js.map +1 -1
- package/dist/semantic-index.d.ts +116 -0
- package/dist/semantic-index.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,1018 @@
|
|
|
1
|
+
// src/semantic-index.ts
|
|
2
|
+
var SEMANTIC_INDEX_VERSION = "1.0.0";
|
|
3
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
4
|
+
"a",
|
|
5
|
+
"an",
|
|
6
|
+
"and",
|
|
7
|
+
"are",
|
|
8
|
+
"as",
|
|
9
|
+
"at",
|
|
10
|
+
"be",
|
|
11
|
+
"by",
|
|
12
|
+
"for",
|
|
13
|
+
"from",
|
|
14
|
+
"how",
|
|
15
|
+
"i",
|
|
16
|
+
"in",
|
|
17
|
+
"into",
|
|
18
|
+
"is",
|
|
19
|
+
"it",
|
|
20
|
+
"of",
|
|
21
|
+
"on",
|
|
22
|
+
"or",
|
|
23
|
+
"that",
|
|
24
|
+
"the",
|
|
25
|
+
"this",
|
|
26
|
+
"to",
|
|
27
|
+
"use",
|
|
28
|
+
"uses",
|
|
29
|
+
"what",
|
|
30
|
+
"when",
|
|
31
|
+
"where",
|
|
32
|
+
"which",
|
|
33
|
+
"who",
|
|
34
|
+
"with"
|
|
35
|
+
]);
|
|
36
|
+
var FRAMEWORK_SUFFIXES = /* @__PURE__ */ new Set([
|
|
37
|
+
"app",
|
|
38
|
+
"application",
|
|
39
|
+
"client",
|
|
40
|
+
"config",
|
|
41
|
+
"configuration",
|
|
42
|
+
"controller",
|
|
43
|
+
"dto",
|
|
44
|
+
"entity",
|
|
45
|
+
"factory",
|
|
46
|
+
"guard",
|
|
47
|
+
"handler",
|
|
48
|
+
"hook",
|
|
49
|
+
"interceptor",
|
|
50
|
+
"middleware",
|
|
51
|
+
"module",
|
|
52
|
+
"pipe",
|
|
53
|
+
"provider",
|
|
54
|
+
"repository",
|
|
55
|
+
"resolver",
|
|
56
|
+
"route",
|
|
57
|
+
"schema",
|
|
58
|
+
"service",
|
|
59
|
+
"store",
|
|
60
|
+
"table",
|
|
61
|
+
"type"
|
|
62
|
+
]);
|
|
63
|
+
var CATEGORY_TYPES = {
|
|
64
|
+
concept: [],
|
|
65
|
+
symbol: ["Function", "Method", "Class", "Interface", "TypeAlias", "Enum", "Field", "Service", "Provider", "Controller", "Repository", "Resource", "Model", "Route"],
|
|
66
|
+
feature: ["Route", "Controller", "Service", "Provider", "Module", "Package", "Repository", "Model", "Resource", "Class", "Interface", "TypeAlias", "Function", "Method", "Operation"],
|
|
67
|
+
configuration: ["Configuration", "EnvironmentVariable", "BuildTarget", "Script", "Task"],
|
|
68
|
+
environment: ["EnvironmentVariable"],
|
|
69
|
+
route: ["Route", "Operation", "Controller"],
|
|
70
|
+
entrypoint: ["Route", "Operation", "Controller", "Function", "Method", "Module", "Script", "Task", "Application"],
|
|
71
|
+
repository: ["Workspace", "Application", "Package", "Module", "Framework", "Dependency", "Configuration"]
|
|
72
|
+
};
|
|
73
|
+
var RELATIONSHIP_CAPABILITIES = [
|
|
74
|
+
{ capability: "RequestTrace", terms: ["route", "request", "endpoint", "api", "handler", "controller", "login", "logout"] },
|
|
75
|
+
{ capability: "ConfigurationUsage", terms: ["config", "configuration", "setting", "environment", "env", "secret"] },
|
|
76
|
+
{ capability: "EnvironmentUsage", terms: ["env", "environment", "secret", "variable"] },
|
|
77
|
+
{ capability: "AuthenticationFlow", terms: ["auth", "authentication", "login", "signin", "jwt", "session", "token"] },
|
|
78
|
+
{ capability: "AuthorizationFlow", terms: ["authorization", "permission", "guard", "role", "policy", "authorize"] },
|
|
79
|
+
{ capability: "ImpactAnalysis", terms: ["remove", "delete", "change", "break", "impact", "depends", "dependent"] },
|
|
80
|
+
{ capability: "DependencyAnalysis", terms: ["dependency", "depends", "imports", "uses", "injects", "providers"] },
|
|
81
|
+
{ capability: "FeatureTouchpoints", terms: ["feature", "owner", "touchpoint", "where", "find", "locate"] }
|
|
82
|
+
];
|
|
83
|
+
var SEMANTIC_EXPANSIONS = {
|
|
84
|
+
api: ["route", "endpoint", "operation", "controller"],
|
|
85
|
+
auth: ["authentication", "authorize", "authorization", "identity", "jwt", "login", "session", "signin", "token"],
|
|
86
|
+
authentication: ["auth", "identity", "jwt", "login", "session", "signin", "token"],
|
|
87
|
+
authorize: ["authorization", "permission", "guard"],
|
|
88
|
+
authorization: ["authorize", "permission", "guard", "role", "policy"],
|
|
89
|
+
average: ["averages", "aggregate", "aggregation", "mean", "statistics"],
|
|
90
|
+
averages: ["average", "aggregate", "aggregation", "mean", "statistics"],
|
|
91
|
+
batch: ["bulk", "group", "job", "pipeline", "data"],
|
|
92
|
+
bulk: ["batch", "group", "job", "pipeline", "data"],
|
|
93
|
+
config: ["configuration", "setting", "settings"],
|
|
94
|
+
configuration: ["config", "setting", "settings"],
|
|
95
|
+
controller: ["handler", "route", "endpoint"],
|
|
96
|
+
data: ["resource", "payload", "record", "model"],
|
|
97
|
+
database: ["db", "repository", "persistence", "prisma", "table"],
|
|
98
|
+
db: ["database", "repository", "persistence", "table"],
|
|
99
|
+
duration: ["time", "period", "interval", "sleep", "threshold"],
|
|
100
|
+
endpoint: ["api", "route", "operation"],
|
|
101
|
+
env: ["environment", "variable", "secret", "configuration"],
|
|
102
|
+
environment: ["env", "variable", "secret", "configuration"],
|
|
103
|
+
fhir: ["resource", "healthcare"],
|
|
104
|
+
handler: ["controller", "route", "endpoint"],
|
|
105
|
+
identity: ["auth", "authentication", "user", "session"],
|
|
106
|
+
jwt: ["auth", "authentication", "token", "session"],
|
|
107
|
+
login: ["auth", "authentication", "signin", "session", "jwt"],
|
|
108
|
+
notification: ["notifications", "alert", "event", "message"],
|
|
109
|
+
notifications: ["notification", "alert", "event", "message"],
|
|
110
|
+
observation: ["observations", "signal", "metric", "measurement", "statistic", "fhir"],
|
|
111
|
+
observations: ["observation", "signal", "metric", "measurement", "statistic", "fhir"],
|
|
112
|
+
patient: ["resident", "user", "subject"],
|
|
113
|
+
permission: ["authorization", "authorize", "guard", "role"],
|
|
114
|
+
plan: ["definition", "resource", "fhir"],
|
|
115
|
+
plandefinition: ["plan", "definition", "resource", "fhir"],
|
|
116
|
+
provider: ["service", "injectable", "dependency"],
|
|
117
|
+
repo: ["repository", "persistence", "database"],
|
|
118
|
+
repository: ["repo", "persistence", "database"],
|
|
119
|
+
resident: ["patient", "user", "subject"],
|
|
120
|
+
route: ["api", "endpoint", "operation", "handler"],
|
|
121
|
+
service: ["provider", "injectable", "dependency"],
|
|
122
|
+
session: ["auth", "authentication", "login", "signin", "jwt"],
|
|
123
|
+
signin: ["auth", "authentication", "login", "session"],
|
|
124
|
+
sleep: ["duration", "threshold", "thresholds", "statistic", "statistics", "observation"],
|
|
125
|
+
signal: ["signals", "metric", "measurement", "event"],
|
|
126
|
+
signals: ["signal", "metric", "measurement", "event"],
|
|
127
|
+
statistic: ["statistics", "average", "aggregate"],
|
|
128
|
+
statistics: ["statistic", "average", "aggregate"],
|
|
129
|
+
threshold: ["thresholds", "limit", "configuration", "setting"],
|
|
130
|
+
thresholds: ["threshold", "limit", "configuration", "setting"],
|
|
131
|
+
token: ["jwt", "auth", "authentication", "session"]
|
|
132
|
+
};
|
|
133
|
+
var SEMANTIC_METADATA_DEPTH_LIMIT = 3;
|
|
134
|
+
var SEMANTIC_METADATA_ARRAY_LIMIT = 12;
|
|
135
|
+
var SEMANTIC_METADATA_ENTRY_LIMIT = 24;
|
|
136
|
+
var SEMANTIC_METADATA_VALUES_LIMIT = 80;
|
|
137
|
+
var SEMANTIC_TEXT_LIMIT = 240;
|
|
138
|
+
function createSemanticIndex(graph) {
|
|
139
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
140
|
+
const incoming = groupEdges(graph.edges, "to");
|
|
141
|
+
const outgoing = groupEdges(graph.edges, "from");
|
|
142
|
+
const entries = graph.nodes.map((node) => createEntry(graph, node, nodeById, incoming, outgoing)).sort(compareEntries);
|
|
143
|
+
const invertedIndex = buildInvertedIndex(entries);
|
|
144
|
+
const vocabulary = buildVocabulary(entries);
|
|
145
|
+
const statistics = {
|
|
146
|
+
entries: entries.length,
|
|
147
|
+
aliases: sum(entries.map((entry) => entry.aliases.length)),
|
|
148
|
+
keywords: sum(entries.map((entry) => entry.keywords.length)),
|
|
149
|
+
vocabulary: vocabulary.length,
|
|
150
|
+
graphHash: graph.metadata.deterministicHash
|
|
151
|
+
};
|
|
152
|
+
const indexWithoutMetadata = {
|
|
153
|
+
version: SEMANTIC_INDEX_VERSION,
|
|
154
|
+
graphVersion: graph.version,
|
|
155
|
+
graphHash: graph.metadata.deterministicHash,
|
|
156
|
+
repository: {
|
|
157
|
+
name: graph.repository.name,
|
|
158
|
+
root: graph.repository.root,
|
|
159
|
+
packageName: graph.repository.packageName
|
|
160
|
+
},
|
|
161
|
+
entries,
|
|
162
|
+
entryIds: entries.map((entry) => entry.stableId),
|
|
163
|
+
invertedIndex,
|
|
164
|
+
vocabulary,
|
|
165
|
+
metadata: {
|
|
166
|
+
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
167
|
+
deterministicHash: "",
|
|
168
|
+
statistics
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const deterministicHash = hashSemanticIndex(indexWithoutMetadata);
|
|
172
|
+
return {
|
|
173
|
+
...indexWithoutMetadata,
|
|
174
|
+
metadata: {
|
|
175
|
+
...indexWithoutMetadata.metadata,
|
|
176
|
+
deterministicHash
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function hashSemanticIndex(index) {
|
|
181
|
+
const entryHashes = index.entries.map((entry) => stableHash(stableStringify(entry))).sort();
|
|
182
|
+
const invertedHashes = Object.entries(index.invertedIndex).sort(([left], [right]) => left.localeCompare(right)).map(([term, ids]) => stableHash(stableStringify({ term, ids })));
|
|
183
|
+
const vocabularyHashes = index.vocabulary.map((term) => stableHash(stableStringify(term))).sort();
|
|
184
|
+
return stableHash(stableStringify({
|
|
185
|
+
version: index.version,
|
|
186
|
+
graphVersion: index.graphVersion,
|
|
187
|
+
graphHash: index.graphHash,
|
|
188
|
+
repository: index.repository,
|
|
189
|
+
entryIds: index.entryIds,
|
|
190
|
+
entries: {
|
|
191
|
+
count: entryHashes.length,
|
|
192
|
+
hashes: entryHashes
|
|
193
|
+
},
|
|
194
|
+
invertedIndex: {
|
|
195
|
+
count: invertedHashes.length,
|
|
196
|
+
hashes: invertedHashes
|
|
197
|
+
},
|
|
198
|
+
vocabulary: {
|
|
199
|
+
count: vocabularyHashes.length,
|
|
200
|
+
hashes: vocabularyHashes
|
|
201
|
+
},
|
|
202
|
+
metadata: {
|
|
203
|
+
generatedAt: index.metadata.generatedAt,
|
|
204
|
+
statistics: index.metadata.statistics
|
|
205
|
+
}
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
function validateSemanticIndex(index, graph) {
|
|
209
|
+
const issues = [];
|
|
210
|
+
if (index.version !== SEMANTIC_INDEX_VERSION) {
|
|
211
|
+
issues.push(`Unsupported semantic index version ${index.version}.`);
|
|
212
|
+
}
|
|
213
|
+
if (graph && index.graphHash !== graph.metadata.deterministicHash) {
|
|
214
|
+
issues.push("Semantic index graph hash does not match Software Graph hash.");
|
|
215
|
+
}
|
|
216
|
+
const seen = /* @__PURE__ */ new Set();
|
|
217
|
+
for (const entry of index.entries) {
|
|
218
|
+
if (seen.has(entry.stableId)) {
|
|
219
|
+
issues.push(`Duplicate semantic index entry ${entry.stableId}.`);
|
|
220
|
+
}
|
|
221
|
+
seen.add(entry.stableId);
|
|
222
|
+
}
|
|
223
|
+
for (const [term, ids] of Object.entries(index.invertedIndex)) {
|
|
224
|
+
for (const id of ids) {
|
|
225
|
+
if (!seen.has(id)) {
|
|
226
|
+
issues.push(`Inverted index term "${term}" references missing entry ${id}.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return issues.sort();
|
|
231
|
+
}
|
|
232
|
+
function normalizeIntent(input) {
|
|
233
|
+
const raw = input.trim();
|
|
234
|
+
const tokens = tokenize(raw);
|
|
235
|
+
const phrases = phraseVariants(raw, tokens);
|
|
236
|
+
const expandedTerms = uniqueStrings([
|
|
237
|
+
...tokens,
|
|
238
|
+
...phrases,
|
|
239
|
+
...tokens.flatMap(expansionsFor),
|
|
240
|
+
...tokens.map(singularize),
|
|
241
|
+
...tokens.map(pluralize)
|
|
242
|
+
]);
|
|
243
|
+
return {
|
|
244
|
+
raw,
|
|
245
|
+
normalized: normalizePhrase(raw),
|
|
246
|
+
tokens,
|
|
247
|
+
phrases,
|
|
248
|
+
expandedTerms
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function resolveIntent(index, query, options = {}) {
|
|
252
|
+
const startedAt = performanceNow();
|
|
253
|
+
const category = options.category ?? "concept";
|
|
254
|
+
const intent = normalizeIntent(query);
|
|
255
|
+
const candidates = rankCandidates(index, intent, {
|
|
256
|
+
...options,
|
|
257
|
+
category
|
|
258
|
+
});
|
|
259
|
+
const confidence = candidates[0]?.confidence ?? 0;
|
|
260
|
+
const matchedConcepts = uniqueStrings(candidates.flatMap((candidate) => candidate.matchedTerms)).slice(0, 20);
|
|
261
|
+
return {
|
|
262
|
+
query,
|
|
263
|
+
category,
|
|
264
|
+
intent,
|
|
265
|
+
matchedConcepts,
|
|
266
|
+
candidates,
|
|
267
|
+
confidence,
|
|
268
|
+
recommendedCapability: recommendCapability(intent, candidates, category),
|
|
269
|
+
evidence: candidates.slice(0, 5).map(
|
|
270
|
+
(candidate) => `${candidate.displayName} (${candidate.kind}) matched ${candidate.matchedTerms.join(", ") || "graph context"} with score ${candidate.score.toFixed(2)}.`
|
|
271
|
+
),
|
|
272
|
+
latencyMs: round(performanceNow() - startedAt, 3)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function findConcept(index, query, options = {}) {
|
|
276
|
+
return resolveIntent(index, query, { ...options, category: "concept" });
|
|
277
|
+
}
|
|
278
|
+
function findSymbol(index, query, options = {}) {
|
|
279
|
+
return resolveIntent(index, query, { ...options, category: "symbol", kinds: options.kinds ?? CATEGORY_TYPES.symbol });
|
|
280
|
+
}
|
|
281
|
+
function findFeature(index, query, options = {}) {
|
|
282
|
+
return resolveIntent(index, query, { ...options, category: "feature", kinds: options.kinds ?? CATEGORY_TYPES.feature });
|
|
283
|
+
}
|
|
284
|
+
function findConfiguration(index, query, options = {}) {
|
|
285
|
+
return resolveIntent(index, query, {
|
|
286
|
+
...options,
|
|
287
|
+
category: "configuration",
|
|
288
|
+
kinds: options.kinds ?? CATEGORY_TYPES.configuration
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function findEnvironment(index, query, options = {}) {
|
|
292
|
+
return resolveIntent(index, query, { ...options, category: "environment", kinds: options.kinds ?? CATEGORY_TYPES.environment });
|
|
293
|
+
}
|
|
294
|
+
function findRoute(index, query, options = {}) {
|
|
295
|
+
return resolveIntent(index, query, { ...options, category: "route", kinds: options.kinds ?? CATEGORY_TYPES.route });
|
|
296
|
+
}
|
|
297
|
+
function findEntryPoint(index, query, options = {}) {
|
|
298
|
+
return resolveIntent(index, query, { ...options, category: "entrypoint", kinds: options.kinds ?? CATEGORY_TYPES.entrypoint });
|
|
299
|
+
}
|
|
300
|
+
function findRepositoryConcept(index, query, options = {}) {
|
|
301
|
+
return resolveIntent(index, query, { ...options, category: "repository", kinds: options.kinds ?? CATEGORY_TYPES.repository });
|
|
302
|
+
}
|
|
303
|
+
function createEntry(graph, node, nodeById, incoming, outgoing) {
|
|
304
|
+
const incomingEdges = incoming.get(node.id) ?? [];
|
|
305
|
+
const outgoingEdges = outgoing.get(node.id) ?? [];
|
|
306
|
+
const neighbors = uniqueStrings([
|
|
307
|
+
...incomingEdges.map((edge) => edge.from),
|
|
308
|
+
...outgoingEdges.map((edge) => edge.to)
|
|
309
|
+
]);
|
|
310
|
+
const neighborNodes = neighbors.map((id) => nodeById.get(id)).filter(isNode);
|
|
311
|
+
const parentChain = parentChainFor(node, nodeById, incomingEdges);
|
|
312
|
+
const aliasSource = aliasSourceValues(graph, node, neighborNodes, parentChain);
|
|
313
|
+
const aliases = generateAliases(aliasSource, node);
|
|
314
|
+
const keywords = generateKeywords(aliasSource, node);
|
|
315
|
+
const degree = incomingEdges.length + outgoingEdges.length;
|
|
316
|
+
return withOptionalProperties({
|
|
317
|
+
stableId: node.id,
|
|
318
|
+
displayName: node.name,
|
|
319
|
+
normalizedName: normalizePhrase(node.name),
|
|
320
|
+
kind: node.type,
|
|
321
|
+
relationships: {
|
|
322
|
+
incoming: countEdgesByType(incomingEdges),
|
|
323
|
+
outgoing: countEdgesByType(outgoingEdges),
|
|
324
|
+
degree,
|
|
325
|
+
neighborIds: neighbors,
|
|
326
|
+
neighborNames: uniqueStrings(neighborNodes.map((neighbor) => neighbor.name))
|
|
327
|
+
},
|
|
328
|
+
aliases,
|
|
329
|
+
keywords,
|
|
330
|
+
parentChain,
|
|
331
|
+
importance: nodeImportance(node, degree, incomingEdges.length),
|
|
332
|
+
usageFrequency: incomingEdges.length,
|
|
333
|
+
architectureLayer: architectureLayer(node)
|
|
334
|
+
}, {
|
|
335
|
+
module: containingName(parentChain, "Module"),
|
|
336
|
+
package: node.package ?? stringMetadata(node.metadata, "package") ?? stringMetadata(node.metadata, "packageName"),
|
|
337
|
+
namespace: stringMetadata(node.metadata, "namespace"),
|
|
338
|
+
framework: stringMetadata(node.metadata, "framework") ?? frameworkFromNeighbors(neighborNodes),
|
|
339
|
+
filePath: node.file ? normalizePath(node.file) : void 0,
|
|
340
|
+
folderPath: node.file ? folderPath(node.file) : void 0,
|
|
341
|
+
documentation: deterministicDocumentation(node.metadata),
|
|
342
|
+
comments: deterministicComments(node.metadata)
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
function aliasSourceValues(graph, node, neighbors, parentChain) {
|
|
346
|
+
const metadataValues = metadataTextValues(node.metadata);
|
|
347
|
+
const routeValues = routeTextValues(node);
|
|
348
|
+
const envValues = node.type === "EnvironmentVariable" ? [node.name, node.id.replace(/^env:/, "")] : [];
|
|
349
|
+
return uniqueStrings([
|
|
350
|
+
node.id,
|
|
351
|
+
node.name,
|
|
352
|
+
node.file ?? "",
|
|
353
|
+
node.package ?? "",
|
|
354
|
+
...parentChain,
|
|
355
|
+
...metadataValues,
|
|
356
|
+
...routeValues,
|
|
357
|
+
...envValues,
|
|
358
|
+
...neighbors.map((neighbor) => neighbor.name),
|
|
359
|
+
...neighbors.map((neighbor) => neighbor.id)
|
|
360
|
+
].filter(Boolean));
|
|
361
|
+
}
|
|
362
|
+
function generateAliases(values, node) {
|
|
363
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
364
|
+
const primaryTokens = new Set(tokenize(node.name));
|
|
365
|
+
for (const value of values) {
|
|
366
|
+
const tokens = tokenize(value);
|
|
367
|
+
const normalized = normalizePhrase(value);
|
|
368
|
+
if (normalized) {
|
|
369
|
+
aliases.add(normalized);
|
|
370
|
+
}
|
|
371
|
+
if (tokens.length > 0) {
|
|
372
|
+
aliases.add(tokens.join(" "));
|
|
373
|
+
aliases.add(tokens.join(""));
|
|
374
|
+
aliases.add(tokens.join("-"));
|
|
375
|
+
aliases.add(tokens.join("_"));
|
|
376
|
+
const withoutSuffix = stripFrameworkSuffix(tokens);
|
|
377
|
+
if (withoutSuffix.length > 0 && withoutSuffix.length !== tokens.length) {
|
|
378
|
+
aliases.add(withoutSuffix.join(" "));
|
|
379
|
+
aliases.add(withoutSuffix.join(""));
|
|
380
|
+
}
|
|
381
|
+
for (const token of tokens) {
|
|
382
|
+
aliases.add(token);
|
|
383
|
+
aliases.add(singularize(token));
|
|
384
|
+
aliases.add(pluralize(token));
|
|
385
|
+
if (primaryTokens.has(token)) {
|
|
386
|
+
for (const expansion of expansionsFor(token)) {
|
|
387
|
+
aliases.add(expansion);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (const typeAlias of aliasesForNodeType(node.type)) {
|
|
394
|
+
aliases.add(typeAlias);
|
|
395
|
+
}
|
|
396
|
+
if (isDtoLikeNode(node)) {
|
|
397
|
+
aliases.add("dto");
|
|
398
|
+
aliases.add("data transfer object");
|
|
399
|
+
aliases.add("contract");
|
|
400
|
+
aliases.add("payload");
|
|
401
|
+
}
|
|
402
|
+
if (isTestNode(node)) {
|
|
403
|
+
aliases.add("test");
|
|
404
|
+
aliases.add("spec");
|
|
405
|
+
aliases.add("coverage");
|
|
406
|
+
}
|
|
407
|
+
if (isFeatureModuleNode(node)) {
|
|
408
|
+
aliases.add("feature");
|
|
409
|
+
aliases.add("feature module");
|
|
410
|
+
}
|
|
411
|
+
return uniqueStrings([...aliases].filter((alias) => alias.length > 1));
|
|
412
|
+
}
|
|
413
|
+
function generateKeywords(values, node) {
|
|
414
|
+
const keywords = /* @__PURE__ */ new Set();
|
|
415
|
+
for (const value of values) {
|
|
416
|
+
for (const token of tokenize(value)) {
|
|
417
|
+
keywords.add(token);
|
|
418
|
+
keywords.add(singularize(token));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
keywords.add(node.type.toLowerCase());
|
|
422
|
+
for (const alias of aliasesForNodeType(node.type)) {
|
|
423
|
+
keywords.add(alias);
|
|
424
|
+
}
|
|
425
|
+
if (isDtoLikeNode(node)) {
|
|
426
|
+
keywords.add("dto");
|
|
427
|
+
keywords.add("contract");
|
|
428
|
+
keywords.add("payload");
|
|
429
|
+
}
|
|
430
|
+
if (isTestNode(node)) {
|
|
431
|
+
keywords.add("test");
|
|
432
|
+
keywords.add("spec");
|
|
433
|
+
}
|
|
434
|
+
if (isFeatureModuleNode(node)) {
|
|
435
|
+
keywords.add("feature");
|
|
436
|
+
}
|
|
437
|
+
return uniqueStrings([...keywords].filter((keyword) => keyword.length > 1));
|
|
438
|
+
}
|
|
439
|
+
function buildInvertedIndex(entries) {
|
|
440
|
+
const inverted = /* @__PURE__ */ new Map();
|
|
441
|
+
for (const entry of entries) {
|
|
442
|
+
for (const term of uniqueStrings([entry.normalizedName, ...entry.aliases, ...entry.keywords])) {
|
|
443
|
+
addInverted(inverted, term, entry.stableId);
|
|
444
|
+
for (const token of tokenize(term)) {
|
|
445
|
+
addInverted(inverted, token, entry.stableId);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return Object.fromEntries(
|
|
450
|
+
[...inverted.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([term, ids]) => [term, [...ids].sort()])
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
function buildVocabulary(entries) {
|
|
454
|
+
const terms = /* @__PURE__ */ new Map();
|
|
455
|
+
for (const entry of entries) {
|
|
456
|
+
for (const term of uniqueStrings([...entry.keywords, ...entry.aliases.filter((alias) => !alias.includes(" "))])) {
|
|
457
|
+
if (term.length < 2 || STOP_WORDS.has(term)) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const current = terms.get(term) ?? { count: 0, nodeIds: /* @__PURE__ */ new Set(), kinds: /* @__PURE__ */ new Set() };
|
|
461
|
+
current.count += 1;
|
|
462
|
+
current.nodeIds.add(entry.stableId);
|
|
463
|
+
current.kinds.add(entry.kind);
|
|
464
|
+
terms.set(term, current);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return [...terms.entries()].map(([term, value]) => ({
|
|
468
|
+
term,
|
|
469
|
+
frequency: value.count,
|
|
470
|
+
nodeIds: [...value.nodeIds].sort(),
|
|
471
|
+
kinds: [...value.kinds].sort()
|
|
472
|
+
})).sort((left, right) => right.frequency - left.frequency || left.term.localeCompare(right.term));
|
|
473
|
+
}
|
|
474
|
+
function rankCandidates(index, intent, options) {
|
|
475
|
+
const candidateIds = candidateIdsFor(index, intent);
|
|
476
|
+
const entryById = new Map(index.entries.map((entry) => [entry.stableId, entry]));
|
|
477
|
+
const kinds = options.kinds?.length ? new Set(options.kinds) : categoryTypeSet(options.category);
|
|
478
|
+
const candidates = candidateIds.map((id) => entryById.get(id)).filter(isEntry).filter((entry) => includeEntryForCategory(entry, intent, options.category)).filter((entry) => !kinds || kinds.has(entry.kind)).filter((entry) => !options.packageScope || entry.package === options.packageScope || entry.filePath?.startsWith(options.packageScope)).map((entry) => scoreEntry(entry, intent, options.category)).filter((candidate) => candidate.score > 0).sort(compareCandidates);
|
|
479
|
+
return candidates.slice(0, options.limit ?? 20);
|
|
480
|
+
}
|
|
481
|
+
function candidateIdsFor(index, intent) {
|
|
482
|
+
const ids = /* @__PURE__ */ new Set();
|
|
483
|
+
const terms = uniqueStrings([intent.normalized, ...intent.phrases, ...intent.tokens, ...intent.expandedTerms]);
|
|
484
|
+
for (const term of terms) {
|
|
485
|
+
for (const id of index.invertedIndex[term] ?? []) {
|
|
486
|
+
ids.add(id);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const fuzzyIds = index.entries.filter((entry) => fuzzyEntryMatch(entry, intent)).map((entry) => entry.stableId).sort();
|
|
490
|
+
if (ids.size > 0) {
|
|
491
|
+
for (const id of fuzzyIds.slice(0, 250)) {
|
|
492
|
+
ids.add(id);
|
|
493
|
+
}
|
|
494
|
+
return [...ids].sort();
|
|
495
|
+
}
|
|
496
|
+
return fuzzyIds;
|
|
497
|
+
}
|
|
498
|
+
function scoreEntry(entry, intent, category) {
|
|
499
|
+
const reasons = [];
|
|
500
|
+
const matchedTerms = /* @__PURE__ */ new Set();
|
|
501
|
+
const entryTerms = /* @__PURE__ */ new Set([entry.normalizedName, ...entry.aliases, ...entry.keywords]);
|
|
502
|
+
const normalizedId = normalizePhrase(entry.stableId);
|
|
503
|
+
addScore(reasons, "exact-symbol", intent.raw === entry.stableId ? 1e3 : 0, entry.stableId);
|
|
504
|
+
if (intent.raw === entry.stableId) {
|
|
505
|
+
matchedTerms.add(entry.stableId);
|
|
506
|
+
}
|
|
507
|
+
if (entry.normalizedName === intent.normalized || normalizedId === intent.normalized) {
|
|
508
|
+
addScore(reasons, "exact-normalized-name", 500, entry.displayName);
|
|
509
|
+
matchedTerms.add(intent.normalized);
|
|
510
|
+
}
|
|
511
|
+
if (entry.aliases.includes(intent.normalized)) {
|
|
512
|
+
addScore(reasons, "exact-alias", 420, intent.normalized);
|
|
513
|
+
matchedTerms.add(intent.normalized);
|
|
514
|
+
}
|
|
515
|
+
const nameTokens = tokenize(entry.displayName);
|
|
516
|
+
if (nameTokens.length > 0 && nameTokens.every((token) => intent.expandedTerms.includes(token))) {
|
|
517
|
+
addScore(reasons, "complete-name-token-match", 260, nameTokens.join(", "));
|
|
518
|
+
for (const token of nameTokens) {
|
|
519
|
+
matchedTerms.add(token);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
for (const phrase of intent.phrases) {
|
|
523
|
+
if (entry.aliases.includes(phrase) || entry.normalizedName === phrase) {
|
|
524
|
+
addScore(reasons, "phrase-match", 180, phrase);
|
|
525
|
+
matchedTerms.add(phrase);
|
|
526
|
+
} else if (phrase.length > 2 && [...entryTerms].some((term) => term.includes(phrase) || phrase.includes(term))) {
|
|
527
|
+
addScore(reasons, "partial-phrase-match", 80, phrase);
|
|
528
|
+
matchedTerms.add(phrase);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const tokenMatches = intent.tokens.filter((token) => entryTerms.has(token) || entryTerms.has(singularize(token)));
|
|
532
|
+
for (const token of tokenMatches) {
|
|
533
|
+
matchedTerms.add(token);
|
|
534
|
+
}
|
|
535
|
+
addScore(reasons, "token-overlap", tokenMatches.length * 55, tokenMatches.join(", "));
|
|
536
|
+
const expandedMatches = intent.expandedTerms.filter((term) => !intent.tokens.includes(term)).filter((term) => entryTerms.has(term));
|
|
537
|
+
for (const term of expandedMatches) {
|
|
538
|
+
matchedTerms.add(term);
|
|
539
|
+
}
|
|
540
|
+
addScore(reasons, "intent-expansion", expandedMatches.length * 32, expandedMatches.join(", "));
|
|
541
|
+
const relationshipMatches = intent.expandedTerms.filter((term) => entry.relationships.neighborNames.some((name) => tokenize(name).includes(term)));
|
|
542
|
+
addScore(reasons, "relationship-context", Math.min(120, relationshipMatches.length * 18), relationshipMatches.join(", "));
|
|
543
|
+
const categoryBoost = categoryBoostFor(entry, category);
|
|
544
|
+
addScore(reasons, "architecture-layer", categoryBoost, `${entry.kind} in ${entry.architectureLayer}`);
|
|
545
|
+
addScore(reasons, "graph-importance", Math.min(90, entry.importance * 18), `degree ${entry.relationships.degree}`);
|
|
546
|
+
addScore(reasons, "usage-frequency", Math.min(60, entry.usageFrequency * 8), `${entry.usageFrequency} incoming edges`);
|
|
547
|
+
addScore(reasons, "repository-locality", repositoryLocalityBoost(entry), repositoryLocalityEvidence(entry));
|
|
548
|
+
const penalty = repositoryRankingPenalty(entry);
|
|
549
|
+
const score = Math.max(0, reasons.reduce((total, reason) => total + reason.score, 0) - penalty);
|
|
550
|
+
const returnedReasons = penalty > 0 ? [...reasons, { factor: "repository-noise-demotion", score: -penalty, evidence: repositoryNoiseEvidence(entry) }] : reasons;
|
|
551
|
+
return {
|
|
552
|
+
nodeId: entry.stableId,
|
|
553
|
+
stableId: entry.stableId,
|
|
554
|
+
displayName: entry.displayName,
|
|
555
|
+
kind: entry.kind,
|
|
556
|
+
score: round(score, 3),
|
|
557
|
+
confidence: round(Math.min(1, score / 650), 3),
|
|
558
|
+
matchedTerms: uniqueStrings([...matchedTerms]),
|
|
559
|
+
reasons: returnedReasons.filter((reason) => reason.score !== 0).sort(compareReasons),
|
|
560
|
+
entry
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function repositoryLocalityBoost(entry) {
|
|
564
|
+
let score = 0;
|
|
565
|
+
if (isRepositoryLocalEntry(entry)) {
|
|
566
|
+
score += 32;
|
|
567
|
+
}
|
|
568
|
+
if (["Service", "Controller", "Repository", "Provider"].includes(entry.kind)) {
|
|
569
|
+
score += 34;
|
|
570
|
+
}
|
|
571
|
+
if (["Module", "Route", "Operation"].includes(entry.kind)) {
|
|
572
|
+
score += 26;
|
|
573
|
+
}
|
|
574
|
+
if (isRepositorySymbolEntry(entry)) {
|
|
575
|
+
score += 16;
|
|
576
|
+
}
|
|
577
|
+
if (isDtoLikeEntry(entry)) {
|
|
578
|
+
score += 28;
|
|
579
|
+
}
|
|
580
|
+
if (isFeatureModuleEntry(entry)) {
|
|
581
|
+
score += 8;
|
|
582
|
+
}
|
|
583
|
+
if (["Configuration", "EnvironmentVariable"].includes(entry.kind)) {
|
|
584
|
+
score += 18;
|
|
585
|
+
}
|
|
586
|
+
if (/(^|[/.])(test|tests|spec|__tests__)([/.]|$)|\.(test|spec)\./i.test(entry.filePath ?? "")) {
|
|
587
|
+
score += 22;
|
|
588
|
+
}
|
|
589
|
+
return score;
|
|
590
|
+
}
|
|
591
|
+
function repositoryRankingPenalty(entry) {
|
|
592
|
+
let penalty = 0;
|
|
593
|
+
const text = `${entry.stableId} ${entry.filePath ?? ""} ${entry.package ?? ""} ${entry.displayName}`.toLowerCase();
|
|
594
|
+
if (text.includes("node_modules") || entry.stableId.startsWith("dep:")) {
|
|
595
|
+
penalty += 120;
|
|
596
|
+
}
|
|
597
|
+
if (/(\bdto\b|dto$|schema$|type$|types$|generated|__generated__)/i.test(entry.displayName) && !isRepositoryLocalEntry(entry)) {
|
|
598
|
+
penalty += 35;
|
|
599
|
+
}
|
|
600
|
+
if (/node_modules|@nestjs\/|next\/dist|react\/|typescript\/lib/i.test(text)) {
|
|
601
|
+
penalty += 80;
|
|
602
|
+
}
|
|
603
|
+
if (/^framework:|framework|adapter|platform|runtime/.test(entry.stableId.toLowerCase()) && !isRepositoryLocalEntry(entry)) {
|
|
604
|
+
penalty += 55;
|
|
605
|
+
}
|
|
606
|
+
if (isGenericUtilityEntry(entry)) {
|
|
607
|
+
penalty += 35;
|
|
608
|
+
}
|
|
609
|
+
return penalty;
|
|
610
|
+
}
|
|
611
|
+
function repositoryLocalityEvidence(entry) {
|
|
612
|
+
return isRepositoryLocalEntry(entry) ? `${entry.kind} from repository-local graph evidence` : `${entry.kind} from external or generated boundary`;
|
|
613
|
+
}
|
|
614
|
+
function repositoryNoiseEvidence(entry) {
|
|
615
|
+
return `${entry.displayName} is external, generated, or generic framework-adjacent evidence`;
|
|
616
|
+
}
|
|
617
|
+
function isRepositoryLocalEntry(entry) {
|
|
618
|
+
const text = `${entry.stableId} ${entry.filePath ?? ""} ${entry.package ?? ""}`.toLowerCase();
|
|
619
|
+
return !text.includes("node_modules") && !entry.stableId.startsWith("dep:") && !entry.stableId.startsWith("framework:");
|
|
620
|
+
}
|
|
621
|
+
function isRepositorySymbolEntry(entry) {
|
|
622
|
+
return ["Function", "Method", "Class", "Interface", "TypeAlias", "Enum", "Field", "Model", "Resource"].includes(entry.kind);
|
|
623
|
+
}
|
|
624
|
+
function isDtoLikeEntry(entry) {
|
|
625
|
+
const text = `${entry.displayName} ${entry.filePath ?? ""} ${entry.keywords.join(" ")}`;
|
|
626
|
+
return ["Model", "Interface", "TypeAlias", "Class"].includes(entry.kind) && /\bdto\b|dto$|data transfer|payload|request|response/i.test(text);
|
|
627
|
+
}
|
|
628
|
+
function isFeatureModuleEntry(entry) {
|
|
629
|
+
return entry.kind === "Module" && isRepositoryLocalEntry(entry) && !isGenericUtilityEntry(entry);
|
|
630
|
+
}
|
|
631
|
+
function isGenericUtilityEntry(entry) {
|
|
632
|
+
const text = `${entry.stableId} ${entry.displayName} ${entry.filePath ?? ""}`.toLowerCase();
|
|
633
|
+
return /(^|[/.:-])(common|shared|utils?|helpers?|internal|misc|core|index)([/.:-]|$)/.test(text) || /\b(base|abstract|generic|utility|utils?|helpers?)\b/.test(entry.displayName);
|
|
634
|
+
}
|
|
635
|
+
function fuzzyEntryMatch(entry, intent) {
|
|
636
|
+
const haystack = `${entry.stableId} ${entry.displayName} ${entry.aliases.join(" ")} ${entry.keywords.join(" ")}`.toLowerCase();
|
|
637
|
+
return intent.tokens.some((token) => haystack.includes(token));
|
|
638
|
+
}
|
|
639
|
+
function recommendCapability(intent, candidates, category) {
|
|
640
|
+
if (category === "configuration") {
|
|
641
|
+
return "ConfigurationUsage";
|
|
642
|
+
}
|
|
643
|
+
if (category === "environment") {
|
|
644
|
+
return "EnvironmentUsage";
|
|
645
|
+
}
|
|
646
|
+
if (category === "route") {
|
|
647
|
+
return "RequestTrace";
|
|
648
|
+
}
|
|
649
|
+
const terms = new Set(intent.expandedTerms);
|
|
650
|
+
const matched = RELATIONSHIP_CAPABILITIES.map((item) => ({
|
|
651
|
+
capability: item.capability,
|
|
652
|
+
score: item.terms.filter((term) => terms.has(term)).length
|
|
653
|
+
})).sort((left, right) => right.score - left.score || left.capability.localeCompare(right.capability))[0];
|
|
654
|
+
if (matched && matched.score > 0) {
|
|
655
|
+
return matched.capability;
|
|
656
|
+
}
|
|
657
|
+
const topKind = candidates[0]?.kind;
|
|
658
|
+
if (topKind === "Route" || topKind === "Controller") {
|
|
659
|
+
return "RequestTrace";
|
|
660
|
+
}
|
|
661
|
+
if (topKind === "Configuration" || topKind === "EnvironmentVariable") {
|
|
662
|
+
return "ConfigurationUsage";
|
|
663
|
+
}
|
|
664
|
+
return category === "repository" ? "ArchitectureSummary" : "FeatureTouchpoints";
|
|
665
|
+
}
|
|
666
|
+
function tokenize(value) {
|
|
667
|
+
return uniqueStrings(splitIdentifier(value).map((part) => normalizeToken(part)).filter((part) => part.length > 0).filter((part) => !STOP_WORDS.has(part)).flatMap((part) => uniqueStrings([part, singularize(part)])));
|
|
668
|
+
}
|
|
669
|
+
function splitIdentifier(value) {
|
|
670
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[@#]/g, " ").replace(/[:./\\|()[\]{}<>,;'"`~!$%^&*+=?]+/g, " ").replace(/[-_]+/g, " ").split(/\s+/g).filter(Boolean);
|
|
671
|
+
}
|
|
672
|
+
function normalizeToken(token) {
|
|
673
|
+
return token.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
674
|
+
}
|
|
675
|
+
function normalizePhrase(value) {
|
|
676
|
+
return tokenize(value).join(" ");
|
|
677
|
+
}
|
|
678
|
+
function phraseVariants(raw, tokens) {
|
|
679
|
+
const variants = /* @__PURE__ */ new Set();
|
|
680
|
+
const normalized = normalizePhrase(raw);
|
|
681
|
+
if (normalized) {
|
|
682
|
+
variants.add(normalized);
|
|
683
|
+
}
|
|
684
|
+
if (tokens.length > 0) {
|
|
685
|
+
variants.add(tokens.join(" "));
|
|
686
|
+
variants.add(tokens.join(""));
|
|
687
|
+
variants.add(tokens.join("-"));
|
|
688
|
+
variants.add(tokens.join("_"));
|
|
689
|
+
}
|
|
690
|
+
const withoutSuffix = stripFrameworkSuffix(tokens);
|
|
691
|
+
if (withoutSuffix.length > 0) {
|
|
692
|
+
variants.add(withoutSuffix.join(" "));
|
|
693
|
+
variants.add(withoutSuffix.join(""));
|
|
694
|
+
}
|
|
695
|
+
return uniqueStrings([...variants]);
|
|
696
|
+
}
|
|
697
|
+
function stripFrameworkSuffix(tokens) {
|
|
698
|
+
let stripped = [...tokens];
|
|
699
|
+
while (stripped.length > 1 && FRAMEWORK_SUFFIXES.has(stripped[stripped.length - 1] ?? "")) {
|
|
700
|
+
stripped = stripped.slice(0, -1);
|
|
701
|
+
}
|
|
702
|
+
return stripped;
|
|
703
|
+
}
|
|
704
|
+
function singularize(value) {
|
|
705
|
+
if (value.endsWith("ies") && value.length > 4) {
|
|
706
|
+
return `${value.slice(0, -3)}y`;
|
|
707
|
+
}
|
|
708
|
+
if (value.endsWith("sses")) {
|
|
709
|
+
return value.slice(0, -2);
|
|
710
|
+
}
|
|
711
|
+
if (value.endsWith("s") && !value.endsWith("ss") && value.length > 3) {
|
|
712
|
+
return value.slice(0, -1);
|
|
713
|
+
}
|
|
714
|
+
return value;
|
|
715
|
+
}
|
|
716
|
+
function pluralize(value) {
|
|
717
|
+
if (value.endsWith("y") && value.length > 2) {
|
|
718
|
+
return `${value.slice(0, -1)}ies`;
|
|
719
|
+
}
|
|
720
|
+
if (value.endsWith("s")) {
|
|
721
|
+
return value;
|
|
722
|
+
}
|
|
723
|
+
return `${value}s`;
|
|
724
|
+
}
|
|
725
|
+
function aliasesForNodeType(type) {
|
|
726
|
+
switch (type) {
|
|
727
|
+
case "Route":
|
|
728
|
+
case "Operation":
|
|
729
|
+
return ["api", "endpoint", "route", "request"];
|
|
730
|
+
case "Controller":
|
|
731
|
+
return ["controller", "handler", "route"];
|
|
732
|
+
case "Service":
|
|
733
|
+
case "Provider":
|
|
734
|
+
return ["service", "provider", "injectable"];
|
|
735
|
+
case "Repository":
|
|
736
|
+
return ["repository", "repo", "persistence", "database"];
|
|
737
|
+
case "Model":
|
|
738
|
+
return ["model", "dto", "schema", "contract", "payload"];
|
|
739
|
+
case "Resource":
|
|
740
|
+
return ["resource", "model"];
|
|
741
|
+
case "Configuration":
|
|
742
|
+
return ["config", "configuration", "setting"];
|
|
743
|
+
case "EnvironmentVariable":
|
|
744
|
+
return ["env", "environment", "secret", "variable"];
|
|
745
|
+
case "Framework":
|
|
746
|
+
return ["framework", "library"];
|
|
747
|
+
case "Package":
|
|
748
|
+
return ["package", "workspace"];
|
|
749
|
+
default:
|
|
750
|
+
return [type.toLowerCase()];
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function isDtoLikeNode(node) {
|
|
754
|
+
const text = `${node.name} ${node.file ?? ""}`;
|
|
755
|
+
return ["Model", "Interface", "TypeAlias", "Class"].includes(node.type) && /\bdto\b|dto$|data transfer|payload|request|response/i.test(text);
|
|
756
|
+
}
|
|
757
|
+
function isTestNode(node) {
|
|
758
|
+
return /(^|[/.])(test|tests|spec|__tests__)([/.]|$)|\.(test|spec)\./i.test(`${node.file ?? ""} ${node.name}`);
|
|
759
|
+
}
|
|
760
|
+
function isFeatureModuleNode(node) {
|
|
761
|
+
return node.type === "Module" && !/(^|[/.:-])(common|shared|utils?|helpers?|internal|misc|core|index)([/.:-]|$)/i.test(`${node.id} ${node.name} ${node.file ?? ""}`);
|
|
762
|
+
}
|
|
763
|
+
function expansionsFor(token) {
|
|
764
|
+
return Object.prototype.hasOwnProperty.call(SEMANTIC_EXPANSIONS, token) ? SEMANTIC_EXPANSIONS[token] ?? [] : [];
|
|
765
|
+
}
|
|
766
|
+
function categoryTypeSet(category) {
|
|
767
|
+
const types = CATEGORY_TYPES[category];
|
|
768
|
+
return types.length > 0 ? new Set(types) : void 0;
|
|
769
|
+
}
|
|
770
|
+
function includeEntryForCategory(entry, intent, category) {
|
|
771
|
+
if (category !== "concept") {
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
if (!["Import", "Export", "Decorator"].includes(entry.kind)) {
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
const kind = entry.kind.toLowerCase();
|
|
778
|
+
return intent.tokens.includes(kind) || intent.raw === entry.stableId || intent.normalized === entry.normalizedName;
|
|
779
|
+
}
|
|
780
|
+
function categoryBoostFor(entry, category) {
|
|
781
|
+
if (category === "concept") {
|
|
782
|
+
return ["Service", "Provider", "Controller", "Route", "Operation", "Repository", "Model", "Resource", "Function", "Method", "Class", "Interface", "TypeAlias", "Module", "Configuration", "EnvironmentVariable"].includes(entry.kind) ? 45 : 0;
|
|
783
|
+
}
|
|
784
|
+
const types = CATEGORY_TYPES[category];
|
|
785
|
+
if (types.length === 0) {
|
|
786
|
+
return 0;
|
|
787
|
+
}
|
|
788
|
+
const base = types.includes(entry.kind) ? 75 : 0;
|
|
789
|
+
if (category === "feature" && isFeatureModuleEntry(entry)) {
|
|
790
|
+
return base + 10;
|
|
791
|
+
}
|
|
792
|
+
if (category === "feature" && isDtoLikeEntry(entry)) {
|
|
793
|
+
return base + 20;
|
|
794
|
+
}
|
|
795
|
+
return base;
|
|
796
|
+
}
|
|
797
|
+
function nodeImportance(node, degree, inbound) {
|
|
798
|
+
const layerWeight = {
|
|
799
|
+
Workspace: 5,
|
|
800
|
+
Application: 5,
|
|
801
|
+
Package: 4,
|
|
802
|
+
Module: 4,
|
|
803
|
+
Controller: 4,
|
|
804
|
+
Route: 4,
|
|
805
|
+
Service: 4,
|
|
806
|
+
Provider: 3,
|
|
807
|
+
Repository: 3,
|
|
808
|
+
Configuration: 3,
|
|
809
|
+
EnvironmentVariable: 3
|
|
810
|
+
};
|
|
811
|
+
return round((layerWeight[node.type] ?? 1) + Math.log2(degree + 1) + Math.log2(inbound + 1), 3);
|
|
812
|
+
}
|
|
813
|
+
function architectureLayer(node) {
|
|
814
|
+
if (["Route", "Controller", "Operation", "Middleware", "Guard"].includes(node.type)) {
|
|
815
|
+
return "application-boundary";
|
|
816
|
+
}
|
|
817
|
+
if (["Service", "Provider", "Factory"].includes(node.type)) {
|
|
818
|
+
return "application-service";
|
|
819
|
+
}
|
|
820
|
+
if (["Repository", "DatabaseTable", "Model", "Resource"].includes(node.type)) {
|
|
821
|
+
return "persistence";
|
|
822
|
+
}
|
|
823
|
+
if (["Configuration", "EnvironmentVariable", "BuildTarget", "Script", "Task"].includes(node.type)) {
|
|
824
|
+
return "configuration";
|
|
825
|
+
}
|
|
826
|
+
if (["Package", "Workspace", "Application", "Framework", "Dependency"].includes(node.type)) {
|
|
827
|
+
return "architecture";
|
|
828
|
+
}
|
|
829
|
+
return "language";
|
|
830
|
+
}
|
|
831
|
+
function parentChainFor(node, nodeById, incomingEdges) {
|
|
832
|
+
const parents = [];
|
|
833
|
+
let current = node;
|
|
834
|
+
const seen = /* @__PURE__ */ new Set([node.id]);
|
|
835
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
836
|
+
const parentEdge = incomingEdges.find(
|
|
837
|
+
(edge) => edge.to === current.id && (edge.type === "CONTAINS" || edge.type === "BELONGS_TO" || edge.type === "DECLARES" || edge.type === "REGISTERS")
|
|
838
|
+
);
|
|
839
|
+
if (!parentEdge || seen.has(parentEdge.from)) {
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
const parent = nodeById.get(parentEdge.from);
|
|
843
|
+
if (!parent) {
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
parents.push(`${parent.type}:${parent.name}`);
|
|
847
|
+
seen.add(parent.id);
|
|
848
|
+
current = parent;
|
|
849
|
+
}
|
|
850
|
+
return parents;
|
|
851
|
+
}
|
|
852
|
+
function containingName(parentChain, type) {
|
|
853
|
+
const prefix = `${type}:`;
|
|
854
|
+
return parentChain.find((item) => item.startsWith(prefix))?.slice(prefix.length);
|
|
855
|
+
}
|
|
856
|
+
function metadataTextValues(metadata) {
|
|
857
|
+
if (!metadata) {
|
|
858
|
+
return [];
|
|
859
|
+
}
|
|
860
|
+
const values = [];
|
|
861
|
+
collectMetadataText(metadata, values, 0, /* @__PURE__ */ new WeakSet());
|
|
862
|
+
return values;
|
|
863
|
+
}
|
|
864
|
+
function collectMetadataText(value, values, depth, seen) {
|
|
865
|
+
if (depth > SEMANTIC_METADATA_DEPTH_LIMIT || values.length >= SEMANTIC_METADATA_VALUES_LIMIT || value === null || value === void 0) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
869
|
+
values.push(truncateSemanticText(String(value)));
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (Array.isArray(value)) {
|
|
873
|
+
if (seen.has(value)) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
seen.add(value);
|
|
877
|
+
for (const item of value.slice(0, SEMANTIC_METADATA_ARRAY_LIMIT)) {
|
|
878
|
+
collectMetadataText(item, values, depth + 1, seen);
|
|
879
|
+
if (values.length >= SEMANTIC_METADATA_VALUES_LIMIT) {
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (typeof value === "object") {
|
|
886
|
+
if (seen.has(value)) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
seen.add(value);
|
|
890
|
+
for (const [key, entry] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).slice(0, SEMANTIC_METADATA_ENTRY_LIMIT)) {
|
|
891
|
+
if (isNoisyMetadataKey(key)) {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
values.push(truncateSemanticText(key));
|
|
895
|
+
collectMetadataText(entry, values, depth + 1, seen);
|
|
896
|
+
if (values.length >= SEMANTIC_METADATA_VALUES_LIMIT) {
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function isNoisyMetadataKey(key) {
|
|
903
|
+
return ["language", "parser", "parserVersion", "passId", "provenance", "source"].includes(key);
|
|
904
|
+
}
|
|
905
|
+
function routeTextValues(node) {
|
|
906
|
+
if (node.type !== "Route" && node.type !== "Operation") {
|
|
907
|
+
return [];
|
|
908
|
+
}
|
|
909
|
+
return [
|
|
910
|
+
stringMetadata(node.metadata, "method") ?? "",
|
|
911
|
+
stringMetadata(node.metadata, "path") ?? "",
|
|
912
|
+
stringMetadata(node.metadata, "route") ?? "",
|
|
913
|
+
node.name
|
|
914
|
+
].filter(Boolean);
|
|
915
|
+
}
|
|
916
|
+
function deterministicDocumentation(metadata) {
|
|
917
|
+
return firstTruncatedMetadata(metadata, ["documentation", "description", "summary"]);
|
|
918
|
+
}
|
|
919
|
+
function deterministicComments(metadata) {
|
|
920
|
+
return firstTruncatedMetadata(metadata, ["comments", "comment", "jsdoc"]);
|
|
921
|
+
}
|
|
922
|
+
function stringMetadata(metadata, key) {
|
|
923
|
+
const value = metadata?.[key];
|
|
924
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
925
|
+
}
|
|
926
|
+
function firstTruncatedMetadata(metadata, keys) {
|
|
927
|
+
for (const key of keys) {
|
|
928
|
+
const value = stringMetadata(metadata, key);
|
|
929
|
+
if (value) {
|
|
930
|
+
return truncateSemanticText(value);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return void 0;
|
|
934
|
+
}
|
|
935
|
+
function truncateSemanticText(value) {
|
|
936
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
937
|
+
return normalized.length > SEMANTIC_TEXT_LIMIT ? `${normalized.slice(0, SEMANTIC_TEXT_LIMIT)}...[truncated]` : normalized;
|
|
938
|
+
}
|
|
939
|
+
function frameworkFromNeighbors(neighbors) {
|
|
940
|
+
return neighbors.find((node) => node.type === "Framework")?.name;
|
|
941
|
+
}
|
|
942
|
+
function folderPath(file) {
|
|
943
|
+
const normalized = normalizePath(file);
|
|
944
|
+
const parts = normalized.split("/");
|
|
945
|
+
parts.pop();
|
|
946
|
+
return parts.join("/") || ".";
|
|
947
|
+
}
|
|
948
|
+
function countEdgesByType(edges) {
|
|
949
|
+
const counts = /* @__PURE__ */ new Map();
|
|
950
|
+
for (const edge of edges) {
|
|
951
|
+
counts.set(edge.type, (counts.get(edge.type) ?? 0) + 1);
|
|
952
|
+
}
|
|
953
|
+
return Object.fromEntries([...counts.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
954
|
+
}
|
|
955
|
+
function groupEdges(edges, side) {
|
|
956
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
957
|
+
for (const edge of edges) {
|
|
958
|
+
const current = grouped.get(edge[side]) ?? [];
|
|
959
|
+
current.push(edge);
|
|
960
|
+
grouped.set(edge[side], current);
|
|
961
|
+
}
|
|
962
|
+
return new Map([...grouped.entries()].map(([key, values]) => [key, values.sort(compareEdges)]));
|
|
963
|
+
}
|
|
964
|
+
function addInverted(map, term, id) {
|
|
965
|
+
if (!term || STOP_WORDS.has(term)) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const current = map.get(term) ?? /* @__PURE__ */ new Set();
|
|
969
|
+
current.add(id);
|
|
970
|
+
map.set(term, current);
|
|
971
|
+
}
|
|
972
|
+
function addScore(reasons, factor, score, evidence) {
|
|
973
|
+
if (score <= 0) {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
reasons.push({ factor, score: round(score, 3), evidence });
|
|
977
|
+
}
|
|
978
|
+
function compareCandidates(left, right) {
|
|
979
|
+
return right.score - left.score || left.kind.localeCompare(right.kind) || left.stableId.localeCompare(right.stableId);
|
|
980
|
+
}
|
|
981
|
+
function compareReasons(left, right) {
|
|
982
|
+
return right.score - left.score || left.factor.localeCompare(right.factor);
|
|
983
|
+
}
|
|
984
|
+
function compareEntries(left, right) {
|
|
985
|
+
return left.stableId.localeCompare(right.stableId);
|
|
986
|
+
}
|
|
987
|
+
function compareEdges(left, right) {
|
|
988
|
+
return left.id.localeCompare(right.id);
|
|
989
|
+
}
|
|
990
|
+
function uniqueStrings(values) {
|
|
991
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
|
|
992
|
+
}
|
|
993
|
+
function sum(values) {
|
|
994
|
+
return values.reduce((total, value) => total + value, 0);
|
|
995
|
+
}
|
|
996
|
+
function round(value, places = 3) {
|
|
997
|
+
const factor = 10 ** places;
|
|
998
|
+
return Math.round(value * factor) / factor;
|
|
999
|
+
}
|
|
1000
|
+
function performanceNow() {
|
|
1001
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1002
|
+
}
|
|
1003
|
+
function isNode(value) {
|
|
1004
|
+
return Boolean(value);
|
|
1005
|
+
}
|
|
1006
|
+
function isEntry(value) {
|
|
1007
|
+
return Boolean(value);
|
|
1008
|
+
}
|
|
1009
|
+
function withOptionalProperties(target, optional) {
|
|
1010
|
+
return {
|
|
1011
|
+
...target,
|
|
1012
|
+
...Object.fromEntries(Object.entries(optional).filter(([, value]) => value !== void 0))
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1
1016
|
// src/index.ts
|
|
2
1017
|
var SOFTWARE_GRAPH_VERSION = "1.0.0";
|
|
3
1018
|
var NODE_TYPES = [
|
|
@@ -241,7 +1256,7 @@ var GraphBuilder = class {
|
|
|
241
1256
|
};
|
|
242
1257
|
function createSoftwareGraph(input) {
|
|
243
1258
|
const nodes = [...input.nodes].map(normalizeNode).sort(compareNodes);
|
|
244
|
-
const edges = [...input.edges].map(normalizeEdge).sort(
|
|
1259
|
+
const edges = [...input.edges].map(normalizeEdge).sort(compareEdges2);
|
|
245
1260
|
const diagnostics = [...input.diagnostics ?? []].sort(compareDiagnostics);
|
|
246
1261
|
const indexes = buildIndexes(nodes, edges);
|
|
247
1262
|
const deterministicHash = stableHash(
|
|
@@ -319,7 +1334,7 @@ function normalizeNode(node) {
|
|
|
319
1334
|
type: node.type,
|
|
320
1335
|
name: node.name
|
|
321
1336
|
};
|
|
322
|
-
return
|
|
1337
|
+
return withOptionalProperties2(normalized, {
|
|
323
1338
|
file: node.file ? normalizePath(node.file) : void 0,
|
|
324
1339
|
package: node.package,
|
|
325
1340
|
span: node.span ? normalizeSpan(node.span) : void 0,
|
|
@@ -333,7 +1348,7 @@ function normalizeEdge(edge) {
|
|
|
333
1348
|
from: edge.from,
|
|
334
1349
|
to: edge.to
|
|
335
1350
|
};
|
|
336
|
-
return
|
|
1351
|
+
return withOptionalProperties2(normalized, {
|
|
337
1352
|
evidence: edge.evidence?.map((item) => ({
|
|
338
1353
|
...item,
|
|
339
1354
|
span: item.span ? normalizeSpan(item.span) : void 0
|
|
@@ -352,7 +1367,7 @@ function mergeNode(left, right) {
|
|
|
352
1367
|
...left,
|
|
353
1368
|
metadata: mergeJsonObject(left.metadata, right.metadata)
|
|
354
1369
|
};
|
|
355
|
-
return
|
|
1370
|
+
return withOptionalProperties2(merged, {
|
|
356
1371
|
span: left.span ?? right.span,
|
|
357
1372
|
file: left.file ?? right.file,
|
|
358
1373
|
package: left.package ?? right.package
|
|
@@ -364,7 +1379,7 @@ function mergeEdge(left, right) {
|
|
|
364
1379
|
...left,
|
|
365
1380
|
metadata: mergeJsonObject(left.metadata, right.metadata)
|
|
366
1381
|
};
|
|
367
|
-
return
|
|
1382
|
+
return withOptionalProperties2(merged, {
|
|
368
1383
|
evidence: evidence.length > 0 ? evidence : void 0
|
|
369
1384
|
});
|
|
370
1385
|
}
|
|
@@ -418,13 +1433,13 @@ function summarizeCounts(values) {
|
|
|
418
1433
|
function compareNodes(left, right) {
|
|
419
1434
|
return left.id.localeCompare(right.id);
|
|
420
1435
|
}
|
|
421
|
-
function
|
|
1436
|
+
function compareEdges2(left, right) {
|
|
422
1437
|
return left.id.localeCompare(right.id);
|
|
423
1438
|
}
|
|
424
1439
|
function compareDiagnostics(left, right) {
|
|
425
1440
|
return left.id.localeCompare(right.id);
|
|
426
1441
|
}
|
|
427
|
-
function
|
|
1442
|
+
function withOptionalProperties2(target, optional) {
|
|
428
1443
|
const entries = Object.entries(optional).filter(([, value]) => value !== void 0);
|
|
429
1444
|
return {
|
|
430
1445
|
...target,
|
|
@@ -435,19 +1450,32 @@ export {
|
|
|
435
1450
|
GraphBuilder,
|
|
436
1451
|
NODE_TYPES,
|
|
437
1452
|
RELATIONSHIP_TYPES,
|
|
1453
|
+
SEMANTIC_INDEX_VERSION,
|
|
438
1454
|
SOFTWARE_GRAPH_VERSION,
|
|
439
1455
|
buildIndexes,
|
|
440
1456
|
createDiagnosticId,
|
|
441
1457
|
createEdgeId,
|
|
442
1458
|
createGraphBuilder,
|
|
443
1459
|
createNodeId,
|
|
1460
|
+
createSemanticIndex,
|
|
444
1461
|
createSoftwareGraph,
|
|
445
1462
|
createSyntaxEvidence,
|
|
1463
|
+
findConcept,
|
|
1464
|
+
findConfiguration,
|
|
1465
|
+
findEntryPoint,
|
|
1466
|
+
findEnvironment,
|
|
1467
|
+
findFeature,
|
|
1468
|
+
findRepositoryConcept,
|
|
1469
|
+
findRoute,
|
|
1470
|
+
findSymbol,
|
|
446
1471
|
getEdge,
|
|
447
1472
|
getNode,
|
|
1473
|
+
normalizeIntent,
|
|
448
1474
|
normalizePath,
|
|
1475
|
+
resolveIntent,
|
|
449
1476
|
stableHash,
|
|
450
1477
|
stableStringify,
|
|
451
|
-
summarizeGraph
|
|
1478
|
+
summarizeGraph,
|
|
1479
|
+
validateSemanticIndex
|
|
452
1480
|
};
|
|
453
1481
|
//# sourceMappingURL=index.js.map
|