@danielblomma/cortex-mcp 2.4.0 → 2.4.1

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.
Files changed (173) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +19 -3
  3. package/bin/cortex.mjs +308 -75
  4. package/bin/daemon-control.mjs +162 -0
  5. package/package.json +7 -3
  6. package/scaffold/mcp/dist/.cortex-build-hash +1 -0
  7. package/scaffold/mcp/dist/cli/enterprise-setup.js +134 -0
  8. package/scaffold/mcp/dist/cli/govern.js +937 -0
  9. package/scaffold/mcp/dist/cli/query.js +409 -0
  10. package/scaffold/mcp/dist/cli/run.js +295 -0
  11. package/scaffold/mcp/dist/cli/stage.js +308 -0
  12. package/scaffold/mcp/dist/cli/telemetry-test.js +141 -0
  13. package/scaffold/mcp/dist/cli/ungoverned-detector.js +133 -0
  14. package/scaffold/mcp/dist/contextEntities.js +282 -0
  15. package/scaffold/mcp/dist/core/audit/query.js +72 -0
  16. package/scaffold/mcp/dist/core/audit/writer.js +28 -0
  17. package/scaffold/mcp/dist/core/config.js +235 -0
  18. package/scaffold/mcp/dist/core/enterprise-host-identity.js +193 -0
  19. package/scaffold/mcp/dist/core/enterprise-identity.js +18 -0
  20. package/scaffold/mcp/dist/core/enterprise-rotation.js +158 -0
  21. package/scaffold/mcp/dist/core/govern-paths.js +21 -0
  22. package/scaffold/mcp/dist/core/index.js +14 -0
  23. package/scaffold/mcp/dist/core/license.js +278 -0
  24. package/scaffold/mcp/dist/core/policy/enforce.js +66 -0
  25. package/scaffold/mcp/dist/core/policy/injection.js +172 -0
  26. package/scaffold/mcp/dist/core/policy/store.js +179 -0
  27. package/scaffold/mcp/dist/core/rbac/check.js +30 -0
  28. package/scaffold/mcp/dist/core/secure-endpoint.js +25 -0
  29. package/scaffold/mcp/dist/core/telemetry/collector.js +285 -0
  30. package/scaffold/mcp/dist/core/telemetry/state-dir.js +31 -0
  31. package/scaffold/mcp/dist/core/validators/builtins.js +632 -0
  32. package/scaffold/mcp/dist/core/validators/config.js +44 -0
  33. package/scaffold/mcp/dist/core/validators/engine.js +120 -0
  34. package/scaffold/mcp/dist/core/validators/evaluators/code_comments.js +236 -0
  35. package/scaffold/mcp/dist/core/validators/evaluators/regex.js +116 -0
  36. package/scaffold/mcp/dist/core/workflow/artifact-io.js +103 -0
  37. package/scaffold/mcp/dist/core/workflow/capabilities.js +88 -0
  38. package/scaffold/mcp/dist/core/workflow/default-workflows.js +102 -0
  39. package/scaffold/mcp/dist/core/workflow/enforcement.js +164 -0
  40. package/scaffold/mcp/dist/core/workflow/envelope.js +132 -0
  41. package/scaffold/mcp/dist/core/workflow/index.js +11 -0
  42. package/scaffold/mcp/dist/core/workflow/mcp-tools.js +175 -0
  43. package/scaffold/mcp/dist/core/workflow/resolution.js +69 -0
  44. package/scaffold/mcp/dist/core/workflow/run-lifecycle.js +123 -0
  45. package/scaffold/mcp/dist/core/workflow/schemas.js +141 -0
  46. package/scaffold/mcp/dist/core/workflow/synced-capability-registry.js +60 -0
  47. package/scaffold/mcp/dist/core/workflow/synced-registry.js +60 -0
  48. package/scaffold/mcp/dist/daemon/capability-sync-checker.js +242 -0
  49. package/scaffold/mcp/dist/daemon/client.js +130 -0
  50. package/scaffold/mcp/dist/daemon/egress-proxy.js +288 -0
  51. package/scaffold/mcp/dist/daemon/global-host-events.js +222 -0
  52. package/scaffold/mcp/dist/daemon/heartbeat-pusher.js +116 -0
  53. package/scaffold/mcp/dist/daemon/heartbeat-tracker.js +165 -0
  54. package/scaffold/mcp/dist/daemon/host-events-pusher.js +249 -0
  55. package/scaffold/mcp/dist/daemon/main.js +449 -0
  56. package/scaffold/mcp/dist/daemon/paths.js +36 -0
  57. package/scaffold/mcp/dist/daemon/project-service-registry.js +78 -0
  58. package/scaffold/mcp/dist/daemon/protocol.js +8 -0
  59. package/scaffold/mcp/dist/daemon/server.js +180 -0
  60. package/scaffold/mcp/dist/daemon/skill-sync-checker.js +557 -0
  61. package/scaffold/mcp/dist/daemon/sync-checker.js +165 -0
  62. package/scaffold/mcp/dist/daemon/ungoverned-scanner.js +136 -0
  63. package/scaffold/mcp/dist/daemon/workflow-sync-checker.js +249 -0
  64. package/scaffold/mcp/dist/defaults.js +6 -0
  65. package/scaffold/mcp/dist/embed.js +627 -0
  66. package/scaffold/mcp/dist/embedScheduler.js +479 -0
  67. package/scaffold/mcp/dist/embeddings.js +167 -0
  68. package/scaffold/mcp/dist/enterprise/audit/push.js +66 -0
  69. package/scaffold/mcp/dist/enterprise/index.js +327 -0
  70. package/scaffold/mcp/dist/enterprise/model/deploy.js +27 -0
  71. package/scaffold/mcp/dist/enterprise/policy/sync.js +129 -0
  72. package/scaffold/mcp/dist/enterprise/privacy/boundary.js +184 -0
  73. package/scaffold/mcp/dist/enterprise/reviews/changed-files.js +33 -0
  74. package/scaffold/mcp/dist/enterprise/reviews/pattern-context.js +202 -0
  75. package/scaffold/mcp/dist/enterprise/reviews/policy-selection.js +46 -0
  76. package/scaffold/mcp/dist/enterprise/reviews/push.js +102 -0
  77. package/scaffold/mcp/dist/enterprise/reviews/trust-state.js +186 -0
  78. package/scaffold/mcp/dist/enterprise/telemetry/sync.js +57 -0
  79. package/scaffold/mcp/dist/enterprise/tools/enterprise.js +826 -0
  80. package/scaffold/mcp/dist/enterprise/tools/harness.js +40 -0
  81. package/scaffold/mcp/dist/enterprise/tools/walk.js +73 -0
  82. package/scaffold/mcp/dist/enterprise/violations/push.js +77 -0
  83. package/scaffold/mcp/dist/enterprise/workflow/push.js +44 -0
  84. package/scaffold/mcp/dist/enterprise/workflow/state.js +329 -0
  85. package/scaffold/mcp/dist/frontmatter.js +33 -0
  86. package/scaffold/mcp/dist/graph.js +769 -0
  87. package/scaffold/mcp/dist/graphCsv.js +55 -0
  88. package/scaffold/mcp/dist/graphMetrics.js +8 -0
  89. package/scaffold/mcp/dist/hooks/permission-request.js +89 -0
  90. package/scaffold/mcp/dist/hooks/post-tool-use.js +105 -0
  91. package/scaffold/mcp/dist/hooks/pre-compact.js +29 -0
  92. package/scaffold/mcp/dist/hooks/pre-tool-use.js +78 -0
  93. package/scaffold/mcp/dist/hooks/session-end.js +43 -0
  94. package/scaffold/mcp/dist/hooks/session-start.js +41 -0
  95. package/scaffold/mcp/dist/hooks/shared.js +194 -0
  96. package/scaffold/mcp/dist/hooks/stop.js +28 -0
  97. package/scaffold/mcp/dist/hooks/user-prompt-submit.js +33 -0
  98. package/scaffold/mcp/dist/impactPresentation.js +137 -0
  99. package/scaffold/mcp/dist/impactRanking.js +191 -0
  100. package/scaffold/mcp/dist/impactResponse.js +30 -0
  101. package/scaffold/mcp/dist/impactResults.js +105 -0
  102. package/scaffold/mcp/dist/impactSeed.js +20 -0
  103. package/scaffold/mcp/dist/impactTraversal.js +64 -0
  104. package/scaffold/mcp/dist/jsonl.js +77 -0
  105. package/scaffold/mcp/dist/loadGraph.js +759 -0
  106. package/scaffold/mcp/dist/lruCache.js +38 -0
  107. package/scaffold/mcp/dist/paths.js +97 -0
  108. package/scaffold/mcp/dist/patternEvidence.js +272 -0
  109. package/scaffold/mcp/dist/plugin.js +81 -0
  110. package/scaffold/mcp/dist/presets.js +78 -0
  111. package/scaffold/mcp/dist/relatedResponse.js +18 -0
  112. package/scaffold/mcp/dist/relatedTraversal.js +78 -0
  113. package/scaffold/mcp/dist/rules.js +23 -0
  114. package/scaffold/mcp/dist/search.js +212 -0
  115. package/scaffold/mcp/dist/searchCore.js +457 -0
  116. package/scaffold/mcp/dist/searchResults.js +230 -0
  117. package/scaffold/mcp/dist/server.js +317 -0
  118. package/scaffold/mcp/dist/types.js +1 -0
  119. package/scaffold/mcp/package-lock.json +321 -200
  120. package/scaffold/mcp/package.json +15 -6
  121. package/scaffold/mcp/src/cli/enterprise-setup.ts +82 -8
  122. package/scaffold/mcp/src/cli/govern.ts +137 -52
  123. package/scaffold/mcp/src/cli/run.ts +53 -19
  124. package/scaffold/mcp/src/cli/stage.ts +8 -3
  125. package/scaffold/mcp/src/core/config.ts +9 -2
  126. package/scaffold/mcp/src/core/enterprise-host-identity.ts +259 -0
  127. package/scaffold/mcp/src/core/enterprise-identity.ts +20 -0
  128. package/scaffold/mcp/src/core/enterprise-rotation.ts +185 -0
  129. package/scaffold/mcp/src/core/govern-paths.ts +30 -0
  130. package/scaffold/mcp/src/core/license.ts +186 -17
  131. package/scaffold/mcp/src/core/secure-endpoint.ts +23 -0
  132. package/scaffold/mcp/src/core/workflow/enforcement.ts +8 -1
  133. package/scaffold/mcp/src/core/workflow/mcp-tools.ts +3 -0
  134. package/scaffold/mcp/src/core/workflow/resolution.ts +9 -1
  135. package/scaffold/mcp/src/core/workflow/synced-capability-registry.ts +15 -0
  136. package/scaffold/mcp/src/core/workflow/synced-registry.ts +15 -0
  137. package/scaffold/mcp/src/daemon/capability-sync-checker.ts +38 -2
  138. package/scaffold/mcp/src/daemon/egress-proxy.ts +14 -8
  139. package/scaffold/mcp/src/daemon/global-host-events.ts +288 -0
  140. package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +4 -0
  141. package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +2 -0
  142. package/scaffold/mcp/src/daemon/host-events-pusher.ts +5 -0
  143. package/scaffold/mcp/src/daemon/main.ts +99 -25
  144. package/scaffold/mcp/src/daemon/project-service-registry.ts +107 -0
  145. package/scaffold/mcp/src/daemon/skill-sync-checker.ts +368 -31
  146. package/scaffold/mcp/src/daemon/sync-checker.ts +4 -0
  147. package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +57 -25
  148. package/scaffold/mcp/src/daemon/workflow-sync-checker.ts +41 -2
  149. package/scaffold/mcp/src/enterprise/audit/push.ts +8 -0
  150. package/scaffold/mcp/src/enterprise/policy/sync.ts +12 -0
  151. package/scaffold/mcp/src/enterprise/reviews/push.ts +9 -0
  152. package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +3 -1
  153. package/scaffold/mcp/src/enterprise/telemetry/sync.ts +9 -0
  154. package/scaffold/mcp/src/enterprise/violations/push.ts +9 -0
  155. package/scaffold/mcp/src/enterprise/workflow/push.ts +7 -0
  156. package/scaffold/mcp/src/plugin.ts +20 -0
  157. package/scaffold/mcp/tests/copilot-shim.test.mjs +19 -1
  158. package/scaffold/mcp/tests/egress-proxy.test.mjs +37 -0
  159. package/scaffold/mcp/tests/enterprise-identity-sync.test.mjs +401 -0
  160. package/scaffold/mcp/tests/enterprise-setup.test.mjs +189 -0
  161. package/scaffold/mcp/tests/global-host-events.test.mjs +81 -0
  162. package/scaffold/mcp/tests/govern-install.test.mjs +95 -2
  163. package/scaffold/mcp/tests/govern-repair.test.mjs +20 -3
  164. package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +26 -0
  165. package/scaffold/mcp/tests/license.test.mjs +367 -0
  166. package/scaffold/mcp/tests/project-service-registry.test.mjs +172 -0
  167. package/scaffold/mcp/tests/review-trust-contract.test.mjs +18 -0
  168. package/scaffold/mcp/tests/secure-enterprise-endpoint.test.mjs +46 -0
  169. package/scaffold/mcp/tests/skill-sync-checker.test.mjs +446 -2
  170. package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +104 -22
  171. package/scaffold/mcp/tests/workflow-cli.test.mjs +14 -1
  172. package/scaffold/mcp/tests/workflow-synced-capabilities.test.mjs +37 -0
  173. package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +40 -1
@@ -0,0 +1,212 @@
1
+ import { embedQuery, getEmbeddingRuntimeWarning, loadEmbeddingIndex } from "./embeddings.js";
2
+ import { buildChunkPartOfRelations, buildEntitySearchMap, iterateSearchEntities, entityCatalog } from "./contextEntities.js";
3
+ import { relationDegree } from "./graphMetrics.js";
4
+ import { loadContextData } from "./graph.js";
5
+ import { buildEmptyImpactResponse, buildImpactResponseMeta } from "./impactResponse.js";
6
+ import { buildImpactResults } from "./impactResults.js";
7
+ import { resolveImpactSeed } from "./impactSeed.js";
8
+ import { cosineSimilarity, expandQueryTokens, legacyDataAccessBoost, normalizeText, recencyScore, semanticScore, structuralSearchBoost, tokenize } from "./searchCore.js";
9
+ import { buildSearchResultsWithStats } from "./searchResults.js";
10
+ import { traverseImpactGraph } from "./impactTraversal.js";
11
+ import { buildEmptyRelatedResponse, buildRelatedResponseMeta } from "./relatedResponse.js";
12
+ import { traverseRelatedGraph } from "./relatedTraversal.js";
13
+ import { resolveImpactPathMustExclude, resolveImpactPathMustInclude, resolveImpactRelationTypes, resolveImpactResultDomains, resolveImpactResultEntityTypes } from "./impactRanking.js";
14
+ import { resolveImpactResponsePreset, resolveRelatedResponsePreset, resolveSearchResponsePreset } from "./presets.js";
15
+ const MIN_LEXICAL_RELEVANCE = 0.05;
16
+ const MIN_VECTOR_RELEVANCE = 0.2;
17
+ const IMPACT_RELATION_TYPES = new Set([
18
+ "CALLS",
19
+ "CALLS_SQL",
20
+ "IMPORTS",
21
+ "USES_CONFIG_KEY",
22
+ "USES_RESOURCE_KEY",
23
+ "USES_SETTING_KEY",
24
+ "USES_CONFIG",
25
+ "TRANSFORMS_CONFIG",
26
+ "PART_OF"
27
+ ]);
28
+ function* filterSearchCandidates(candidates, includeDeprecated, candidateFilter) {
29
+ for (const entity of candidates) {
30
+ if ((includeDeprecated || entity.status.toLowerCase() !== "deprecated") &&
31
+ (!candidateFilter || candidateFilter(entity))) {
32
+ yield entity;
33
+ }
34
+ }
35
+ }
36
+ export async function runContextSearch(parsed, options = {}) {
37
+ const searchPresetConfig = resolveSearchResponsePreset(parsed);
38
+ const responsePreset = searchPresetConfig.responsePreset;
39
+ const includeScores = searchPresetConfig.includeScores;
40
+ const includeMatchedRules = searchPresetConfig.includeMatchedRules;
41
+ const includeContent = searchPresetConfig.includeContent;
42
+ const data = options.data ?? await loadContextData();
43
+ const allRelations = [...data.relations, ...buildChunkPartOfRelations(data)];
44
+ const degreeByEntity = relationDegree(allRelations);
45
+ const queryTokens = expandQueryTokens(Array.from(new Set(tokenize(parsed.query))));
46
+ const queryPhrase = normalizeText(parsed.query).trim();
47
+ const candidates = () => filterSearchCandidates(iterateSearchEntities(data, includeContent), parsed.include_deprecated, options.candidate_filter);
48
+ const embeddings = options.embedding_index ?? loadEmbeddingIndex();
49
+ const queryVector = Object.prototype.hasOwnProperty.call(options, "query_vector")
50
+ ? options.query_vector ?? null
51
+ : embeddings.model && embeddings.vectors.size > 0
52
+ ? await embedQuery(parsed.query, embeddings.model)
53
+ : null;
54
+ const { results, totalCandidates } = buildSearchResultsWithStats({
55
+ candidates,
56
+ degreeByEntity,
57
+ queryTokens,
58
+ queryPhrase,
59
+ ranking: data.ranking,
60
+ includeScores,
61
+ includeMatchedRules,
62
+ includeContent,
63
+ queryVector,
64
+ embeddingVectors: embeddings.vectors,
65
+ topK: parsed.top_k,
66
+ minLexicalRelevance: MIN_LEXICAL_RELEVANCE,
67
+ minVectorRelevance: MIN_VECTOR_RELEVANCE,
68
+ semanticScorer: semanticScore,
69
+ vectorScorer: cosineSimilarity,
70
+ recencyScorer: options.reference_time_ms === undefined
71
+ ? recencyScore
72
+ : (isoDate) => recencyScore(isoDate, options.reference_time_ms),
73
+ structuralSearchBooster: structuralSearchBoost,
74
+ legacyDataAccessBooster: legacyDataAccessBoost
75
+ });
76
+ const warningMessages = [data.warning, embeddings.warning, getEmbeddingRuntimeWarning()].filter(Boolean);
77
+ return {
78
+ query: parsed.query,
79
+ top_k: parsed.top_k,
80
+ response_preset: responsePreset,
81
+ include_scores: includeScores,
82
+ include_matched_rules: includeMatchedRules,
83
+ include_content: includeContent,
84
+ ranking: data.ranking,
85
+ total_candidates: totalCandidates,
86
+ context_source: data.source,
87
+ warning: warningMessages.length > 0 ? warningMessages.join(" | ") : undefined,
88
+ semantic_engine: queryVector && embeddings.model ? `embedding+lexical (${embeddings.model})` : "lexical-only",
89
+ results
90
+ };
91
+ }
92
+ export async function runContextRelated(parsed) {
93
+ const relatedPresetConfig = resolveRelatedResponsePreset(parsed);
94
+ const responsePreset = relatedPresetConfig.responsePreset;
95
+ const includeEdges = relatedPresetConfig.includeEdges;
96
+ const includeEntityMetadata = relatedPresetConfig.includeEntityMetadata;
97
+ const data = await loadContextData();
98
+ const catalog = entityCatalog(data);
99
+ const relations = [...data.relations, ...buildChunkPartOfRelations(data)];
100
+ const relatedResponseMeta = buildRelatedResponseMeta({
101
+ parsed,
102
+ responsePreset,
103
+ includeEdges,
104
+ includeEntityMetadata,
105
+ contextSource: data.source
106
+ });
107
+ if (!catalog.has(parsed.entity_id)) {
108
+ return buildEmptyRelatedResponse({
109
+ meta: relatedResponseMeta,
110
+ warning: "Entity not found in indexed context."
111
+ });
112
+ }
113
+ const { related, traversedEdges } = traverseRelatedGraph({
114
+ entityId: parsed.entity_id,
115
+ relations,
116
+ depth: parsed.depth,
117
+ catalog,
118
+ includeEntityMetadata
119
+ });
120
+ return {
121
+ ...relatedResponseMeta,
122
+ warning: data.warning,
123
+ related,
124
+ edges: includeEdges ? traversedEdges : []
125
+ };
126
+ }
127
+ export async function runContextImpact(parsed) {
128
+ const data = await loadContextData();
129
+ const catalog = entityCatalog(data);
130
+ const relations = [...data.relations, ...buildChunkPartOfRelations(data)];
131
+ const allowedRelationTypes = resolveImpactRelationTypes(parsed);
132
+ const impactRelations = relations.filter((relation) => allowedRelationTypes.has(relation.relation));
133
+ const searchEntities = buildEntitySearchMap(data);
134
+ const degreeByEntity = relationDegree(relations);
135
+ const profile = parsed.profile ?? "all";
136
+ const sortBy = parsed.sort_by ?? "impact_score";
137
+ const responsePresetConfig = resolveImpactResponsePreset(parsed);
138
+ const responsePreset = responsePresetConfig.responsePreset;
139
+ const includeScores = responsePresetConfig.includeScores;
140
+ const includeReasons = responsePresetConfig.includeReasons;
141
+ const verbosePaths = responsePresetConfig.verbosePaths;
142
+ const maxPathHopsShown = responsePresetConfig.maxPathHopsShown;
143
+ const resultDomains = resolveImpactResultDomains(parsed);
144
+ const resultEntityTypes = resolveImpactResultEntityTypes(parsed);
145
+ const pathMustInclude = resolveImpactPathMustInclude(parsed);
146
+ const pathMustExclude = resolveImpactPathMustExclude(parsed);
147
+ const seedResolution = await resolveImpactSeed(parsed, runContextSearch);
148
+ const seedId = seedResolution.id;
149
+ const impactResponseMeta = buildImpactResponseMeta({
150
+ parsed,
151
+ responsePreset,
152
+ includeScores,
153
+ includeReasons,
154
+ verbosePaths,
155
+ maxPathHopsShown,
156
+ profile,
157
+ sortBy,
158
+ allowedRelationTypes,
159
+ contextSource: data.source
160
+ });
161
+ if (!seedId) {
162
+ return buildEmptyImpactResponse({
163
+ meta: impactResponseMeta,
164
+ warning: seedResolution.warning ?? "No matching seed entity found for impact analysis."
165
+ });
166
+ }
167
+ if (!catalog.has(seedId)) {
168
+ return buildEmptyImpactResponse({
169
+ meta: impactResponseMeta,
170
+ warning: "Seed entity not found in indexed context."
171
+ });
172
+ }
173
+ const { visited, traversedEdges } = traverseImpactGraph({
174
+ seedId,
175
+ relations: impactRelations,
176
+ depth: parsed.depth
177
+ });
178
+ const queryTokens = parsed.query ? expandQueryTokens(Array.from(new Set(tokenize(parsed.query)))) : [];
179
+ const queryPhrase = parsed.query ? normalizeText(parsed.query).trim() : "";
180
+ const results = buildImpactResults({
181
+ visited,
182
+ seedId,
183
+ catalog,
184
+ searchEntities,
185
+ degreeByEntity,
186
+ queryTokens,
187
+ queryPhrase,
188
+ hasQuery: Boolean(parsed.query),
189
+ profile,
190
+ includeReasons,
191
+ includeScores,
192
+ verbosePaths,
193
+ maxPathHopsShown,
194
+ resultDomains,
195
+ resultEntityTypes,
196
+ pathMustInclude,
197
+ pathMustExclude,
198
+ sortBy,
199
+ topK: parsed.top_k,
200
+ semanticScorer: semanticScore
201
+ });
202
+ return {
203
+ ...impactResponseMeta,
204
+ resolved_seed_id: seedId,
205
+ resolved_from_query: !parsed.entity_id,
206
+ seed: catalog.get(seedId),
207
+ warning: data.warning,
208
+ query_results: seedResolution.query_results,
209
+ results,
210
+ edges: parsed.include_edges && verbosePaths ? traversedEdges : []
211
+ };
212
+ }
@@ -0,0 +1,457 @@
1
+ const SQL_ENTITY_KINDS = new Set(["procedure", "view", "function", "table", "trigger"]);
2
+ const SQL_LIKE_EXTENSIONS = [".sql"];
3
+ const CONFIG_LIKE_EXTENSIONS = [".config"];
4
+ const RESOURCE_LIKE_EXTENSIONS = [".resx"];
5
+ const SETTINGS_LIKE_EXTENSIONS = [".settings"];
6
+ const CONFIG_ENVIRONMENT_TOKENS = [
7
+ "release",
8
+ "debug",
9
+ "prod",
10
+ "production",
11
+ "staging",
12
+ "stage",
13
+ "dev",
14
+ "development",
15
+ "test",
16
+ "qa",
17
+ "uat"
18
+ ];
19
+ const QUERY_TOKEN_EXPANSIONS = {
20
+ dashboard: ["status"],
21
+ embed: ["embedding", "embeddings"],
22
+ embedding: ["embed", "embeddings"],
23
+ embeddings: ["embed", "embedding"],
24
+ git: ["githooks"],
25
+ hook: ["hooks", "githooks"],
26
+ hooks: ["hook", "githooks"],
27
+ import: ["imports"],
28
+ imports: ["import"],
29
+ javascript: ["js"],
30
+ parser: ["parsers"],
31
+ parsers: ["parser"],
32
+ status: ["dashboard"],
33
+ semantisk: ["semantic"],
34
+ sökning: ["search"],
35
+ sokning: ["search"],
36
+ regel: ["rule"],
37
+ regler: ["rules"],
38
+ relaterad: ["related"],
39
+ meddelande: ["message"],
40
+ avvikelse: ["deviation"]
41
+ };
42
+ const STRUCTURAL_QUERY_STOP_TOKENS = new Set([
43
+ "about",
44
+ "after",
45
+ "and",
46
+ "are",
47
+ "before",
48
+ "between",
49
+ "does",
50
+ "from",
51
+ "happen",
52
+ "happens",
53
+ "has",
54
+ "how",
55
+ "into",
56
+ "the",
57
+ "their",
58
+ "then",
59
+ "through",
60
+ "when",
61
+ "where",
62
+ "while",
63
+ "with",
64
+ "what",
65
+ "which",
66
+ "who",
67
+ "why"
68
+ ]);
69
+ const IDENTIFIER_COMPOUND_TOKENS = new Set(["csharp", "fsharp", "graphql", "javascript", "typescript", "vbnet"]);
70
+ const LANGUAGE_TOKEN_GROUPS = [
71
+ ["javascript", "typescript", "js", "ts"],
72
+ ["csharp", "cs"],
73
+ ["vbnet", "vb"],
74
+ ["fsharp", "fs"],
75
+ ["java"],
76
+ ["cpp"],
77
+ ["python", "py"],
78
+ ["rust", "rs"],
79
+ ["ruby", "rb"],
80
+ ["go"],
81
+ ["php"],
82
+ ["swift"],
83
+ ["kotlin", "kt"],
84
+ ["sql"]
85
+ ];
86
+ export function normalizeText(value) {
87
+ return value.normalize("NFKC").toLowerCase();
88
+ }
89
+ function normalizeLanguageAliases(value) {
90
+ return value
91
+ .replace(/(^|[^\p{L}\p{N}])c#(?=$|[^\p{L}\p{N}])/giu, "$1 csharp ")
92
+ .replace(/(^|[^\p{L}\p{N}])f#(?=$|[^\p{L}\p{N}])/giu, "$1 fsharp ")
93
+ .replace(/(^|[^\p{L}\p{N}])vb\.net(?=$|[^\p{L}\p{N}])/giu, "$1 vbnet ");
94
+ }
95
+ function splitIdentifierPart(part) {
96
+ const split = part
97
+ .replace(/([\p{Ll}\p{N}])([\p{Lu}])/gu, "$1 $2")
98
+ .replace(/([\p{Lu}]+)([\p{Lu}][\p{Ll}])/gu, "$1 $2")
99
+ .split(/\s+/u)
100
+ .filter(Boolean);
101
+ if (split.length <= 1) {
102
+ return [];
103
+ }
104
+ const merged = [];
105
+ for (let index = 0; index < split.length; index += 1) {
106
+ const current = normalizeText(split[index]);
107
+ const next = index + 1 < split.length ? normalizeText(split[index + 1]) : "";
108
+ const compound = `${current}${next}`;
109
+ if (next && IDENTIFIER_COMPOUND_TOKENS.has(compound)) {
110
+ merged.push(compound);
111
+ index += 1;
112
+ continue;
113
+ }
114
+ merged.push(current);
115
+ }
116
+ return merged;
117
+ }
118
+ export function tokenize(value) {
119
+ const tokens = [];
120
+ for (const rawPart of normalizeLanguageAliases(value).normalize("NFKC").split(/[^\p{L}\p{N}]+/gu)) {
121
+ const part = rawPart.trim();
122
+ if (part.length < 2) {
123
+ continue;
124
+ }
125
+ const variants = [part, ...splitIdentifierPart(part)];
126
+ for (const variant of variants) {
127
+ const token = normalizeText(variant).trim();
128
+ if (token.length >= 2) {
129
+ tokens.push(token);
130
+ }
131
+ }
132
+ }
133
+ return tokens;
134
+ }
135
+ export function expandQueryTokens(tokens) {
136
+ const expanded = new Set(tokens);
137
+ for (const token of tokens) {
138
+ const aliases = QUERY_TOKEN_EXPANSIONS[token];
139
+ if (!aliases) {
140
+ continue;
141
+ }
142
+ for (const alias of aliases) {
143
+ expanded.add(alias);
144
+ }
145
+ }
146
+ return Array.from(expanded);
147
+ }
148
+ function relatedQueryTokens(token) {
149
+ const related = new Set(QUERY_TOKEN_EXPANSIONS[token] ?? []);
150
+ for (const [source, aliases] of Object.entries(QUERY_TOKEN_EXPANSIONS)) {
151
+ if (!aliases.includes(token)) {
152
+ continue;
153
+ }
154
+ related.add(source);
155
+ for (const alias of aliases) {
156
+ related.add(alias);
157
+ }
158
+ }
159
+ return Array.from(related);
160
+ }
161
+ function queryTokenGroups(queryTokens) {
162
+ const inputTokens = new Set(queryTokens);
163
+ const visited = new Set();
164
+ const groups = [];
165
+ for (const token of queryTokens) {
166
+ if (visited.has(token)) {
167
+ continue;
168
+ }
169
+ const group = new Set();
170
+ const queue = [token];
171
+ for (let index = 0; index < queue.length; index += 1) {
172
+ const current = queue[index];
173
+ if (group.has(current)) {
174
+ continue;
175
+ }
176
+ group.add(current);
177
+ for (const related of relatedQueryTokens(current)) {
178
+ group.add(related);
179
+ if (inputTokens.has(related) && !visited.has(related)) {
180
+ queue.push(related);
181
+ }
182
+ }
183
+ }
184
+ for (const member of group) {
185
+ if (inputTokens.has(member)) {
186
+ visited.add(member);
187
+ }
188
+ }
189
+ groups.push(Array.from(group));
190
+ }
191
+ return groups;
192
+ }
193
+ function daysSince(isoDate, referenceTimeMs) {
194
+ const timestamp = Date.parse(isoDate);
195
+ if (Number.isNaN(timestamp)) {
196
+ return 3650;
197
+ }
198
+ return Math.max(0, (referenceTimeMs - timestamp) / (1000 * 60 * 60 * 24));
199
+ }
200
+ export function recencyScore(isoDate, referenceTimeMs = Date.now()) {
201
+ const days = daysSince(isoDate, referenceTimeMs);
202
+ return 1 / (1 + days / 30);
203
+ }
204
+ export function semanticScore(queryTokens, queryPhrase, text) {
205
+ if (queryTokens.length === 0) {
206
+ return 0;
207
+ }
208
+ const textTokenSet = new Set(tokenize(text));
209
+ if (textTokenSet.size === 0) {
210
+ return 0;
211
+ }
212
+ const tokenGroups = queryTokenGroups(queryTokens);
213
+ let matched = 0;
214
+ for (const group of tokenGroups) {
215
+ if (group.some((token) => textTokenSet.has(token))) {
216
+ matched += 1;
217
+ }
218
+ }
219
+ const overlap = matched / tokenGroups.length;
220
+ if (overlap <= 0) {
221
+ return 0;
222
+ }
223
+ const normalizedText = normalizeText(text);
224
+ const phraseBonus = queryPhrase && normalizedText.includes(queryPhrase) ? 0.15 : 0;
225
+ return Math.min(1, overlap * 0.85 + phraseBonus);
226
+ }
227
+ function structuralQueryTokens(queryTokens) {
228
+ return queryTokens.filter((token) => token.length >= 3 && !STRUCTURAL_QUERY_STOP_TOKENS.has(token));
229
+ }
230
+ function tokenSet(value) {
231
+ return new Set(tokenize(value));
232
+ }
233
+ function overlapCount(queryTokens, targetTokens) {
234
+ let matched = 0;
235
+ for (const token of queryTokens) {
236
+ if (targetTokens.has(token)) {
237
+ matched += 1;
238
+ }
239
+ }
240
+ return matched;
241
+ }
242
+ function basenameWithoutExtension(pathValue) {
243
+ const base = pathValue.split(/[\\/]/u).pop() ?? pathValue;
244
+ const extensionIndex = base.lastIndexOf(".");
245
+ return extensionIndex > 0 ? base.slice(0, extensionIndex) : base;
246
+ }
247
+ function languageGroupsForTokens(tokens) {
248
+ const groups = new Set();
249
+ for (let index = 0; index < LANGUAGE_TOKEN_GROUPS.length; index += 1) {
250
+ if (LANGUAGE_TOKEN_GROUPS[index].some((token) => tokens.has(token))) {
251
+ groups.add(index);
252
+ }
253
+ }
254
+ return groups;
255
+ }
256
+ function hasLanguageGroupOverlap(a, b) {
257
+ for (const value of a) {
258
+ if (b.has(value)) {
259
+ return true;
260
+ }
261
+ }
262
+ return false;
263
+ }
264
+ export function structuralSearchBoost(entity, queryTokens, queryPhrase) {
265
+ const structuralTokens = structuralQueryTokens(queryTokens);
266
+ if (structuralTokens.length === 0) {
267
+ return 0;
268
+ }
269
+ const pathTokens = tokenSet(entity.path);
270
+ const labelTokens = tokenSet(entity.label);
271
+ const pathMatches = overlapCount(structuralTokens, pathTokens);
272
+ const basenameMatches = overlapCount(structuralTokens, tokenSet(basenameWithoutExtension(entity.path)));
273
+ const labelMatches = overlapCount(structuralTokens, labelTokens);
274
+ const kindMatches = overlapCount(structuralTokens, tokenSet(entity.kind));
275
+ let boost = 0;
276
+ boost += Math.min(0.1, pathMatches * 0.035);
277
+ boost += Math.min(0.08, basenameMatches * 0.06);
278
+ boost += Math.min(0.08, labelMatches * 0.04);
279
+ boost += Math.min(0.03, kindMatches * 0.02);
280
+ const normalizedPath = normalizeText(entity.path);
281
+ const normalizedLabel = normalizeText(entity.label);
282
+ const compactPhrase = queryPhrase.replace(/[^\p{L}\p{N}]+/gu, "");
283
+ if (compactPhrase.length >= 8) {
284
+ const compactPath = normalizedPath.replace(/[^\p{L}\p{N}]+/gu, "");
285
+ const compactLabel = normalizedLabel.replace(/[^\p{L}\p{N}]+/gu, "");
286
+ if (compactPath.includes(compactPhrase) || compactLabel.includes(compactPhrase)) {
287
+ boost += 0.04;
288
+ }
289
+ }
290
+ const queryLanguageGroups = languageGroupsForTokens(new Set(queryTokens));
291
+ const pathLanguageGroups = languageGroupsForTokens(new Set([...pathTokens, ...labelTokens]));
292
+ if (queryLanguageGroups.size > 0 &&
293
+ pathLanguageGroups.size > 0 &&
294
+ pathTokens.has("parsers") &&
295
+ !hasLanguageGroupOverlap(queryLanguageGroups, pathLanguageGroups)) {
296
+ boost -= 0.08;
297
+ }
298
+ return Math.min(0.2, boost);
299
+ }
300
+ function queryHasAnyToken(queryTokens, candidates) {
301
+ return candidates.some((candidate) => queryTokens.includes(candidate));
302
+ }
303
+ function pathHasExtension(pathValue, extensions) {
304
+ const normalizedPath = normalizeText(pathValue);
305
+ return extensions.some((extension) => normalizedPath.endsWith(extension));
306
+ }
307
+ export function legacyDataAccessBoost(entity, queryTokens, queryPhrase) {
308
+ const normalizedKind = normalizeText(entity.kind);
309
+ const wantsSql = queryHasAnyToken(queryTokens, [
310
+ "sql",
311
+ "database",
312
+ "db",
313
+ "provider",
314
+ "providername",
315
+ "sqlclient",
316
+ "sqlserver",
317
+ "oracle",
318
+ "postgres",
319
+ "postgresql",
320
+ "pgsql",
321
+ "mysql",
322
+ "sqlite",
323
+ "stored",
324
+ "procedure",
325
+ "proc",
326
+ "query",
327
+ "queries",
328
+ "view",
329
+ "table",
330
+ "trigger",
331
+ "report",
332
+ "reporting",
333
+ "data",
334
+ "dataflow"
335
+ ]) || queryPhrase.includes("stored procedure");
336
+ const wantsConfig = queryHasAnyToken(queryTokens, [
337
+ "config",
338
+ "configuration",
339
+ "connection",
340
+ "connectionstring",
341
+ "connectionstrings",
342
+ "appsettings",
343
+ "setting",
344
+ "settings"
345
+ ]) || queryPhrase.includes("connection string");
346
+ const wantsResource = queryHasAnyToken(queryTokens, ["resource", "resources", "resx"]);
347
+ const wantsSettings = queryHasAnyToken(queryTokens, ["setting", "settings", "appsettings"]);
348
+ const wantsConfigTransform = queryHasAnyToken(queryTokens, [...CONFIG_ENVIRONMENT_TOKENS, "transform", "xdt", "override"]) ||
349
+ queryPhrase.includes("web.release.config") ||
350
+ queryPhrase.includes("web.debug.config");
351
+ const wantsMachineConfig = queryHasAnyToken(queryTokens, ["machine", "machineconfig"]);
352
+ const wantsImpact = queryHasAnyToken(queryTokens, [
353
+ "impact",
354
+ "affect",
355
+ "affected",
356
+ "affects",
357
+ "change",
358
+ "changes",
359
+ "changing",
360
+ "override",
361
+ "overrides"
362
+ ]);
363
+ let boost = 0;
364
+ if (entity.entity_type === "Chunk") {
365
+ if (normalizedKind === "connection_string" && (wantsConfig || wantsSql)) {
366
+ boost += 0.16;
367
+ }
368
+ else if (normalizedKind === "database_target" &&
369
+ (wantsConfig ||
370
+ wantsSql ||
371
+ queryHasAnyToken(queryTokens, [
372
+ "database",
373
+ "server",
374
+ "catalog",
375
+ "provider",
376
+ "providername",
377
+ "sqlclient",
378
+ "sqlserver",
379
+ "oracle",
380
+ "postgres",
381
+ "postgresql",
382
+ "pgsql",
383
+ "mysql",
384
+ "sqlite"
385
+ ]))) {
386
+ boost += 0.18;
387
+ }
388
+ else if (normalizedKind === "app_setting" && (wantsConfig || wantsSettings)) {
389
+ boost += 0.12;
390
+ }
391
+ else if (normalizedKind === "resource_entry" && (wantsResource || wantsSql)) {
392
+ boost += 0.1;
393
+ }
394
+ else if (normalizedKind === "setting_entry" && (wantsSettings || wantsConfig || wantsSql)) {
395
+ boost += 0.1;
396
+ }
397
+ else if (SQL_ENTITY_KINDS.has(normalizedKind) && wantsSql) {
398
+ boost += 0.12;
399
+ }
400
+ if (wantsImpact &&
401
+ (normalizedKind === "connection_string" ||
402
+ normalizedKind === "database_target" ||
403
+ normalizedKind === "app_setting" ||
404
+ SQL_ENTITY_KINDS.has(normalizedKind))) {
405
+ boost += 0.08;
406
+ }
407
+ }
408
+ if (entity.entity_type === "File") {
409
+ if (pathHasExtension(entity.path, SQL_LIKE_EXTENSIONS) && wantsSql) {
410
+ boost += 0.04;
411
+ }
412
+ if (pathHasExtension(entity.path, CONFIG_LIKE_EXTENSIONS) && wantsConfig) {
413
+ boost += 0.06;
414
+ }
415
+ if (pathHasExtension(entity.path, CONFIG_LIKE_EXTENSIONS) &&
416
+ wantsConfigTransform &&
417
+ CONFIG_ENVIRONMENT_TOKENS.some((token) => normalizeText(entity.path).includes(`.${token}.config`))) {
418
+ boost += 0.12;
419
+ }
420
+ if (pathHasExtension(entity.path, CONFIG_LIKE_EXTENSIONS) &&
421
+ wantsMachineConfig &&
422
+ normalizeText(entity.path).endsWith("machine.config")) {
423
+ boost += 0.12;
424
+ }
425
+ if (pathHasExtension(entity.path, CONFIG_LIKE_EXTENSIONS) &&
426
+ wantsImpact &&
427
+ (wantsConfig || wantsConfigTransform || wantsSql)) {
428
+ boost += 0.08;
429
+ }
430
+ if (pathHasExtension(entity.path, RESOURCE_LIKE_EXTENSIONS) && (wantsResource || wantsSql)) {
431
+ boost += 0.05;
432
+ }
433
+ if (pathHasExtension(entity.path, SETTINGS_LIKE_EXTENSIONS) && (wantsSettings || wantsConfig)) {
434
+ boost += 0.05;
435
+ }
436
+ }
437
+ return boost;
438
+ }
439
+ export function cosineSimilarity(a, b) {
440
+ if (a.length === 0 || b.length === 0 || a.length !== b.length) {
441
+ return 0;
442
+ }
443
+ let dot = 0;
444
+ let normA = 0;
445
+ let normB = 0;
446
+ for (let index = 0; index < a.length; index += 1) {
447
+ const av = a[index];
448
+ const bv = b[index];
449
+ dot += av * bv;
450
+ normA += av * av;
451
+ normB += bv * bv;
452
+ }
453
+ if (normA === 0 || normB === 0) {
454
+ return 0;
455
+ }
456
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
457
+ }