@twelvehart/supermemory-runtime 1.0.0-next.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/.env.example +57 -0
- package/README.md +374 -0
- package/dist/index.js +189 -0
- package/dist/mcp/index.js +1132 -0
- package/docker-compose.prod.yml +91 -0
- package/docker-compose.yml +358 -0
- package/drizzle/0000_dapper_the_professor.sql +159 -0
- package/drizzle/0001_api_keys.sql +51 -0
- package/drizzle/meta/0000_snapshot.json +1532 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +20 -0
- package/package.json +114 -0
- package/scripts/add-extraction-job.ts +122 -0
- package/scripts/benchmark-pgvector.ts +122 -0
- package/scripts/bootstrap.sh +209 -0
- package/scripts/check-runtime-pack.ts +111 -0
- package/scripts/claude-mcp-config.ts +336 -0
- package/scripts/docker-entrypoint.sh +183 -0
- package/scripts/doctor.ts +377 -0
- package/scripts/init-db.sql +33 -0
- package/scripts/install.sh +1110 -0
- package/scripts/mcp-setup.ts +271 -0
- package/scripts/migrations/001_create_pgvector_extension.sql +31 -0
- package/scripts/migrations/002_create_memory_embeddings_table.sql +75 -0
- package/scripts/migrations/003_create_hnsw_index.sql +94 -0
- package/scripts/migrations/004_create_memory_embeddings_standalone.sql +70 -0
- package/scripts/migrations/005_create_chunks_table.sql +95 -0
- package/scripts/migrations/006_create_processing_queue.sql +45 -0
- package/scripts/migrations/generate_test_data.sql +42 -0
- package/scripts/migrations/phase1_comprehensive_test.sql +204 -0
- package/scripts/migrations/run_migrations.sh +286 -0
- package/scripts/migrations/test_hnsw_index.sql +255 -0
- package/scripts/pre-commit-secrets +282 -0
- package/scripts/run-extraction-worker.ts +46 -0
- package/scripts/run-phase1-tests.sh +291 -0
- package/scripts/setup.ts +222 -0
- package/scripts/smoke-install.sh +12 -0
- package/scripts/test-health-endpoint.sh +328 -0
- package/src/api/index.ts +2 -0
- package/src/api/middleware/auth.ts +80 -0
- package/src/api/middleware/csrf.ts +308 -0
- package/src/api/middleware/errorHandler.ts +166 -0
- package/src/api/middleware/rateLimit.ts +360 -0
- package/src/api/middleware/validation.ts +514 -0
- package/src/api/routes/documents.ts +286 -0
- package/src/api/routes/profiles.ts +237 -0
- package/src/api/routes/search.ts +71 -0
- package/src/api/stores/index.ts +58 -0
- package/src/config/bootstrap-env.ts +3 -0
- package/src/config/env.ts +71 -0
- package/src/config/feature-flags.ts +25 -0
- package/src/config/index.ts +140 -0
- package/src/config/secrets.config.ts +291 -0
- package/src/db/client.ts +92 -0
- package/src/db/index.ts +73 -0
- package/src/db/postgres.ts +72 -0
- package/src/db/schema/chunks.schema.ts +31 -0
- package/src/db/schema/containers.schema.ts +46 -0
- package/src/db/schema/documents.schema.ts +49 -0
- package/src/db/schema/embeddings.schema.ts +32 -0
- package/src/db/schema/index.ts +11 -0
- package/src/db/schema/memories.schema.ts +72 -0
- package/src/db/schema/profiles.schema.ts +34 -0
- package/src/db/schema/queue.schema.ts +59 -0
- package/src/db/schema/relationships.schema.ts +42 -0
- package/src/db/schema.ts +223 -0
- package/src/db/worker-connection.ts +47 -0
- package/src/index.ts +235 -0
- package/src/mcp/CLAUDE.md +1 -0
- package/src/mcp/index.ts +1380 -0
- package/src/mcp/legacyState.ts +22 -0
- package/src/mcp/rateLimit.ts +358 -0
- package/src/mcp/resources.ts +309 -0
- package/src/mcp/results.ts +104 -0
- package/src/mcp/tools.ts +401 -0
- package/src/queues/config.ts +119 -0
- package/src/queues/index.ts +289 -0
- package/src/sdk/client.ts +225 -0
- package/src/sdk/errors.ts +266 -0
- package/src/sdk/http.ts +560 -0
- package/src/sdk/index.ts +244 -0
- package/src/sdk/resources/base.ts +65 -0
- package/src/sdk/resources/connections.ts +204 -0
- package/src/sdk/resources/documents.ts +163 -0
- package/src/sdk/resources/index.ts +10 -0
- package/src/sdk/resources/memories.ts +150 -0
- package/src/sdk/resources/search.ts +60 -0
- package/src/sdk/resources/settings.ts +36 -0
- package/src/sdk/types.ts +674 -0
- package/src/services/chunking/index.ts +451 -0
- package/src/services/chunking.service.ts +650 -0
- package/src/services/csrf.service.ts +252 -0
- package/src/services/documents.repository.ts +219 -0
- package/src/services/documents.service.ts +191 -0
- package/src/services/embedding.service.ts +404 -0
- package/src/services/extraction.service.ts +300 -0
- package/src/services/extractors/code.extractor.ts +451 -0
- package/src/services/extractors/index.ts +9 -0
- package/src/services/extractors/markdown.extractor.ts +461 -0
- package/src/services/extractors/pdf.extractor.ts +315 -0
- package/src/services/extractors/text.extractor.ts +118 -0
- package/src/services/extractors/url.extractor.ts +243 -0
- package/src/services/index.ts +235 -0
- package/src/services/ingestion.service.ts +177 -0
- package/src/services/llm/anthropic.ts +400 -0
- package/src/services/llm/base.ts +460 -0
- package/src/services/llm/contradiction-detector.service.ts +526 -0
- package/src/services/llm/heuristics.ts +148 -0
- package/src/services/llm/index.ts +309 -0
- package/src/services/llm/memory-classifier.service.ts +383 -0
- package/src/services/llm/memory-extension-detector.service.ts +523 -0
- package/src/services/llm/mock.ts +470 -0
- package/src/services/llm/openai.ts +398 -0
- package/src/services/llm/prompts.ts +438 -0
- package/src/services/llm/types.ts +373 -0
- package/src/services/memory.repository.ts +1769 -0
- package/src/services/memory.service.ts +1338 -0
- package/src/services/memory.types.ts +234 -0
- package/src/services/persistence/index.ts +295 -0
- package/src/services/pipeline.service.ts +509 -0
- package/src/services/profile.repository.ts +436 -0
- package/src/services/profile.service.ts +560 -0
- package/src/services/profile.types.ts +270 -0
- package/src/services/relationships/detector.ts +1128 -0
- package/src/services/relationships/index.ts +268 -0
- package/src/services/relationships/memory-integration.ts +459 -0
- package/src/services/relationships/strategies.ts +132 -0
- package/src/services/relationships/types.ts +370 -0
- package/src/services/search.service.ts +761 -0
- package/src/services/search.types.ts +220 -0
- package/src/services/secrets.service.ts +384 -0
- package/src/services/vectorstore/base.ts +327 -0
- package/src/services/vectorstore/index.ts +444 -0
- package/src/services/vectorstore/memory.ts +286 -0
- package/src/services/vectorstore/migration.ts +295 -0
- package/src/services/vectorstore/mock.ts +403 -0
- package/src/services/vectorstore/pgvector.ts +695 -0
- package/src/services/vectorstore/types.ts +247 -0
- package/src/startup.ts +389 -0
- package/src/types/api.types.ts +193 -0
- package/src/types/document.types.ts +103 -0
- package/src/types/index.ts +241 -0
- package/src/types/profile.base.ts +133 -0
- package/src/utils/errors.ts +447 -0
- package/src/utils/id.ts +15 -0
- package/src/utils/index.ts +101 -0
- package/src/utils/logger.ts +313 -0
- package/src/utils/sanitization.ts +501 -0
- package/src/utils/secret-validation.ts +273 -0
- package/src/utils/synonyms.ts +188 -0
- package/src/utils/validation.ts +581 -0
- package/src/workers/chunking.worker.ts +242 -0
- package/src/workers/embedding.worker.ts +358 -0
- package/src/workers/extraction.worker.ts +346 -0
- package/src/workers/indexing.worker.ts +505 -0
- package/tsconfig.json +38 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides validation functions for various secret formats and types.
|
|
5
|
+
* Used during startup and secret rotation to ensure secret quality.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { randomBytes } from 'crypto'
|
|
9
|
+
import { ValidationError } from './errors.js'
|
|
10
|
+
|
|
11
|
+
/** API key format patterns for common providers */
|
|
12
|
+
const API_KEY_PATTERNS = {
|
|
13
|
+
generic: /^[a-zA-Z0-9_-]{20,64}$/,
|
|
14
|
+
anthropic: /^sk-ant-[a-zA-Z0-9-_]{95,}$/,
|
|
15
|
+
openai: /^sk-[a-zA-Z0-9]{48,}$/,
|
|
16
|
+
stripe: /^sk_(live|test)_[a-zA-Z0-9]{24,}$/,
|
|
17
|
+
aws: /^AKIA[0-9A-Z]{16}$/,
|
|
18
|
+
google: /^AIza[0-9A-Za-z_-]{35}$/,
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
/** Database URL patterns */
|
|
22
|
+
const DATABASE_URL_PATTERNS = {
|
|
23
|
+
postgresql: /^postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)$/,
|
|
24
|
+
mysql: /^mysql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)$/,
|
|
25
|
+
mongodb: /^mongodb(?:\+srv)?:\/\/([^:]+):([^@]+)@([^/]+)\/(.+)$/,
|
|
26
|
+
} as const
|
|
27
|
+
|
|
28
|
+
const JWT_PATTERN = /^eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$/
|
|
29
|
+
|
|
30
|
+
export interface ApiKeyValidation {
|
|
31
|
+
valid: boolean
|
|
32
|
+
format?: keyof typeof API_KEY_PATTERNS
|
|
33
|
+
error?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DatabaseUrlComponents {
|
|
37
|
+
type: 'postgresql' | 'mysql' | 'mongodb'
|
|
38
|
+
username: string
|
|
39
|
+
password: string
|
|
40
|
+
host: string
|
|
41
|
+
port: number
|
|
42
|
+
database: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SecretStrength {
|
|
46
|
+
entropy: number
|
|
47
|
+
strength: 'weak' | 'fair' | 'good' | 'strong'
|
|
48
|
+
diversity: {
|
|
49
|
+
hasLowercase: boolean
|
|
50
|
+
hasUppercase: boolean
|
|
51
|
+
hasNumbers: boolean
|
|
52
|
+
hasSymbols: boolean
|
|
53
|
+
uniqueChars: number
|
|
54
|
+
}
|
|
55
|
+
recommendations: string[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate API key format
|
|
60
|
+
* @param apiKey - API key to validate
|
|
61
|
+
* @param expectedFormat - Optional expected format
|
|
62
|
+
* @returns Validation result
|
|
63
|
+
*/
|
|
64
|
+
export function validateApiKey(apiKey: string, expectedFormat?: keyof typeof API_KEY_PATTERNS): ApiKeyValidation {
|
|
65
|
+
if (!apiKey || apiKey.trim().length === 0) {
|
|
66
|
+
return { valid: false, error: 'API key cannot be empty' }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If specific format expected, check only that format
|
|
70
|
+
if (expectedFormat) {
|
|
71
|
+
const pattern = API_KEY_PATTERNS[expectedFormat]
|
|
72
|
+
if (pattern.test(apiKey)) {
|
|
73
|
+
return { valid: true, format: expectedFormat }
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
valid: false,
|
|
77
|
+
error: `API key does not match ${expectedFormat} format`,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check all known formats
|
|
82
|
+
for (const [format, pattern] of Object.entries(API_KEY_PATTERNS)) {
|
|
83
|
+
if (pattern.test(apiKey)) {
|
|
84
|
+
return {
|
|
85
|
+
valid: true,
|
|
86
|
+
format: format as keyof typeof API_KEY_PATTERNS,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Not matching any known format
|
|
92
|
+
return {
|
|
93
|
+
valid: false,
|
|
94
|
+
error: 'API key format not recognized',
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Validate and parse database URL
|
|
100
|
+
* @param url - Database connection URL
|
|
101
|
+
* @returns Parsed components
|
|
102
|
+
* @throws ValidationError if URL is invalid
|
|
103
|
+
*/
|
|
104
|
+
export function validateDatabaseUrl(url: string): DatabaseUrlComponents {
|
|
105
|
+
if (!url || url.trim().length === 0) {
|
|
106
|
+
throw new ValidationError('Database URL cannot be empty')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const parsers: Array<{
|
|
110
|
+
type: DatabaseUrlComponents['type']
|
|
111
|
+
pattern: RegExp
|
|
112
|
+
defaultPort?: number
|
|
113
|
+
}> = [
|
|
114
|
+
{ type: 'postgresql', pattern: DATABASE_URL_PATTERNS.postgresql },
|
|
115
|
+
{ type: 'mysql', pattern: DATABASE_URL_PATTERNS.mysql },
|
|
116
|
+
{ type: 'mongodb', pattern: DATABASE_URL_PATTERNS.mongodb, defaultPort: 27017 },
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
for (const { type, pattern, defaultPort } of parsers) {
|
|
120
|
+
const match = url.match(pattern)
|
|
121
|
+
if (match) {
|
|
122
|
+
return {
|
|
123
|
+
type,
|
|
124
|
+
username: decodeURIComponent(match[1]!),
|
|
125
|
+
password: decodeURIComponent(match[2]!),
|
|
126
|
+
host: match[3]!,
|
|
127
|
+
port: defaultPort ?? parseInt(match[4]!, 10),
|
|
128
|
+
database: type === 'mongodb' ? match[4]! : match[5]!,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
throw new ValidationError('Invalid database URL format', {
|
|
134
|
+
url: ['URL must be in format: protocol://username:password@host:port/database'],
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check secret strength based on entropy and character diversity
|
|
140
|
+
* @param secret - Secret to check
|
|
141
|
+
* @returns Strength analysis
|
|
142
|
+
*/
|
|
143
|
+
export function checkSecretStrength(secret: string): SecretStrength {
|
|
144
|
+
const entropy = calculateEntropy(secret)
|
|
145
|
+
const diversity = analyzeCharacterDiversity(secret)
|
|
146
|
+
const recommendations: string[] = []
|
|
147
|
+
|
|
148
|
+
let strength: SecretStrength['strength']
|
|
149
|
+
if (entropy < 64) {
|
|
150
|
+
strength = 'weak'
|
|
151
|
+
recommendations.push('Increase length to at least 16 characters')
|
|
152
|
+
} else if (entropy < 96) {
|
|
153
|
+
strength = 'fair'
|
|
154
|
+
recommendations.push('Consider using at least 24 characters for better security')
|
|
155
|
+
} else if (entropy < 128) {
|
|
156
|
+
strength = 'good'
|
|
157
|
+
} else {
|
|
158
|
+
strength = 'strong'
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!diversity.hasLowercase) recommendations.push('Add lowercase letters')
|
|
162
|
+
if (!diversity.hasUppercase) recommendations.push('Add uppercase letters')
|
|
163
|
+
if (!diversity.hasNumbers) recommendations.push('Add numbers')
|
|
164
|
+
if (!diversity.hasSymbols) recommendations.push('Add symbols for maximum security')
|
|
165
|
+
if (diversity.uniqueChars < secret.length * 0.5) {
|
|
166
|
+
recommendations.push('Increase character diversity (too many repeated characters)')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { entropy, strength, diversity, recommendations }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Generate a cryptographically secure secret
|
|
174
|
+
* @param length - Length in bytes (default: 32)
|
|
175
|
+
* @param encoding - Output encoding (default: base64url)
|
|
176
|
+
* @returns Generated secret
|
|
177
|
+
*/
|
|
178
|
+
export function generateSecret(length: number = 32, encoding: 'hex' | 'base64' | 'base64url' = 'base64url'): string {
|
|
179
|
+
if (length < 16) {
|
|
180
|
+
throw new ValidationError('Secret length must be at least 16 bytes')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return randomBytes(length).toString(encoding)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validate JWT token format
|
|
188
|
+
* @param token - Token to validate
|
|
189
|
+
* @returns True if valid JWT format
|
|
190
|
+
*/
|
|
191
|
+
export function validateJwtFormat(token: string): boolean {
|
|
192
|
+
return JWT_PATTERN.test(token)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Sanitize database URL for logging (hide credentials)
|
|
197
|
+
* @param url - Database URL
|
|
198
|
+
* @returns Sanitized URL with hidden credentials
|
|
199
|
+
*/
|
|
200
|
+
export function sanitizeDatabaseUrl(url: string): string {
|
|
201
|
+
try {
|
|
202
|
+
const parsed = validateDatabaseUrl(url)
|
|
203
|
+
return `${parsed.type}://[REDACTED]:[REDACTED]@${parsed.host}:${parsed.port}/${parsed.database}`
|
|
204
|
+
} catch {
|
|
205
|
+
return '[INVALID_DATABASE_URL]'
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Check if a string appears to be a secret (high entropy, base64-like)
|
|
211
|
+
* @param value - String to check
|
|
212
|
+
* @returns True if likely a secret
|
|
213
|
+
*/
|
|
214
|
+
export function looksLikeSecret(value: string): boolean {
|
|
215
|
+
// Must be at least 16 chars
|
|
216
|
+
if (value.length < 16) {
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check entropy threshold
|
|
221
|
+
const entropy = calculateEntropy(value)
|
|
222
|
+
if (entropy < 64) {
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check if it's base64-like (alphanumeric + special chars)
|
|
227
|
+
const base64Like = /^[a-zA-Z0-9+/=_-]+$/
|
|
228
|
+
if (!base64Like.test(value)) {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return true
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Calculate Shannon entropy of a string
|
|
237
|
+
* @param str - Input string
|
|
238
|
+
* @returns Entropy in bits
|
|
239
|
+
*/
|
|
240
|
+
export function calculateEntropy(str: string): number {
|
|
241
|
+
const len = str.length
|
|
242
|
+
if (len === 0) return 0
|
|
243
|
+
|
|
244
|
+
const frequencies = new Map<string, number>()
|
|
245
|
+
for (const char of str) {
|
|
246
|
+
frequencies.set(char, (frequencies.get(char) || 0) + 1)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let entropy = 0
|
|
250
|
+
for (const count of frequencies.values()) {
|
|
251
|
+
const p = count / len
|
|
252
|
+
entropy -= p * Math.log2(p)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return entropy * len
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function analyzeCharacterDiversity(str: string) {
|
|
259
|
+
return {
|
|
260
|
+
hasLowercase: /[a-z]/.test(str),
|
|
261
|
+
hasUppercase: /[A-Z]/.test(str),
|
|
262
|
+
hasNumbers: /[0-9]/.test(str),
|
|
263
|
+
hasSymbols: /[^a-zA-Z0-9]/.test(str),
|
|
264
|
+
uniqueChars: new Set(str).size,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** Export secret patterns for use in secret detection */
|
|
269
|
+
export const SECRET_FORMAT_PATTERNS = {
|
|
270
|
+
apiKey: API_KEY_PATTERNS,
|
|
271
|
+
databaseUrl: DATABASE_URL_PATTERNS,
|
|
272
|
+
jwt: JWT_PATTERN,
|
|
273
|
+
} as const
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Synonym and Query Expansion Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides centralized synonym mappings and abbreviation expansions
|
|
5
|
+
* for use across search and profile services.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Common action/verb synonyms for query expansion.
|
|
10
|
+
* Keys are the base term, values are arrays of synonyms.
|
|
11
|
+
*/
|
|
12
|
+
export const ACTION_SYNONYMS: Readonly<Record<string, readonly string[]>> = {
|
|
13
|
+
create: ['make', 'build', 'generate', 'construct', 'establish'],
|
|
14
|
+
delete: ['remove', 'destroy', 'erase', 'eliminate', 'clear'],
|
|
15
|
+
update: ['modify', 'change', 'edit', 'revise', 'alter'],
|
|
16
|
+
search: ['find', 'look', 'query', 'seek', 'locate'],
|
|
17
|
+
get: ['retrieve', 'fetch', 'obtain', 'acquire', 'access'],
|
|
18
|
+
list: ['show', 'display', 'enumerate', 'view', 'browse'],
|
|
19
|
+
error: ['bug', 'issue', 'problem', 'fault', 'defect'],
|
|
20
|
+
fix: ['solve', 'resolve', 'repair', 'correct', 'patch'],
|
|
21
|
+
add: ['insert', 'append', 'include', 'attach', 'incorporate'],
|
|
22
|
+
start: ['begin', 'launch', 'initiate', 'commence', 'activate'],
|
|
23
|
+
stop: ['end', 'halt', 'terminate', 'cease', 'pause'],
|
|
24
|
+
send: ['transmit', 'dispatch', 'deliver', 'forward', 'submit'],
|
|
25
|
+
} as const
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Common technical abbreviations with their full forms.
|
|
29
|
+
* Used for expanding abbreviated terms in queries.
|
|
30
|
+
*/
|
|
31
|
+
export const ABBREVIATION_EXPANSIONS: Readonly<Record<string, string>> = {
|
|
32
|
+
api: 'application programming interface',
|
|
33
|
+
db: 'database',
|
|
34
|
+
auth: 'authentication',
|
|
35
|
+
config: 'configuration',
|
|
36
|
+
env: 'environment',
|
|
37
|
+
var: 'variable',
|
|
38
|
+
func: 'function',
|
|
39
|
+
impl: 'implementation',
|
|
40
|
+
repo: 'repository',
|
|
41
|
+
deps: 'dependencies',
|
|
42
|
+
pkg: 'package',
|
|
43
|
+
src: 'source',
|
|
44
|
+
lib: 'library',
|
|
45
|
+
util: 'utility',
|
|
46
|
+
req: 'request',
|
|
47
|
+
res: 'response',
|
|
48
|
+
msg: 'message',
|
|
49
|
+
err: 'error',
|
|
50
|
+
doc: 'document',
|
|
51
|
+
docs: 'documentation',
|
|
52
|
+
dev: 'development',
|
|
53
|
+
prod: 'production',
|
|
54
|
+
ui: 'user interface',
|
|
55
|
+
ux: 'user experience',
|
|
56
|
+
} as const
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Options for query expansion
|
|
60
|
+
*/
|
|
61
|
+
export interface QueryExpansionOptions {
|
|
62
|
+
/** Include synonym expansions (default: true) */
|
|
63
|
+
includeSynonyms?: boolean
|
|
64
|
+
/** Expand abbreviations (default: true) */
|
|
65
|
+
expandAbbreviations?: boolean
|
|
66
|
+
/** Maximum synonyms to include per term (default: 2) */
|
|
67
|
+
maxSynonymsPerTerm?: number
|
|
68
|
+
/** Custom synonym mappings to merge with defaults */
|
|
69
|
+
customSynonyms?: Record<string, string[]>
|
|
70
|
+
/** Custom abbreviation expansions to merge with defaults */
|
|
71
|
+
customAbbreviations?: Record<string, string>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get synonyms for a given term.
|
|
76
|
+
*
|
|
77
|
+
* @param term - The term to find synonyms for (case-insensitive)
|
|
78
|
+
* @param limit - Maximum number of synonyms to return (default: all)
|
|
79
|
+
* @param customSynonyms - Additional synonyms to check
|
|
80
|
+
* @returns Array of synonyms, empty if none found
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* getSynonyms('create'); // ['make', 'build', 'generate', ...]
|
|
85
|
+
* getSynonyms('create', 2); // ['make', 'build']
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function getSynonyms(term: string, limit?: number, customSynonyms?: Record<string, string[]>): string[] {
|
|
89
|
+
const lowerTerm = term.toLowerCase()
|
|
90
|
+
const allSynonyms = { ...ACTION_SYNONYMS, ...customSynonyms }
|
|
91
|
+
const synonyms = allSynonyms[lowerTerm]
|
|
92
|
+
|
|
93
|
+
if (!synonyms) {
|
|
94
|
+
return []
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return limit !== undefined ? [...synonyms].slice(0, limit) : [...synonyms]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Expand an abbreviation to its full form.
|
|
102
|
+
*
|
|
103
|
+
* @param abbreviation - The abbreviation to expand (case-insensitive)
|
|
104
|
+
* @param customAbbreviations - Additional abbreviations to check
|
|
105
|
+
* @returns The expanded form, or undefined if not found
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* expandAbbreviation('api'); // 'application programming interface'
|
|
110
|
+
* expandAbbreviation('unknown'); // undefined
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function expandAbbreviation(
|
|
114
|
+
abbreviation: string,
|
|
115
|
+
customAbbreviations?: Record<string, string>
|
|
116
|
+
): string | undefined {
|
|
117
|
+
const lowerAbbr = abbreviation.toLowerCase()
|
|
118
|
+
const allAbbreviations = { ...ABBREVIATION_EXPANSIONS, ...customAbbreviations }
|
|
119
|
+
return allAbbreviations[lowerAbbr]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Expand a query by adding synonyms and expanding abbreviations.
|
|
124
|
+
*
|
|
125
|
+
* @param query - The original query string
|
|
126
|
+
* @param options - Expansion options
|
|
127
|
+
* @returns Expanded query string with additional terms
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* expandQuery('create api'); // 'create api make build application programming interface'
|
|
132
|
+
* expandQuery('fix db error', { maxSynonymsPerTerm: 1 }); // 'fix db error solve database bug'
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function expandQuery(query: string, options: QueryExpansionOptions = {}): string {
|
|
136
|
+
const {
|
|
137
|
+
includeSynonyms = true,
|
|
138
|
+
expandAbbreviations = true,
|
|
139
|
+
maxSynonymsPerTerm = 2,
|
|
140
|
+
customSynonyms,
|
|
141
|
+
customAbbreviations,
|
|
142
|
+
} = options
|
|
143
|
+
|
|
144
|
+
const tokens = query
|
|
145
|
+
.toLowerCase()
|
|
146
|
+
.split(/\s+/)
|
|
147
|
+
.filter((t) => t.length > 0)
|
|
148
|
+
const expanded: string[] = [...tokens]
|
|
149
|
+
|
|
150
|
+
if (includeSynonyms) {
|
|
151
|
+
for (const token of tokens) {
|
|
152
|
+
const synonyms = getSynonyms(token, maxSynonymsPerTerm, customSynonyms)
|
|
153
|
+
expanded.push(...synonyms)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (expandAbbreviations) {
|
|
158
|
+
for (const token of tokens) {
|
|
159
|
+
const expansion = expandAbbreviation(token, customAbbreviations)
|
|
160
|
+
if (expansion) {
|
|
161
|
+
expanded.push(expansion)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Remove duplicates and return
|
|
167
|
+
return [...new Set(expanded)].join(' ')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if a term has known synonyms.
|
|
172
|
+
*
|
|
173
|
+
* @param term - The term to check
|
|
174
|
+
* @returns True if synonyms exist for this term
|
|
175
|
+
*/
|
|
176
|
+
export function hasSynonyms(term: string): boolean {
|
|
177
|
+
return term.toLowerCase() in ACTION_SYNONYMS
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check if a term is a known abbreviation.
|
|
182
|
+
*
|
|
183
|
+
* @param term - The term to check
|
|
184
|
+
* @returns True if this is a known abbreviation
|
|
185
|
+
*/
|
|
186
|
+
export function isAbbreviation(term: string): boolean {
|
|
187
|
+
return term.toLowerCase() in ABBREVIATION_EXPANSIONS
|
|
188
|
+
}
|