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.
- package/LICENSE +190 -0
- package/NOTICE +23 -0
- package/core/cli.js +51 -0
- package/core/config.js +27 -0
- package/core/embedding/embedding-cache.js +467 -0
- package/core/embedding/embedding-local-model.js +845 -0
- package/core/embedding/embedding-remote.js +492 -0
- package/core/embedding/embedding-service.js +712 -0
- package/core/embedding/embedding-telemetry.js +219 -0
- package/core/embedding/index.js +40 -0
- package/core/graph/community-detector.js +294 -0
- package/core/graph/graph-expansion.js +839 -0
- package/core/graph/graph-extractor.js +2304 -0
- package/core/graph/graph-search.js +2148 -0
- package/core/graph/hcgs-generator.js +666 -0
- package/core/graph/index.js +16 -0
- package/core/graph/leiden-algorithm.js +547 -0
- package/core/graph/relationship-resolver.js +366 -0
- package/core/graph/repo-map.js +408 -0
- package/core/graph/summary-manager.js +549 -0
- package/core/indexing/artifact-builder.js +1054 -0
- package/core/indexing/ast-chunker.js +709 -0
- package/core/indexing/chunking/chunk-builder.js +170 -0
- package/core/indexing/chunking/markdown-chunker.js +503 -0
- package/core/indexing/chunking/plaintext-chunker.js +104 -0
- package/core/indexing/dedup/dedup-phase.js +159 -0
- package/core/indexing/dedup/exemplar-selector.js +65 -0
- package/core/indexing/document-chunker.js +56 -0
- package/core/indexing/incremental-parser.js +390 -0
- package/core/indexing/incremental-tracker.js +761 -0
- package/core/indexing/index-codebase-v21.js +472 -0
- package/core/indexing/index-maintainer.mjs +1674 -0
- package/core/indexing/index.js +90 -0
- package/core/indexing/indexer-ann.js +1077 -0
- package/core/indexing/indexer-build.js +742 -0
- package/core/indexing/indexer-phases.js +800 -0
- package/core/indexing/indexer-pool.js +764 -0
- package/core/indexing/indexer-sparse-gram.js +98 -0
- package/core/indexing/indexer-utils.js +536 -0
- package/core/indexing/indexer-worker.js +148 -0
- package/core/indexing/li-skip-policy.js +225 -0
- package/core/indexing/merkle-tracker.js +244 -0
- package/core/indexing/model-pool.js +166 -0
- package/core/infrastructure/code-graph-repository.js +120 -0
- package/core/infrastructure/codebase-repository.js +131 -0
- package/core/infrastructure/config/dedup.js +54 -0
- package/core/infrastructure/config/embedding.js +298 -0
- package/core/infrastructure/config/graph.js +80 -0
- package/core/infrastructure/config/index.js +82 -0
- package/core/infrastructure/config/indexing.js +8 -0
- package/core/infrastructure/config/platform.js +254 -0
- package/core/infrastructure/config/ranking.js +221 -0
- package/core/infrastructure/config/search.js +396 -0
- package/core/infrastructure/config/translation.js +89 -0
- package/core/infrastructure/config/vector-store.js +114 -0
- package/core/infrastructure/constants.js +86 -0
- package/core/infrastructure/coreml-cascade.js +909 -0
- package/core/infrastructure/coreml-cascade.json +46 -0
- package/core/infrastructure/coreml-provider.js +81 -0
- package/core/infrastructure/db-utils.js +69 -0
- package/core/infrastructure/dedup-hashing.js +83 -0
- package/core/infrastructure/hardware-capability.js +332 -0
- package/core/infrastructure/index.js +104 -0
- package/core/infrastructure/language-patterns/maps.js +121 -0
- package/core/infrastructure/language-patterns/registry-core.js +323 -0
- package/core/infrastructure/language-patterns/registry-data-query.js +155 -0
- package/core/infrastructure/language-patterns/registry-object-oriented.js +285 -0
- package/core/infrastructure/language-patterns/registry-tooling.js +240 -0
- package/core/infrastructure/language-patterns/registry-web-style.js +143 -0
- package/core/infrastructure/language-patterns/registry.js +19 -0
- package/core/infrastructure/language-patterns.js +141 -0
- package/core/infrastructure/llm-provider.js +733 -0
- package/core/infrastructure/manifest.json +46 -0
- package/core/infrastructure/maxsim.wasm +0 -0
- package/core/infrastructure/model-fetcher.js +423 -0
- package/core/infrastructure/model-registry.js +214 -0
- package/core/infrastructure/native-inference.js +587 -0
- package/core/infrastructure/native-resolver.js +187 -0
- package/core/infrastructure/native-sparse-gram.js +257 -0
- package/core/infrastructure/native-tokenizer.js +160 -0
- package/core/infrastructure/onnx-mutex.js +45 -0
- package/core/infrastructure/onnx-session-utils.js +261 -0
- package/core/infrastructure/ort-pipeline.js +111 -0
- package/core/infrastructure/project-detector.js +102 -0
- package/core/infrastructure/quantization.js +410 -0
- package/core/infrastructure/simd-distance.js +502 -0
- package/core/infrastructure/simd-distance.wasm +0 -0
- package/core/infrastructure/tree-sitter-provider.js +665 -0
- package/core/infrastructure/webgpu-maxsim.js +222 -0
- package/core/query/index.js +35 -0
- package/core/query/intent-detector.js +201 -0
- package/core/query/intent-router.js +156 -0
- package/core/query/query-router-catboost.js +222 -0
- package/core/query/query-router-ml.js +266 -0
- package/core/query/query-router.js +213 -0
- package/core/ranking/cascaded-scorer.js +379 -0
- package/core/ranking/flashrank.js +810 -0
- package/core/ranking/index.js +49 -0
- package/core/ranking/late-interaction-index.js +2383 -0
- package/core/ranking/late-interaction-model.js +812 -0
- package/core/ranking/local-reranker.js +374 -0
- package/core/ranking/mmr.js +379 -0
- package/core/ranking/quality-scorer.js +363 -0
- package/core/search/context-expander.js +1167 -0
- package/core/search/dedup/sibling-expander.js +327 -0
- package/core/search/index.js +16 -0
- package/core/search/search-boost.js +259 -0
- package/core/search/search-cli.js +544 -0
- package/core/search/search-format.js +282 -0
- package/core/search/search-fusion.js +327 -0
- package/core/search/search-hybrid.js +204 -0
- package/core/search/search-pattern-chunks.js +337 -0
- package/core/search/search-pattern-planner.js +439 -0
- package/core/search/search-pattern-prefilter.js +412 -0
- package/core/search/search-pattern-ripgrep.js +663 -0
- package/core/search/search-pattern.js +463 -0
- package/core/search/search-postprocess.js +452 -0
- package/core/search/search-semantic.js +706 -0
- package/core/search/search-server.js +554 -0
- package/core/search/session-daemon-prewarm.mjs +164 -0
- package/core/search/session-warmup.js +595 -0
- package/core/search/sweet-search.js +632 -0
- package/core/search/warmup-metrics.js +532 -0
- package/core/start-server.js +6 -0
- package/core/training/query-router/features/extractor.js +762 -0
- package/core/training/query-router/features/multilingual-patterns.js +431 -0
- package/core/training/query-router/features/text-segmenter.js +303 -0
- package/core/training/query-router/features/unicode-utils.js +383 -0
- package/core/training/query-router/output/v45_router_d4.js +11521 -0
- package/core/training/query-router/output/v46_router_d4.js +11498 -0
- package/core/vector-store/binary-heap.js +227 -0
- package/core/vector-store/binary-hnsw-index.js +1004 -0
- package/core/vector-store/float-vector-store.js +234 -0
- package/core/vector-store/hnsw-index.js +580 -0
- package/core/vector-store/index.js +39 -0
- package/core/vector-store/seismic-index.js +498 -0
- package/core/vocabulary/index.js +84 -0
- package/core/vocabulary/vocab-constants.js +20 -0
- package/core/vocabulary/vocab-miner-extractors.js +375 -0
- package/core/vocabulary/vocab-miner-nl.js +404 -0
- package/core/vocabulary/vocab-miner-utils.js +146 -0
- package/core/vocabulary/vocab-miner.js +574 -0
- package/core/vocabulary/vocab-prewarm-cli.js +110 -0
- package/core/vocabulary/vocab-ranker.js +492 -0
- package/core/vocabulary/vocab-warmer.js +523 -0
- package/core/vocabulary/vocab-warmup-orchestrator.js +425 -0
- package/core/vocabulary/vocabulary-utils.js +704 -0
- package/crates/wasm-router/pkg/package.json +13 -0
- package/crates/wasm-router/pkg/query_router_wasm.d.ts +36 -0
- package/crates/wasm-router/pkg/query_router_wasm.js +271 -0
- package/crates/wasm-router/pkg/query_router_wasm_bg.wasm +0 -0
- package/crates/wasm-router/pkg/query_router_wasm_bg.wasm.d.ts +19 -0
- package/mcp/config-gen.js +121 -0
- package/mcp/server.js +335 -0
- package/mcp/tool-handlers.js +476 -0
- package/package.json +131 -9
- package/scripts/benchmark-harness.js +794 -0
- package/scripts/init.js +1058 -0
- package/scripts/smoke-test.js +435 -0
- package/scripts/uninstall.js +478 -0
- 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 };
|