sweet-search 0.0.1 → 2.3.0

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 (161) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +23 -0
  3. package/core/cli.js +51 -0
  4. package/core/config.js +27 -0
  5. package/core/embedding/embedding-cache.js +467 -0
  6. package/core/embedding/embedding-local-model.js +845 -0
  7. package/core/embedding/embedding-remote.js +492 -0
  8. package/core/embedding/embedding-service.js +712 -0
  9. package/core/embedding/embedding-telemetry.js +219 -0
  10. package/core/embedding/index.js +40 -0
  11. package/core/graph/community-detector.js +294 -0
  12. package/core/graph/graph-expansion.js +839 -0
  13. package/core/graph/graph-extractor.js +2304 -0
  14. package/core/graph/graph-search.js +2148 -0
  15. package/core/graph/hcgs-generator.js +666 -0
  16. package/core/graph/index.js +16 -0
  17. package/core/graph/leiden-algorithm.js +547 -0
  18. package/core/graph/relationship-resolver.js +366 -0
  19. package/core/graph/repo-map.js +408 -0
  20. package/core/graph/summary-manager.js +549 -0
  21. package/core/indexing/artifact-builder.js +1054 -0
  22. package/core/indexing/ast-chunker.js +709 -0
  23. package/core/indexing/chunking/chunk-builder.js +170 -0
  24. package/core/indexing/chunking/markdown-chunker.js +503 -0
  25. package/core/indexing/chunking/plaintext-chunker.js +104 -0
  26. package/core/indexing/dedup/dedup-phase.js +159 -0
  27. package/core/indexing/dedup/exemplar-selector.js +65 -0
  28. package/core/indexing/document-chunker.js +56 -0
  29. package/core/indexing/incremental-parser.js +390 -0
  30. package/core/indexing/incremental-tracker.js +761 -0
  31. package/core/indexing/index-codebase-v21.js +472 -0
  32. package/core/indexing/index-maintainer.mjs +1674 -0
  33. package/core/indexing/index.js +90 -0
  34. package/core/indexing/indexer-ann.js +1077 -0
  35. package/core/indexing/indexer-build.js +742 -0
  36. package/core/indexing/indexer-phases.js +800 -0
  37. package/core/indexing/indexer-pool.js +764 -0
  38. package/core/indexing/indexer-sparse-gram.js +98 -0
  39. package/core/indexing/indexer-utils.js +536 -0
  40. package/core/indexing/indexer-worker.js +148 -0
  41. package/core/indexing/li-skip-policy.js +225 -0
  42. package/core/indexing/merkle-tracker.js +244 -0
  43. package/core/indexing/model-pool.js +166 -0
  44. package/core/infrastructure/code-graph-repository.js +120 -0
  45. package/core/infrastructure/codebase-repository.js +131 -0
  46. package/core/infrastructure/config/dedup.js +54 -0
  47. package/core/infrastructure/config/embedding.js +298 -0
  48. package/core/infrastructure/config/graph.js +80 -0
  49. package/core/infrastructure/config/index.js +82 -0
  50. package/core/infrastructure/config/indexing.js +8 -0
  51. package/core/infrastructure/config/platform.js +254 -0
  52. package/core/infrastructure/config/ranking.js +221 -0
  53. package/core/infrastructure/config/search.js +396 -0
  54. package/core/infrastructure/config/translation.js +89 -0
  55. package/core/infrastructure/config/vector-store.js +114 -0
  56. package/core/infrastructure/constants.js +86 -0
  57. package/core/infrastructure/coreml-cascade.js +909 -0
  58. package/core/infrastructure/coreml-cascade.json +46 -0
  59. package/core/infrastructure/coreml-provider.js +81 -0
  60. package/core/infrastructure/db-utils.js +69 -0
  61. package/core/infrastructure/dedup-hashing.js +83 -0
  62. package/core/infrastructure/hardware-capability.js +332 -0
  63. package/core/infrastructure/index.js +104 -0
  64. package/core/infrastructure/language-patterns/maps.js +121 -0
  65. package/core/infrastructure/language-patterns/registry-core.js +323 -0
  66. package/core/infrastructure/language-patterns/registry-data-query.js +155 -0
  67. package/core/infrastructure/language-patterns/registry-object-oriented.js +285 -0
  68. package/core/infrastructure/language-patterns/registry-tooling.js +240 -0
  69. package/core/infrastructure/language-patterns/registry-web-style.js +143 -0
  70. package/core/infrastructure/language-patterns/registry.js +19 -0
  71. package/core/infrastructure/language-patterns.js +141 -0
  72. package/core/infrastructure/llm-provider.js +733 -0
  73. package/core/infrastructure/manifest.json +46 -0
  74. package/core/infrastructure/maxsim.wasm +0 -0
  75. package/core/infrastructure/model-fetcher.js +423 -0
  76. package/core/infrastructure/model-registry.js +214 -0
  77. package/core/infrastructure/native-inference.js +587 -0
  78. package/core/infrastructure/native-resolver.js +187 -0
  79. package/core/infrastructure/native-sparse-gram.js +257 -0
  80. package/core/infrastructure/native-tokenizer.js +160 -0
  81. package/core/infrastructure/onnx-mutex.js +45 -0
  82. package/core/infrastructure/onnx-session-utils.js +261 -0
  83. package/core/infrastructure/ort-pipeline.js +111 -0
  84. package/core/infrastructure/project-detector.js +102 -0
  85. package/core/infrastructure/quantization.js +410 -0
  86. package/core/infrastructure/simd-distance.js +502 -0
  87. package/core/infrastructure/simd-distance.wasm +0 -0
  88. package/core/infrastructure/tree-sitter-provider.js +665 -0
  89. package/core/infrastructure/webgpu-maxsim.js +222 -0
  90. package/core/query/index.js +35 -0
  91. package/core/query/intent-detector.js +201 -0
  92. package/core/query/intent-router.js +156 -0
  93. package/core/query/query-router-catboost.js +222 -0
  94. package/core/query/query-router-ml.js +266 -0
  95. package/core/query/query-router.js +213 -0
  96. package/core/ranking/cascaded-scorer.js +379 -0
  97. package/core/ranking/flashrank.js +810 -0
  98. package/core/ranking/index.js +49 -0
  99. package/core/ranking/late-interaction-index.js +2383 -0
  100. package/core/ranking/late-interaction-model.js +812 -0
  101. package/core/ranking/local-reranker.js +374 -0
  102. package/core/ranking/mmr.js +379 -0
  103. package/core/ranking/quality-scorer.js +363 -0
  104. package/core/search/context-expander.js +1167 -0
  105. package/core/search/dedup/sibling-expander.js +327 -0
  106. package/core/search/index.js +16 -0
  107. package/core/search/search-boost.js +259 -0
  108. package/core/search/search-cli.js +544 -0
  109. package/core/search/search-format.js +282 -0
  110. package/core/search/search-fusion.js +327 -0
  111. package/core/search/search-hybrid.js +204 -0
  112. package/core/search/search-pattern-chunks.js +337 -0
  113. package/core/search/search-pattern-planner.js +439 -0
  114. package/core/search/search-pattern-prefilter.js +412 -0
  115. package/core/search/search-pattern-ripgrep.js +663 -0
  116. package/core/search/search-pattern.js +463 -0
  117. package/core/search/search-postprocess.js +452 -0
  118. package/core/search/search-semantic.js +706 -0
  119. package/core/search/search-server.js +554 -0
  120. package/core/search/session-daemon-prewarm.mjs +164 -0
  121. package/core/search/session-warmup.js +595 -0
  122. package/core/search/sweet-search.js +632 -0
  123. package/core/search/warmup-metrics.js +532 -0
  124. package/core/start-server.js +6 -0
  125. package/core/training/query-router/features/extractor.js +762 -0
  126. package/core/training/query-router/features/multilingual-patterns.js +431 -0
  127. package/core/training/query-router/features/text-segmenter.js +303 -0
  128. package/core/training/query-router/features/unicode-utils.js +383 -0
  129. package/core/training/query-router/output/v45_router_d4.js +11521 -0
  130. package/core/training/query-router/output/v46_router_d4.js +11498 -0
  131. package/core/vector-store/binary-heap.js +227 -0
  132. package/core/vector-store/binary-hnsw-index.js +1004 -0
  133. package/core/vector-store/float-vector-store.js +234 -0
  134. package/core/vector-store/hnsw-index.js +580 -0
  135. package/core/vector-store/index.js +39 -0
  136. package/core/vector-store/seismic-index.js +498 -0
  137. package/core/vocabulary/index.js +84 -0
  138. package/core/vocabulary/vocab-constants.js +20 -0
  139. package/core/vocabulary/vocab-miner-extractors.js +375 -0
  140. package/core/vocabulary/vocab-miner-nl.js +404 -0
  141. package/core/vocabulary/vocab-miner-utils.js +146 -0
  142. package/core/vocabulary/vocab-miner.js +574 -0
  143. package/core/vocabulary/vocab-prewarm-cli.js +110 -0
  144. package/core/vocabulary/vocab-ranker.js +492 -0
  145. package/core/vocabulary/vocab-warmer.js +523 -0
  146. package/core/vocabulary/vocab-warmup-orchestrator.js +425 -0
  147. package/core/vocabulary/vocabulary-utils.js +704 -0
  148. package/crates/wasm-router/pkg/package.json +13 -0
  149. package/crates/wasm-router/pkg/query_router_wasm.d.ts +36 -0
  150. package/crates/wasm-router/pkg/query_router_wasm.js +271 -0
  151. package/crates/wasm-router/pkg/query_router_wasm_bg.wasm +0 -0
  152. package/crates/wasm-router/pkg/query_router_wasm_bg.wasm.d.ts +19 -0
  153. package/mcp/config-gen.js +121 -0
  154. package/mcp/server.js +335 -0
  155. package/mcp/tool-handlers.js +476 -0
  156. package/package.json +131 -9
  157. package/scripts/benchmark-harness.js +794 -0
  158. package/scripts/init.js +1058 -0
  159. package/scripts/smoke-test.js +435 -0
  160. package/scripts/uninstall.js +478 -0
  161. package/scripts/verify-runtime.js +176 -0
@@ -0,0 +1,762 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Feature Extractor for Query Router ML Model
5
+ *
6
+ * Extracts 29 language-agnostic features from queries for ML classification.
7
+ * These features work across ALL languages because they focus on:
8
+ * - Character patterns (camelCase, snake_case work in any codebase)
9
+ * - Statistical features (token count, uppercase ratio)
10
+ * - Script detection (non-ASCII ratio for CJK/Cyrillic)
11
+ * - Unicode identifier detection (UAX #31 compliant)
12
+ * - Multilingual keyword patterns (15+ languages)
13
+ *
14
+ * Target: <10μs feature extraction
15
+ */
16
+
17
+ import {
18
+ isUnicodeIdentifier,
19
+ hasNonAsciiIdentifierChars,
20
+ hasMixedScript,
21
+ hasIdentifierInQuery as hasUnicodeIdentifierInQuery,
22
+ hasNonAsciiIdentifierInQuery,
23
+ } from './unicode-utils.js';
24
+
25
+ import {
26
+ hasStructuralKeywordNonEn,
27
+ hasSemanticKeywordNonEn,
28
+ hasCrossScriptConcept,
29
+ } from './multilingual-patterns.js';
30
+
31
+ /**
32
+ * Extract 15 hand-crafted language-agnostic features.
33
+ * These features generalize across all programming languages and natural languages.
34
+ *
35
+ * @param {string} query - The query to extract features from
36
+ * @returns {Float32Array} - 15-element feature vector
37
+ */
38
+ export function extractFeatures(query) {
39
+ const trimmed = query.trim();
40
+ const chars = trimmed.replace(/\s/g, '');
41
+ const tokens = trimmed.split(/\s+/);
42
+ const charLen = Math.max(1, chars.length);
43
+
44
+ return new Float32Array([
45
+ // === Identifier Detection (5 features) ===
46
+ // Feature 0: hasCamelBoundary - lowercase followed by uppercase
47
+ /[a-z][A-Z]/.test(trimmed) ? 1 : 0,
48
+
49
+ // Feature 1: hasUnderscoreWord - underscore followed by lowercase (snake_case)
50
+ /_[a-z]/.test(trimmed) ? 1 : 0,
51
+
52
+ // Feature 2: startsWithUppercase - PascalCase indicator
53
+ /^[A-Z]/.test(trimmed) ? 1 : 0,
54
+
55
+ // Feature 3: hasConsecutiveUppercase - SCREAMING_SNAKE or abbreviations
56
+ /[A-Z]{2,}/.test(trimmed) ? 1 : 0,
57
+
58
+ // Feature 4: isSingleToken - single word without spaces
59
+ !trimmed.includes(' ') && trimmed.length > 2 ? 1 : 0,
60
+
61
+ // === Code Pattern Detection (4 features) ===
62
+ // Feature 5: hasDotNotation - method.call or package.name
63
+ /\w\.\w/.test(trimmed) ? 1 : 0,
64
+
65
+ // Feature 6: hasParentheses - method()
66
+ /\(\)/.test(trimmed) ? 1 : 0,
67
+
68
+ // Feature 7: hasFileExtension - .java, .ts, .py, etc.
69
+ /\.(java|ts|tsx|js|jsx|py|go|rs|cpp|c|h|rb|php|swift|kt|proto|yml|yaml|json|xml|md)$/i.test(trimmed) ? 1 : 0,
70
+
71
+ // Feature 8: hasPathSeparator - / or \
72
+ /[/\\]/.test(trimmed) ? 1 : 0,
73
+
74
+ // === Statistical Features (4 features) ===
75
+ // Feature 9: tokenCount - number of whitespace-separated tokens
76
+ tokens.length,
77
+
78
+ // Feature 10: avgTokenLength - average length per token
79
+ chars.length / Math.max(1, tokens.length),
80
+
81
+ // Feature 11: uppercaseRatio - fraction of uppercase chars
82
+ (chars.match(/[A-Z]/g) || []).length / charLen,
83
+
84
+ // Feature 12: digitRatio - fraction of digit chars
85
+ (chars.match(/[0-9]/g) || []).length / charLen,
86
+
87
+ // === Multilingual Detection (2 features) ===
88
+ // Feature 13: nonAsciiRatio - fraction of non-ASCII chars
89
+ (chars.match(/[^\x00-\x7F]/g) || []).length / charLen,
90
+
91
+ // Feature 14: hasNonLatinScript - CJK, Cyrillic, Arabic, etc.
92
+ /[\u4e00-\u9fff\u0400-\u04ff\u0600-\u06ff\u0980-\u09ff\u3040-\u30ff\uac00-\ud7af]/.test(trimmed) ? 1 : 0,
93
+ ]);
94
+ }
95
+
96
+ /**
97
+ * Get feature names for interpretability and debugging.
98
+ */
99
+ export function getFeatureNames() {
100
+ return [
101
+ 'hasCamelBoundary', // 0
102
+ 'hasUnderscoreWord', // 1
103
+ 'startsWithUppercase', // 2
104
+ 'hasConsecutiveUppercase', // 3
105
+ 'isSingleToken', // 4
106
+ 'hasDotNotation', // 5
107
+ 'hasParentheses', // 6
108
+ 'hasFileExtension', // 7
109
+ 'hasPathSeparator', // 8
110
+ 'tokenCount', // 9
111
+ 'avgTokenLength', // 10
112
+ 'uppercaseRatio', // 11
113
+ 'digitRatio', // 12
114
+ 'nonAsciiRatio', // 13
115
+ 'hasNonLatinScript', // 14
116
+ ];
117
+ }
118
+
119
+ /**
120
+ * Additional structural pattern features.
121
+ * These detect specific query patterns for structural queries.
122
+ */
123
+ export function extractStructuralFeatures(query) {
124
+ const lower = query.toLowerCase();
125
+
126
+ return new Float32Array([
127
+ // Caller patterns
128
+ /\b(callers?\s+of|what\s+calls|who\s+calls|usages?\s+of|references?\s+to|where\s+is\s+\w+\s+called)\b/.test(lower) ? 1 : 0,
129
+
130
+ // Callee patterns
131
+ /\b(callees?\s+of|what\s+does\s+\w+\s+call|dependencies\s+of|methods?\s+called\s+by)\b/.test(lower) ? 1 : 0,
132
+
133
+ // Implementation patterns
134
+ /\b(implementations?\s+of|classes?\s+implementing|who\s+extends|subtypes?\s+of)\b/.test(lower) ? 1 : 0,
135
+
136
+ // Impact patterns
137
+ /\b(impact\s+of|affected\s+by|depends?\s+on|downstream\s+effects?|will\s+break)\b/.test(lower) ? 1 : 0,
138
+
139
+ // Has identifier in query (PascalCase, camelCase, or Unicode identifier)
140
+ // Updated to use UAX #31 compliant Unicode identifier detection
141
+ hasUnicodeIdentifierInQuery(query) ? 1 : 0,
142
+ ]);
143
+ }
144
+
145
+ /**
146
+ * Additional semantic pattern features.
147
+ * These detect conceptual/question patterns.
148
+ */
149
+ export function extractSemanticFeatures(query) {
150
+ const lower = query.toLowerCase();
151
+
152
+ return new Float32Array([
153
+ // Question words at start
154
+ /^(how|what|why|where|when|which|who|can|does|is|are|should)\s/.test(lower) ? 1 : 0,
155
+
156
+ // Ends with question mark
157
+ query.trim().endsWith('?') ? 1 : 0,
158
+
159
+ // Semantic concept words (expanded for HYBRID accuracy)
160
+ /\b(flow|process|mechanism|strategy|pattern|approach|logic|algorithm|system|detection|validation|authentication|handling|database|quer(?:y|ies)|token|management|security|checks?|session|config(?:uration)?|error|request|response|service|login|password|reset|user|data|storage|cache|sync(?:hronization)?)\b/.test(lower) ? 1 : 0,
161
+
162
+ // Explanation requests
163
+ /\b(explain|describe|understand|overview|architecture|design|how|why|what)\b/.test(lower) ? 1 : 0,
164
+
165
+ // How-does-X-work pattern
166
+ /how\s+does\s+.+\s+work/.test(lower) ? 1 : 0,
167
+ ]);
168
+ }
169
+
170
+ /**
171
+ * Extract discriminative features (4 features for HYBRID vs STRUCTURAL distinction).
172
+ * These features address the "structural vacuum" problem identified by Gemini.
173
+ *
174
+ * Key insight: HYBRID has identifier at START, STRUCTURAL has identifier at END
175
+ * "AuthService login flow" → HYBRID (identifier first, then concept)
176
+ * "callers of AuthService" → STRUCTURAL (structural keyword, then identifier)
177
+ *
178
+ * @param {string} query - The query to extract features from
179
+ * @returns {Float32Array} - 4-element feature vector
180
+ */
181
+ export function extractDiscriminativeFeatures(query) {
182
+ const tokens = query.trim().split(/\s+/);
183
+ const lower = query.toLowerCase();
184
+
185
+ // Identifier patterns (Latin: PascalCase/camelCase, Non-Latin: 2+ consecutive non-ASCII)
186
+ const identifierPattern = /^[A-Z][a-z]+[A-Z]|^[a-z]+[A-Z]|^[A-Z][a-z]+(?:[A-Z][a-z]+)+$|^[\u0400-\u04ff\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af\u0900-\u097f]{2,}$/;
187
+
188
+ // Check first and last tokens for identifiers
189
+ const firstToken = tokens[0] || '';
190
+ const lastToken = tokens[tokens.length - 1] || '';
191
+ const identifierAtStart = identifierPattern.test(firstToken) ? 1 : 0;
192
+ const identifierAtEnd = tokens.length > 1 && identifierPattern.test(lastToken) ? 1 : 0;
193
+
194
+ // Explicit structural keywords (simple word list, not complex patterns)
195
+ const structuralKeywords = /\b(calls?|callers?|callees?|uses?|usages?|references?|implementations?|implements?|extends?|dependencies|subtypes?|impact|affected|downstream)\b/;
196
+ const hasExplicitStructuralKeyword = structuralKeywords.test(lower) ? 1 : 0;
197
+
198
+ // Unknown trailing content: has identifier at start + more words, but NO structural keywords and NO semantic concepts
199
+ // This is the "net" to catch domain-specific concepts like "virus scanning", "stock alerts"
200
+ const hasSemanticConcept = /\b(flow|process|mechanism|strategy|pattern|approach|logic|algorithm|system|detection|validation|authentication|handling|database|quer(?:y|ies)|token|management|security|checks?|session|config(?:uration)?|error|request|response|service|login|password|reset|user|data|storage|cache|sync(?:hronization)?)\b/.test(lower);
201
+
202
+ const hasUnknownTrailingContent = (
203
+ tokens.length > 1 &&
204
+ identifierAtStart === 1 &&
205
+ hasExplicitStructuralKeyword === 0 &&
206
+ !hasSemanticConcept
207
+ ) ? 1 : 0;
208
+
209
+ return new Float32Array([
210
+ // Feature 30: identifierAtStart - Strong HYBRID signal
211
+ identifierAtStart,
212
+
213
+ // Feature 31: identifierAtEnd - Strong STRUCTURAL signal (e.g., "callers of AuthService")
214
+ identifierAtEnd,
215
+
216
+ // Feature 32: hasExplicitStructuralKeyword - Explicit structural intent
217
+ hasExplicitStructuralKeyword,
218
+
219
+ // Feature 33: hasUnknownTrailingContent - Identifier + unknown words = likely HYBRID
220
+ // Catches: "FileUploader virus scanning", "JobScheduler failure recovery"
221
+ hasUnknownTrailingContent,
222
+ ]);
223
+ }
224
+
225
+ /**
226
+ * Extract Grammar Layer features (4 features for modeling query structure).
227
+ * These features describe WHERE things are, not just IF they exist.
228
+ *
229
+ * Key insight: The model memorized words instead of grammar. These features
230
+ * teach the structure of developer intent.
231
+ *
232
+ * @param {string} query - The query to extract features from
233
+ * @returns {Float32Array} - 4-element feature vector
234
+ */
235
+ export function extractGrammarFeatures(query) {
236
+ const tokens = query.trim().split(/\s+/);
237
+ const lower = query.toLowerCase();
238
+
239
+ // Identifier pattern (PascalCase, camelCase, non-Latin identifiers)
240
+ const identifierPattern = /^[A-Z][a-z]+[A-Z]|^[a-z]+[A-Z]|^[A-Z][a-z]+(?:[A-Z][a-z]+)+$|^[\u0400-\u04ff\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af\u0900-\u097f]{2,}$/;
241
+
242
+ // Feature 34: identifierIndex - Which token position has the identifier?
243
+ // HYBRID: Usually 0 ("AuthService...") or 2 ("How does AuthService...")
244
+ // STRUCTURAL: Usually 3+ ("callers of AuthService", "what calls AuthService")
245
+ let identifierIndex = -1; // -1 means no identifier found
246
+ for (let i = 0; i < tokens.length; i++) {
247
+ if (identifierPattern.test(tokens[i])) {
248
+ identifierIndex = i;
249
+ break; // Take first identifier position
250
+ }
251
+ }
252
+ // Normalize to 0-1 range: -1 → 0, 0 → 0.1, 1 → 0.2, ..., 5+ → 0.6+
253
+ const normalizedIdentifierIndex = identifierIndex < 0 ? 0 : Math.min(1, (identifierIndex + 1) / 10);
254
+
255
+ // Feature 35: verbPresence - Action verbs that are HYBRID signals (never in structural queries)
256
+ // These verbs describe "how something works" rather than "what calls what"
257
+ const actionVerbs = /\b(handle|handles?|handling|send|sends?|sending|calculate|calculates?|calculating|compute|computes?|computing|parse|parses?|parsing|validate|validates?|validating|trigger|triggers?|triggering|perform|performs?|performing|initialize|initializes?|initializing|process|processes|processing|execute|executes?|executing|generate|generates?|generating|fetch|fetches?|fetching|store|stores?|storing|load|loads?|loading|save|saves?|saving|create|creates?|creating|update|updates?|updating|delete|deletes?|deleting|render|renders?|rendering|display|displays?|displaying|format|formats?|formatting|convert|converts?|converting|transform|transforms?|transforming|encrypt|encrypts?|encrypting|decrypt|decrypts?|decrypting|authenticate|authenticates?|authenticating|authorize|authorizes?|authorizing|schedule|schedules?|scheduling|batch|batches?|batching|queue|queues?|queueing|cache|caches?|caching|log|logs?|logging|track|tracks?|tracking|monitor|monitors?|monitoring|retry|retries?|retrying|recover|recovers?|recovering)\b/;
258
+ const hasActionVerb = actionVerbs.test(lower) ? 1 : 0;
259
+
260
+ // Feature 36: hasQuestionIdentifier - "How does [Identifier]..." pattern
261
+ // This is the KEY pattern that fails: "how does PaymentGateway handle failures"
262
+ // Logic: isQuestion AND tokens[2] or tokens[3] is an identifier
263
+ const isQuestion = /^(how|what|why|where|when|which|who|can|does|is|are|should)\s/i.test(query);
264
+ const hasQuestionIdentifier = (
265
+ isQuestion &&
266
+ tokens.length > 2 &&
267
+ (identifierPattern.test(tokens[2]) || (tokens.length > 3 && identifierPattern.test(tokens[3])))
268
+ ) ? 1 : 0;
269
+
270
+ // Feature 37: structuralKeywordCount - Raw count of structural keywords
271
+ // If this is 0, probability of STRUCTURAL should be near-zero
272
+ const structuralKeywordsList = [
273
+ 'callers', 'caller', 'calls', 'call',
274
+ 'callees', 'callee',
275
+ 'uses', 'use', 'usages', 'usage',
276
+ 'references', 'reference',
277
+ 'implementations', 'implementation', 'implements', 'implement',
278
+ 'extends', 'extend',
279
+ 'dependencies', 'dependency',
280
+ 'subtypes', 'subtype',
281
+ 'impact', 'affected', 'downstream',
282
+ 'inherits', 'inherit',
283
+ ];
284
+ let structuralKeywordCount = 0;
285
+ for (const kw of structuralKeywordsList) {
286
+ const regex = new RegExp(`\\b${kw}\\b`, 'i');
287
+ if (regex.test(lower)) {
288
+ structuralKeywordCount++;
289
+ }
290
+ }
291
+ // Normalize: 0→0, 1→0.2, 2→0.4, 3+→0.6+
292
+ const normalizedStructuralCount = Math.min(1, structuralKeywordCount / 5);
293
+
294
+ return new Float32Array([
295
+ normalizedIdentifierIndex, // 34: Where is the identifier? (0 = none, 0.1 = first, etc.)
296
+ hasActionVerb, // 35: Action verb present? (massive HYBRID signal)
297
+ hasQuestionIdentifier, // 36: "How does [ID]..." pattern (HYBRID)
298
+ normalizedStructuralCount, // 37: Count of structural keywords (STRUCTURAL signal)
299
+ ]);
300
+ }
301
+
302
+ /**
303
+ * Extract "Zen" features (5 features for 99.9% accuracy).
304
+ * These are the "Master Features" that fix the remaining 4 roadblocks:
305
+ *
306
+ * 1. CJK Single Token Trap - CJK text without spaces gets isSingleToken=1
307
+ * 2. Question vs Structure Conflict - Structural questions misrouted to SEMANTIC
308
+ * 3. German Compound Problem - Long compound nouns mistaken for identifiers
309
+ * 4. Cross-Feature Interactions - Features that depend on OTHER feature values
310
+ *
311
+ * @param {string} query - The query to extract features from
312
+ * @returns {Float32Array} - 5-element feature vector
313
+ */
314
+ export function extractZenFeatures(query) {
315
+ const trimmed = query.trim();
316
+ const chars = trimmed.replace(/\s/g, '');
317
+ const tokens = trimmed.split(/\s+/);
318
+ const lower = query.toLowerCase();
319
+
320
+ // =========================================================================
321
+ // Feature 43: cjkDensity - CJK character ratio (force-stops LEXICAL for CJK)
322
+ // =========================================================================
323
+ // CJK ranges: Chinese (4e00-9fff), Japanese Hiragana (3040-309f),
324
+ // Katakana (30a0-30ff), Korean (ac00-d7af)
325
+ const cjkChars = (chars.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length;
326
+ const cjkDensity = cjkChars / Math.max(1, chars.length);
327
+
328
+ // =========================================================================
329
+ // Feature 44: effectiveTokenCount - Virtual tokenization for CJK
330
+ // =========================================================================
331
+ // Problem: "認証の仕組み" has 0 spaces → tokenCount=1 → LEXICAL
332
+ // Fix: For CJK text, estimate tokens by character count / 2 (avg CJK word ~2 chars)
333
+ // or by script transitions (e.g., Kanji→Hiragana boundaries in Japanese)
334
+ let effectiveTokenCount = tokens.length;
335
+ if (cjkDensity > 0.5) {
336
+ // Count script transitions (Kanji↔Hiragana↔Katakana)
337
+ const scriptTransitions = (trimmed.match(
338
+ /[\u4e00-\u9fff][\u3040-\u30ff]|[\u3040-\u30ff][\u4e00-\u9fff]|[\u3040-\u309f][\u30a0-\u30ff]|[\u30a0-\u30ff][\u3040-\u309f]/g
339
+ ) || []).length;
340
+
341
+ // Use max of: space-separated tokens, script transitions + 1, or charCount/3
342
+ effectiveTokenCount = Math.max(
343
+ tokens.length,
344
+ scriptTransitions + 1,
345
+ Math.ceil(cjkChars / 3)
346
+ );
347
+ }
348
+ // Normalize to 0-1 range: 1→0.1, 5→0.5, 10+→1.0
349
+ const normalizedEffectiveTokenCount = Math.min(1, effectiveTokenCount / 10);
350
+
351
+ // =========================================================================
352
+ // Feature 45: isComplexQuestion - (tokenCount >= 4) && startsWithQuestionWord
353
+ // =========================================================================
354
+ // Distinguishes "how does X work" (SEMANTIC) from "X logic" (HYBRID)
355
+ // Complex questions with 4+ tokens are almost always SEMANTIC
356
+ const startsWithQuestionWord = /^(how|what|why|where|when|which|who|can|does|is|are|should)\s/i.test(trimmed);
357
+ const isComplexQuestion = (effectiveTokenCount >= 4 && startsWithQuestionWord) ? 1 : 0;
358
+
359
+ // =========================================================================
360
+ // Feature 46: isStructuralQuestion - Cross-feature interaction
361
+ // =========================================================================
362
+ // Problem: "What invokes the webhook handler?" has both:
363
+ // - Structural verb (invokes) → STRUCTURAL
364
+ // - Question structure (What...) → SEMANTIC
365
+ // Fix: When asking about a relationship, STRUCTURAL intent overrides
366
+ const structuralKeywords = /\b(invoke[sd]?|invoking|call[sd]?|calling|callers?|callees?|use[sd]?|using|usages?|reference[sd]?|referencing|derived|inherit[sd]?|inheriting|implementations?|implements?|implementing|depends?|depended|depending|dependencies?|affects?|affected|affecting|downstream|upstream|subtypes?|extends?|extending)\b/i;
367
+ const hasStructuralKeyword = structuralKeywords.test(lower);
368
+ const isStructuralQuestion = (startsWithQuestionWord && hasStructuralKeyword) ? 1 : 0;
369
+
370
+ // =========================================================================
371
+ // Feature 47: tokenCharacterDensity - German compound detection
372
+ // =========================================================================
373
+ // Problem: "Fehlerbehandlungsstrategie" (Error handling strategy)
374
+ // - Single token, 24 chars, no camelCase/snake_case → LEXICAL (wrong!)
375
+ // - Actually a high-level concept noun → SEMANTIC
376
+ // Fix: Long tokens WITHOUT identifier boundaries (camelCase/snake_case) are
377
+ // likely German/Dutch compound concept words, not code identifiers.
378
+ // Normal identifiers: 10-20 chars with boundaries
379
+ // German concepts: 25-40 chars without boundaries
380
+ const hasCamelOrSnake = /[a-z][A-Z]|_[a-z]/.test(trimmed);
381
+ const avgCharPerToken = chars.length / Math.max(1, tokens.length);
382
+ // If single long token (>15 chars) WITHOUT identifier boundaries → likely concept
383
+ const tokenCharacterDensity = (tokens.length === 1 && avgCharPerToken > 15 && !hasCamelOrSnake)
384
+ ? Math.min(1, avgCharPerToken / 30) // Normalize: 15→0.5, 30→1.0
385
+ : 0;
386
+
387
+ // =========================================================================
388
+ // Feature 48: hasNonLatinIdentifier - Non-Latin PascalCase detection
389
+ // =========================================================================
390
+ // Problem: Cyrillic/German identifiers like "СервисПлатежа", "Bestellungsprozessor"
391
+ // don't have ASCII camelCase → misclassified as SEMANTIC
392
+ // Fix: Detect non-Latin PascalCase (capital letter + lowercase letters)
393
+ const cyrillicPascalCase = /^[\u0410-\u042f][\u0430-\u044f]+(?:[\u0410-\u042f][\u0430-\u044f]+)*$/;
394
+ const germanPascalCase = /^[A-ZÄÖÜ][a-zäöüß]+(?:[a-zäöüß]+)*$/;
395
+ const greekPascalCase = /^[\u0391-\u03a9][\u03b1-\u03c9]+$/;
396
+ const hasNonLatinIdentifier = tokens.length === 1 && (
397
+ cyrillicPascalCase.test(tokens[0]) ||
398
+ germanPascalCase.test(tokens[0]) ||
399
+ greekPascalCase.test(tokens[0])
400
+ ) ? 1 : 0;
401
+
402
+ // =========================================================================
403
+ // Feature 49: isAllCapsConstant - ALL_CAPS constant detection
404
+ // =========================================================================
405
+ // Problem: ALL_CAPS constants (MAX_CONNECTION_POOL_SIZE) classified as SEMANTIC
406
+ // because they have high uppercaseRatio but no camelCase
407
+ // Fix: Detect ALL_CAPS pattern (all uppercase with underscores)
408
+ const allCapsPattern = /^[A-Z][A-Z0-9_]+$/;
409
+ const isAllCapsConstant = tokens.some(t => allCapsPattern.test(t) && t.length > 3) ? 1 : 0;
410
+
411
+ return new Float32Array([
412
+ cjkDensity, // 43: CJK character ratio
413
+ normalizedEffectiveTokenCount, // 44: Effective token count (CJK-aware)
414
+ isComplexQuestion, // 45: Long question (>4 tokens)
415
+ isStructuralQuestion, // 46: Question + structural keyword
416
+ tokenCharacterDensity, // 47: German compound detector
417
+ hasNonLatinIdentifier, // 48: Non-Latin PascalCase (Cyrillic/German)
418
+ isAllCapsConstant, // 49: ALL_CAPS constant pattern
419
+ ]);
420
+ }
421
+
422
+ /**
423
+ * Extract "Final Push" features (5 features for 99%+ accuracy).
424
+ * These fix the remaining cross-language and structural edge cases.
425
+ *
426
+ * Key insight: The model is "over-indexed" on HYBRID signals, causing it to
427
+ * see "ghost identifiers" in pure native language queries.
428
+ *
429
+ * @param {string} query - The query to extract features from
430
+ * @returns {Float32Array} - 5-element feature vector
431
+ */
432
+ export function extractFinalPushFeatures(query) {
433
+ const tokens = query.trim().split(/\s+/);
434
+ const lower = query.toLowerCase();
435
+ const chars = query.replace(/\s/g, '');
436
+
437
+ // =========================================================================
438
+ // Feature 38: hasVerifiedAsciiIdentifier - STRICT ASCII identifier check
439
+ // =========================================================================
440
+ // Returns 1 ONLY if there's a clear ASCII PascalCase, camelCase, or snake_case.
441
+ // If this is 0, the model should avoid HYBRID/LEXICAL (no identifier to lookup).
442
+ const strictAsciiIdentifier = /^[A-Z][a-z]+[A-Z][a-zA-Z]*$|^[a-z]+[A-Z][a-zA-Z]*$|^[a-z]+_[a-z]+(?:_[a-z]+)*$|^[A-Z]+_[A-Z]+(?:_[A-Z]+)*$/;
443
+ const hasVerifiedAsciiIdentifier = tokens.some(t => strictAsciiIdentifier.test(t)) ? 1 : 0;
444
+
445
+ // =========================================================================
446
+ // Feature 39: structuralTemplateMatch - "[Verb] [Article] [Noun]" pattern
447
+ // =========================================================================
448
+ // Detects: "what invokes the handler", "who calls the service"
449
+ // These are classic call-graph questions that the keyword list missed.
450
+ const structuralTemplates = [
451
+ /\b(what|who|which)\s+(invokes?|calls?|uses?|references?|depends\s+on)\s+(the|a|an)?\s*\w+/i,
452
+ /\b(what|who|which)\s+does\s+\w+\s+(invoke|call|use|reference|depend)/i,
453
+ /\binvok(?:es?|ed|ing)\s+(the|a|an)?\s*\w+/i,
454
+ /\bderived\s+from\b/i,
455
+ /\binherits?\s+from\b/i,
456
+ /\b(?:classes?|types?)\s+(?:that\s+)?implement(?:s|ing)?\b/i,
457
+ /\busages?\s+of\b/i,
458
+ ];
459
+ const structuralTemplateMatch = structuralTemplates.some(t => t.test(lower)) ? 1 : 0;
460
+
461
+ // =========================================================================
462
+ // Feature 40: hasNonEnglishConceptStem - Cross-language concept stems
463
+ // =========================================================================
464
+ // Detects German, Spanish, French, Russian, Serbian concept words by prefix.
465
+ // This helps identify SEMANTIC queries in non-English languages.
466
+ const conceptStems = new RegExp(
467
+ // German stems
468
+ '(authentifiz|konfig|verarbeit|verwalt|fehler|behandl|validier|' +
469
+ 'speicher|laden|senden|empfang|format|konvert|verschlüssel|' +
470
+ 'strategie|mechanismus|prozess|logik|system|architektur|' +
471
+ 'zahlungs?|benutzer|sitzung|nachricht|fehlerbehandlung|ereignis)' +
472
+ // Spanish stems
473
+ '|(autentic|config|valid|proces|mane[jx]|error|almacen|carga|' +
474
+ 'enviar|recib|format|convert|cifr|estrateg|mecan|proces|' +
475
+ 'lógic|sistem|arquitectur|pag|usuario|sesión|mensaje)' +
476
+ // French stems
477
+ '|(authenti[fq]|config|valid|trait|gest|erreur|stockag|charg|' +
478
+ 'envoy|recev|format|convert|chiffr|stratég|mécan|process|' +
479
+ 'logiq|système|architect|paie|utilisat|session|messag)' +
480
+ // Russian stems (Cyrillic)
481
+ '|(аутентификац|конфигурац|валидац|обработ|управлен|ошибк|' +
482
+ 'хранен|загруз|отправ|получ|формат|преобраз|шифрован|' +
483
+ 'стратег|механизм|процесс|логик|систем|архитектур|' +
484
+ 'платеж|пользовател|сесси|сообщен)' +
485
+ // Serbian stems (Cyrillic)
486
+ '|(аутентификациј|конфигурациј|валидациј|обрад|управљањ|грешк|' +
487
+ 'складиштењ|учитавањ|слањ|примањ|формат|конверт|шифровањ|' +
488
+ 'стратегиј|механизам|процес|логик|систем|архитектур|' +
489
+ 'плаћањ|корисник|сесиј|порук|кеширањ)',
490
+ 'i'
491
+ );
492
+ const hasNonEnglishConceptStem = conceptStems.test(query) ? 1 : 0;
493
+
494
+ // =========================================================================
495
+ // Feature 41: pureNativeLanguageScore - High non-ASCII with no identifiers
496
+ // =========================================================================
497
+ // If nonAsciiRatio > 0.7 AND hasVerifiedAsciiIdentifier == 0, this is
498
+ // almost certainly a SEMANTIC query in a non-English language.
499
+ const nonAsciiCount = (chars.match(/[^\x00-\x7F]/g) || []).length;
500
+ const nonAsciiRatio = nonAsciiCount / Math.max(1, chars.length);
501
+ const pureNativeLanguageScore = (nonAsciiRatio > 0.7 && hasVerifiedAsciiIdentifier === 0) ? 1 : 0;
502
+
503
+ // =========================================================================
504
+ // Feature 42: expandedStructuralKeywordPresence
505
+ // =========================================================================
506
+ // Expanded list of structural keywords including ALL forms (base + conjugations).
507
+ // Critical: Include base forms like "use", "call" not just "uses", "calls"
508
+ const expandedStructuralKeywords = /\b(invoke[sd]?|invoking|call[sd]?|calling|callers?|callees?|use[sd]?|using|usages?|reference[sd]?|referencing|derived|inherit[sd]?|inheriting|implementations?|implements?|implementing|depends?|depended|depending|dependencies?|affects?|affected|affecting|downstream|upstream|subtypes?|extends?|extending)\b/i;
509
+ const expandedStructuralKeywordPresence = expandedStructuralKeywords.test(lower) ? 1 : 0;
510
+
511
+ return new Float32Array([
512
+ hasVerifiedAsciiIdentifier, // 38: Strict ASCII identifier present?
513
+ structuralTemplateMatch, // 39: "[Verb] [Article] [Noun]" pattern?
514
+ hasNonEnglishConceptStem, // 40: Non-English concept word stem?
515
+ pureNativeLanguageScore, // 41: Pure native language (no identifiers)?
516
+ expandedStructuralKeywordPresence, // 42: Expanded structural keywords?
517
+ ]);
518
+ }
519
+
520
+ /**
521
+ * Extract multilingual features (5 features for Unicode/non-English support).
522
+ * These detect Unicode identifiers and non-English patterns.
523
+ *
524
+ * @param {string} query - The query to extract features from
525
+ * @returns {Float32Array} - 5-element feature vector
526
+ */
527
+ export function extractMultilingualFeatures(query) {
528
+ // Split into tokens for identifier analysis
529
+ const tokens = query.split(/[\s,;:!?()[\]{}'"]+/).filter(t => t.length > 0);
530
+
531
+ return new Float32Array([
532
+ // Feature 25: hasNonAsciiIdentifier - Query contains a non-ASCII Unicode identifier
533
+ // Detects: КорисникСервис, 用户服务, ユーザーサービス
534
+ hasNonAsciiIdentifierInQuery(query) ? 1 : 0,
535
+
536
+ // Feature 26: hasMixedScriptToken - Any token has multiple scripts (code-switching)
537
+ // Detects: 用户Service, AuthСервис
538
+ tokens.some(t => hasMixedScript(t)) ? 1 : 0,
539
+
540
+ // Feature 27: hasStructuralKeywordNonEn - Non-English structural pattern
541
+ // Detects: "шта позива", "什么调用", "を呼び出す"
542
+ hasStructuralKeywordNonEn(query) ? 1 : 0,
543
+
544
+ // Feature 28: hasSemanticKeywordNonEn - Non-English semantic pattern
545
+ // Detects: "как работает", "如何", "どのように"
546
+ hasSemanticKeywordNonEn(query) ? 1 : 0,
547
+
548
+ // Feature 29: hasCrossScriptConcept - Identifier in one script + concept words in another
549
+ // Detects: "समयट्रैकर security checks", "database queries in समयट्रैकर"
550
+ // Strong HYBRID signal - mixing identifier lookup with conceptual context
551
+ hasCrossScriptConcept(query) ? 1 : 0,
552
+ ]);
553
+ }
554
+
555
+ /**
556
+ * Extract ALL features (15 base + 5 structural + 5 semantic + 5 multilingual + 4 discriminative + 4 grammar + 5 final + 7 zen = 50 total).
557
+ */
558
+ export function extractAllFeatures(query) {
559
+ const base = extractFeatures(query);
560
+ const structural = extractStructuralFeatures(query);
561
+ const semantic = extractSemanticFeatures(query);
562
+ const multilingual = extractMultilingualFeatures(query);
563
+ const discriminative = extractDiscriminativeFeatures(query);
564
+ const grammar = extractGrammarFeatures(query);
565
+ const finalPush = extractFinalPushFeatures(query);
566
+ const zen = extractZenFeatures(query);
567
+
568
+ // Concatenate all features
569
+ const all = new Float32Array(50);
570
+ all.set(base, 0);
571
+ all.set(structural, 15);
572
+ all.set(semantic, 20);
573
+ all.set(multilingual, 25);
574
+ all.set(discriminative, 30);
575
+ all.set(grammar, 34);
576
+ all.set(finalPush, 38);
577
+ all.set(zen, 43);
578
+
579
+ return all;
580
+ }
581
+
582
+ /**
583
+ * Get all feature names including structural, semantic, multilingual, discriminative, grammar, final push, and zen.
584
+ */
585
+ export function getAllFeatureNames() {
586
+ return [
587
+ ...getFeatureNames(),
588
+ // Structural features (15-19)
589
+ 'hasCallerPattern',
590
+ 'hasCalleePattern',
591
+ 'hasImplementationPattern',
592
+ 'hasImpactPattern',
593
+ 'hasIdentifierInQuery',
594
+ // Semantic features (20-24)
595
+ 'startsWithQuestionWord',
596
+ 'endsWithQuestionMark',
597
+ 'hasSemanticConceptWord',
598
+ 'hasExplanationRequest',
599
+ 'hasHowDoesWorkPattern',
600
+ // Multilingual features (25-29)
601
+ 'hasNonAsciiIdentifier',
602
+ 'hasMixedScriptToken',
603
+ 'hasStructuralKeywordNonEn',
604
+ 'hasSemanticKeywordNonEn',
605
+ 'hasCrossScriptConcept',
606
+ // Discriminative features (30-33) - HYBRID vs STRUCTURAL distinction
607
+ 'identifierAtStart',
608
+ 'identifierAtEnd',
609
+ 'hasExplicitStructuralKeyword',
610
+ 'hasUnknownTrailingContent',
611
+ // Grammar features (34-37) - Query structure modeling
612
+ 'identifierIndex', // 34: Token position of identifier (normalized)
613
+ 'hasActionVerb', // 35: Action verb presence (HYBRID signal)
614
+ 'hasQuestionIdentifier', // 36: "How does [ID]..." pattern
615
+ 'structuralKeywordCount', // 37: Count of structural keywords (normalized)
616
+ // Final Push features (38-42) - 99%+ accuracy
617
+ 'hasVerifiedAsciiIdentifier', // 38: Strict ASCII identifier check
618
+ 'structuralTemplateMatch', // 39: "[Verb] [Article] [Noun]" pattern
619
+ 'hasNonEnglishConceptStem', // 40: Non-English concept word stem
620
+ 'pureNativeLanguageScore', // 41: Pure native language (no identifiers)
621
+ 'expandedStructuralKeywordPresence', // 42: Expanded structural keywords
622
+ // Zen features (43-49) - 99.9% "Master Features"
623
+ 'cjkDensity', // 43: CJK character ratio (blocks LEXICAL for CJK)
624
+ 'effectiveTokenCount', // 44: Virtual token count (CJK-aware)
625
+ 'isComplexQuestion', // 45: Long question (>4 effective tokens)
626
+ 'isStructuralQuestion', // 46: Question + structural keyword (cross-feature)
627
+ 'tokenCharacterDensity', // 47: German compound detector
628
+ 'hasNonLatinIdentifier', // 48: Non-Latin PascalCase (Cyrillic/German)
629
+ 'isAllCapsConstant', // 49: ALL_CAPS constant pattern
630
+ ];
631
+ }
632
+
633
+ /**
634
+ * Convert features to object for debugging.
635
+ *
636
+ * P1 Fix: Added bounds check to prevent undefined feature names
637
+ */
638
+ export function featuresToObject(features, names = null) {
639
+ const featureNames = names || (features.length <= 15 ? getFeatureNames() : getAllFeatureNames());
640
+ const obj = {};
641
+ for (let i = 0; i < features.length; i++) {
642
+ // P1 Fix: Bounds check to handle mismatched lengths
643
+ const name = i < featureNames.length ? featureNames[i] : `feature_${i}`;
644
+ obj[name] = features[i];
645
+ }
646
+ return obj;
647
+ }
648
+
649
+ /**
650
+ * Benchmark feature extraction latency.
651
+ */
652
+ export function benchmarkFeatureExtraction(queries, iterations = 1000) {
653
+ const start = performance.now();
654
+
655
+ for (let i = 0; i < iterations; i++) {
656
+ for (const query of queries) {
657
+ extractAllFeatures(query);
658
+ }
659
+ }
660
+
661
+ const end = performance.now();
662
+ const totalQueries = iterations * queries.length;
663
+ const avgLatency = ((end - start) / totalQueries) * 1000; // microseconds
664
+
665
+ return {
666
+ totalTime_ms: end - start,
667
+ totalQueries,
668
+ avgLatency_us: avgLatency.toFixed(2),
669
+ };
670
+ }
671
+
672
+ // CLI
673
+ if (import.meta.url === `file://${process.argv[1]}`) {
674
+ const args = process.argv.slice(2);
675
+
676
+ if (args.length === 0 || args[0] === '--help') {
677
+ console.log(`
678
+ Feature Extractor CLI
679
+
680
+ Usage:
681
+ extractor.js "<query>" Extract features for a single query
682
+ extractor.js --benchmark Benchmark feature extraction speed
683
+ extractor.js --names List feature names
684
+
685
+ Examples:
686
+ extractor.js "AuthService"
687
+ extractor.js "how does authentication work"
688
+ extractor.js "what calls AuthService"
689
+ `);
690
+ process.exit(0);
691
+ }
692
+
693
+ if (args[0] === '--names') {
694
+ console.log('\nFeature Names (29 total):');
695
+ getAllFeatureNames().forEach((name, i) => {
696
+ console.log(` ${i.toString().padStart(2)}: ${name}`);
697
+ });
698
+ process.exit(0);
699
+ }
700
+
701
+ if (args[0] === '--benchmark') {
702
+ const testQueries = [
703
+ 'AuthService',
704
+ 'how does authentication work',
705
+ 'what calls AuthService',
706
+ 'AuthService login flow',
707
+ 'session management',
708
+ 'callers of EmployeeService',
709
+ 'getUserById',
710
+ 'как функционише аутентификација',
711
+ ];
712
+
713
+ const result = benchmarkFeatureExtraction(testQueries);
714
+ console.log('\nBenchmark Results:');
715
+ console.log(` Total queries: ${result.totalQueries}`);
716
+ console.log(` Total time: ${result.totalTime_ms.toFixed(2)}ms`);
717
+ console.log(` Avg latency: ${result.avgLatency_us}μs per query`);
718
+ console.log(` Target: <10μs`);
719
+
720
+ if (parseFloat(result.avgLatency_us) < 10) {
721
+ console.log(' ✓ Target met!');
722
+ } else {
723
+ console.log(' ✗ Target not met');
724
+ }
725
+ process.exit(0);
726
+ }
727
+
728
+ // Extract features for query
729
+ const query = args.join(' ');
730
+ const features = extractAllFeatures(query);
731
+ const featureObj = featuresToObject(features);
732
+
733
+ console.log(`\nQuery: "${query}"\n`);
734
+ console.log('Features (29):');
735
+
736
+ // Group features
737
+ console.log('\n === Base Features (0-14) ===');
738
+ for (let i = 0; i < 15; i++) {
739
+ const name = getAllFeatureNames()[i];
740
+ console.log(` ${i.toString().padStart(2)}: ${name.padEnd(28)} = ${features[i].toFixed(4)}`);
741
+ }
742
+
743
+ console.log('\n === Structural Features (15-19) ===');
744
+ for (let i = 15; i < 20; i++) {
745
+ const name = getAllFeatureNames()[i];
746
+ console.log(` ${i.toString().padStart(2)}: ${name.padEnd(28)} = ${features[i].toFixed(4)}`);
747
+ }
748
+
749
+ console.log('\n === Semantic Features (20-24) ===');
750
+ for (let i = 20; i < 25; i++) {
751
+ const name = getAllFeatureNames()[i];
752
+ console.log(` ${i.toString().padStart(2)}: ${name.padEnd(28)} = ${features[i].toFixed(4)}`);
753
+ }
754
+
755
+ console.log('\n === Multilingual Features (25-28) ===');
756
+ for (let i = 25; i < 29; i++) {
757
+ const name = getAllFeatureNames()[i];
758
+ console.log(` ${i.toString().padStart(2)}: ${name.padEnd(28)} = ${features[i].toFixed(4)}`);
759
+ }
760
+ }
761
+
762
+ export default { extractFeatures, extractAllFeatures, getFeatureNames, getAllFeatureNames };