diff --git a/packages/cli/src/commands/recommend.ts b/packages/cli/src/commands/recommend.ts index 041ff3ae..7f37f854 100644 --- a/packages/cli/src/commands/recommend.ts +++ b/packages/cli/src/commands/recommend.ts @@ -52,6 +52,9 @@ export class RecommendCommand extends Command { ['Update skill index', '$0 recommend --update'], ['Search for skills by task', '$0 recommend --task "authentication"'], ['Search for skills (alias)', '$0 recommend --search "testing"'], + ['Hybrid search (vector + keyword)', '$0 recommend --search "auth" --hybrid'], + ['Hybrid search with query expansion', '$0 recommend --search "auth" --hybrid --expand'], + ['Build hybrid search index', '$0 recommend --build-index'], ], }); @@ -125,6 +128,26 @@ export class RecommendCommand extends Command { description: 'Show category path for each recommendation', }); + // Hybrid search mode + hybrid = Option.Boolean('--hybrid,-H', false, { + description: 'Use hybrid search (vector + keyword)', + }); + + // Query expansion + expand = Option.Boolean('--expand,-x', false, { + description: 'Enable query expansion (requires --hybrid)', + }); + + // Reranking + rerank = Option.Boolean('--rerank', false, { + description: 'Enable LLM reranking (requires --hybrid)', + }); + + // Build index + buildIndex = Option.Boolean('--build-index', false, { + description: 'Build/rebuild the hybrid search embedding index', + }); + async execute(): Promise { const targetPath = resolve(this.projectPath || process.cwd()); @@ -133,6 +156,16 @@ export class RecommendCommand extends Command { return await this.updateIndex(); } + // Handle hybrid index building + if (this.buildIndex) { + return await this.buildHybridIndex(); + } + + // Validate hybrid-dependent options + if ((this.expand || this.rerank) && !this.hybrid) { + warn('--expand and --rerank require --hybrid flag. These options will be ignored.'); + } + if (!this.quiet && !this.json) { header('Skill Recommendations'); } @@ -168,6 +201,9 @@ export class RecommendCommand extends Command { // Handle search mode (--search or --task) const searchQuery = this.search || this.task; if (searchQuery) { + if (this.hybrid) { + return await this.handleHybridSearch(engine, searchQuery); + } return this.handleSearch(engine, searchQuery); } @@ -446,6 +482,132 @@ export class RecommendCommand extends Command { console.log(colors.muted('More details: skillkit recommend --explain --verbose')); } + private async handleHybridSearch(engine: RecommendationEngine, query: string): Promise { + if (!this.quiet && !this.json) { + header(`Hybrid Search: "${query}"`); + } + + const s = !this.quiet && !this.json ? spinner() : null; + s?.start('Initializing hybrid search...'); + + try { + await engine.initHybridSearch(); + s?.message('Searching...'); + + const results = await engine.hybridSearch({ + query, + limit: this.limit ? parseInt(this.limit, 10) : 10, + hybrid: true, + enableExpansion: this.expand, + enableReranking: this.rerank, + filters: { + minScore: this.minScore ? parseInt(this.minScore, 10) : undefined, + }, + }); + + s?.stop(`Found ${results.length} results`); + + if (this.json) { + console.log(JSON.stringify(results, null, 2)); + return 0; + } + + if (results.length === 0) { + warn(`No skills found matching "${query}"`); + return 0; + } + + console.log(''); + console.log(colors.bold(`Hybrid search results for "${query}" (${results.length} found):`)); + if (this.expand && results[0]?.expandedTerms?.length) { + console.log(colors.muted(` Expanded: ${results[0].expandedTerms.join(', ')}`)); + } + console.log(''); + + for (const result of results) { + let relevanceColor: (text: string) => string; + if (result.relevance >= 70) { + relevanceColor = colors.success; + } else if (result.relevance >= 50) { + relevanceColor = colors.warning; + } else { + relevanceColor = colors.muted; + } + const relevanceBar = progressBar(result.relevance, 100, 10); + + console.log(` ${relevanceColor(`${result.relevance}%`)} ${colors.dim(relevanceBar)} ${colors.bold(result.skill.name)}`); + + if (result.skill.description) { + console.log(` ${colors.muted(truncate(result.skill.description, 70))}`); + } + + if (this.verbose) { + const scores: string[] = []; + if (typeof result.vectorSimilarity === 'number') { + scores.push(`vector: ${(result.vectorSimilarity * 100).toFixed(0)}%`); + } + if (typeof result.keywordScore === 'number') { + scores.push(`keyword: ${result.keywordScore.toFixed(0)}%`); + } + if (typeof result.rrfScore === 'number') { + scores.push(`rrf: ${result.rrfScore.toFixed(3)}`); + } + if (scores.length > 0) { + console.log(` ${colors.dim('Scores:')} ${scores.join(' | ')}`); + } + } + + if (result.matchedTerms.length > 0) { + console.log(` ${colors.dim('Matched:')} ${result.matchedTerms.join(', ')}`); + } + + console.log(''); + } + + return 0; + } catch (err) { + s?.stop(colors.error('Hybrid search failed')); + console.log(colors.muted(err instanceof Error ? err.message : String(err))); + console.log(colors.muted('Falling back to standard search...')); + return this.handleSearch(engine, query); + } + } + + private async buildHybridIndex(): Promise { + if (!this.quiet) { + header('Build Hybrid Search Index'); + } + + const index = this.loadIndex(); + if (!index || index.skills.length === 0) { + warn('No skill index found. Run --update first.'); + return 1; + } + + const s = spinner(); + s.start('Initializing...'); + + try { + const engine = new RecommendationEngine(); + engine.loadIndex(index); + + await engine.buildHybridIndex((progress) => { + const percentage = Math.round((progress.current / progress.total) * 100); + s.message(`${progress.phase}: ${progress.message || ''} (${percentage}%)`); + }); + + s.stop(colors.success(`${symbols.success} Built hybrid index for ${index.skills.length} skills`)); + console.log(colors.muted(' Index stored in: ~/.skillkit/search.db')); + console.log(colors.muted(' Use --hybrid flag for vector+keyword search\n')); + + return 0; + } catch (err) { + s.stop(colors.error('Failed to build hybrid index')); + console.log(colors.muted(err instanceof Error ? err.message : String(err))); + return 1; + } + } + private handleSearch(engine: RecommendationEngine, query: string): number { if (!this.quiet && !this.json) { header(`Search: "${query}"`); diff --git a/packages/core/package.json b/packages/core/package.json index be4fe8de..52db9161 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -37,6 +37,10 @@ "./learning": { "import": "./dist/learning/index.js", "types": "./dist/learning/index.d.ts" + }, + "./search": { + "import": "./dist/search/index.js", + "types": "./dist/search/index.d.ts" } }, "files": [ @@ -53,7 +57,13 @@ "yaml": "^2.6.1", "zod": "^3.24.1" }, + "optionalDependencies": { + "node-llama-cpp": "^3.15.0", + "better-sqlite3": "^12.0.0", + "sqlite-vec": "^0.1.6" + }, "devDependencies": { + "@types/better-sqlite3": "^7.6.11", "@types/node": "^22.10.5", "tsup": "^8.3.5", "typescript": "^5.7.2", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 398dc01e..b63034ea 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -101,3 +101,6 @@ export * from './connectors/index.js'; // Execution Flow (Step Tracking & Metrics - Phase 21) export * from './execution/index.js'; + +// Hybrid Search (QMD-Inspired Vector + Keyword Search) +export * from './search/index.js'; diff --git a/packages/core/src/recommend/engine.ts b/packages/core/src/recommend/engine.ts index 61b2a4be..8b7cd88d 100644 --- a/packages/core/src/recommend/engine.ts +++ b/packages/core/src/recommend/engine.ts @@ -11,8 +11,11 @@ import type { SearchOptions, SearchResult, FreshnessResult, + RecommendHybridSearchOptions, + RecommendHybridSearchResult, } from './types.js'; import { DEFAULT_SCORING_WEIGHTS, TAG_TO_TECH, getTechTags } from './types.js'; +import type { HybridSearchPipeline } from '../search/hybrid.js'; /** * Recommendation engine for matching skills to project profiles @@ -20,16 +23,39 @@ import { DEFAULT_SCORING_WEIGHTS, TAG_TO_TECH, getTechTags } from './types.js'; export class RecommendationEngine { private weights: ScoringWeights; private index: SkillIndex | null = null; + private hybridPipeline: HybridSearchPipeline | null = null; constructor(weights?: Partial) { this.weights = { ...DEFAULT_SCORING_WEIGHTS, ...weights }; } + /** + * Initialize hybrid search pipeline for vector + keyword search + */ + async initHybridSearch(): Promise { + const { createHybridSearchPipeline } = await import('../search/hybrid.js'); + this.hybridPipeline = createHybridSearchPipeline(); + if (this.index) { + this.hybridPipeline.loadSkillsIndex(this.index); + } + await this.hybridPipeline.initialize(); + } + + /** + * Check if hybrid search is available + */ + isHybridSearchAvailable(): boolean { + return this.hybridPipeline !== null && this.hybridPipeline.isInitialized(); + } + /** * Load skill index from cache or generate from local skills */ loadIndex(index: SkillIndex): void { this.index = index; + if (this.hybridPipeline) { + this.hybridPipeline.loadSkillsIndex(index); + } } /** @@ -603,6 +629,71 @@ export class RecommendationEngine { return { relevance, matchedTerms, snippet }; } + /** + * Hybrid search combining vector embeddings and keyword matching + */ + async hybridSearch(options: RecommendHybridSearchOptions): Promise { + const { query, limit = 10, hybrid = true, enableExpansion = false, enableReranking = false, filters } = options; + + if (!hybrid || !this.hybridPipeline) { + const basicResults = this.search({ query, limit, semantic: true, filters }); + return basicResults.map((r) => ({ + ...r, + hybridScore: r.relevance / 100, + })); + } + + const response = await this.hybridPipeline.search({ + query, + limit, + enableExpansion, + enableReranking, + }); + + let results = response.results.map((r) => ({ + skill: r.skill, + relevance: r.relevance, + matchedTerms: r.matchedTerms, + snippet: r.snippet, + hybridScore: r.hybridScore, + vectorSimilarity: r.vectorSimilarity, + keywordScore: r.keywordScore, + rrfScore: r.rrfScore, + expandedTerms: r.expandedTerms, + })); + + if (filters?.tags && filters.tags.length > 0) { + results = results.filter((r) => + r.skill.tags?.some((t) => filters.tags!.includes(t)) + ); + } + if (filters?.verified) { + results = results.filter((r) => r.skill.verified); + } + if (filters?.minScore) { + results = results.filter((r) => r.relevance >= filters.minScore!); + } + + return results.slice(0, limit); + } + + /** + * Build hybrid search index from skills + */ + async buildHybridIndex( + onProgress?: (progress: { phase: string; current: number; total: number; message?: string }) => void + ): Promise { + if (!this.index) { + throw new Error('No skill index loaded. Call loadIndex() first.'); + } + + if (!this.hybridPipeline) { + await this.initHybridSearch(); + } + + await this.hybridPipeline!.buildIndex(this.index.skills, onProgress); + } + /** * Check freshness of installed skills against project dependencies * diff --git a/packages/core/src/recommend/types.ts b/packages/core/src/recommend/types.ts index 5e4990b5..1d9299e1 100644 --- a/packages/core/src/recommend/types.ts +++ b/packages/core/src/recommend/types.ts @@ -326,3 +326,25 @@ export interface ReasoningRecommendationResult extends RecommendationResult { strategy: string; }; } + +/** + * Hybrid search options for RecommendationEngine + */ +export interface RecommendHybridSearchOptions extends SearchOptions { + hybrid?: boolean; + enableExpansion?: boolean; + enableReranking?: boolean; + semanticWeight?: number; + keywordWeight?: number; +} + +/** + * Hybrid search result with additional metadata for RecommendationEngine + */ +export interface RecommendHybridSearchResult extends SearchResult { + hybridScore?: number; + vectorSimilarity?: number; + keywordScore?: number; + rrfScore?: number; + expandedTerms?: string[]; +} diff --git a/packages/core/src/search/__tests__/embeddings.test.ts b/packages/core/src/search/__tests__/embeddings.test.ts new file mode 100644 index 00000000..8033937b --- /dev/null +++ b/packages/core/src/search/__tests__/embeddings.test.ts @@ -0,0 +1,217 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { EmbeddingService } from '../embeddings.js'; +import type { SkillSummary } from '../../recommend/types.js'; + +describe('EmbeddingService', () => { + describe('cosineSimilarity', () => { + let service: EmbeddingService; + + beforeEach(() => { + service = new EmbeddingService(); + }); + + it('should return 1 for identical vectors', () => { + const a = new Float32Array([1, 0, 0]); + const b = new Float32Array([1, 0, 0]); + + const similarity = service.cosineSimilarity(a, b); + expect(similarity).toBeCloseTo(1, 5); + }); + + it('should return 0 for orthogonal vectors', () => { + const a = new Float32Array([1, 0, 0]); + const b = new Float32Array([0, 1, 0]); + + const similarity = service.cosineSimilarity(a, b); + expect(similarity).toBeCloseTo(0, 5); + }); + + it('should return -1 for opposite vectors', () => { + const a = new Float32Array([1, 0, 0]); + const b = new Float32Array([-1, 0, 0]); + + const similarity = service.cosineSimilarity(a, b); + expect(similarity).toBeCloseTo(-1, 5); + }); + + it('should handle normalized vectors', () => { + const a = new Float32Array([0.5, 0.5, 0.5, 0.5]); + const b = new Float32Array([0.5, 0.5, 0.5, 0.5]); + + const similarity = service.cosineSimilarity(a, b); + expect(similarity).toBeCloseTo(1, 5); + }); + + it('should throw for mismatched dimensions', () => { + const a = new Float32Array([1, 0]); + const b = new Float32Array([1, 0, 0]); + + expect(() => service.cosineSimilarity(a, b)).toThrow('Vector dimensions mismatch'); + }); + + it('should return 0 for zero vectors', () => { + const a = new Float32Array([0, 0, 0]); + const b = new Float32Array([1, 0, 0]); + + const similarity = service.cosineSimilarity(a, b); + expect(similarity).toBe(0); + }); + }); + + describe('initialization state', () => { + it('should not be initialized by default', () => { + const service = new EmbeddingService(); + expect(service.isInitialized()).toBe(false); + }); + + it('should return default dimensions', () => { + const service = new EmbeddingService(); + expect(service.getDimensions()).toBe(768); + }); + }); + + describe('skill text building', () => { + it('should handle skill with all fields', async () => { + const service = new EmbeddingService(); + const skill: SkillSummary = { + name: 'test-skill', + description: 'A test skill', + tags: ['tag1', 'tag2'], + compatibility: { + frameworks: ['react'], + languages: ['typescript'], + libraries: ['lodash'], + }, + popularity: 100, + quality: 80, + verified: true, + }; + + const buildSkillText = (s: SkillSummary): string => { + const parts: string[] = [s.name]; + if (s.description) parts.push(s.description); + if (s.tags?.length) parts.push(s.tags.join(' ')); + if (s.compatibility?.frameworks?.length) parts.push(s.compatibility.frameworks.join(' ')); + if (s.compatibility?.languages?.length) parts.push(s.compatibility.languages.join(' ')); + if (s.compatibility?.libraries?.length) parts.push(s.compatibility.libraries.join(' ')); + return parts.join(' ').toLowerCase(); + }; + + const text = buildSkillText(skill); + + expect(text).toContain('test-skill'); + expect(text).toContain('a test skill'); + expect(text).toContain('tag1'); + expect(text).toContain('react'); + expect(text).toContain('typescript'); + expect(text).toContain('lodash'); + }); + + it('should handle skill with minimal fields', () => { + const skill: SkillSummary = { + name: 'minimal-skill', + tags: [], + popularity: 0, + quality: 50, + verified: false, + }; + + const buildSkillText = (s: SkillSummary): string => { + const parts: string[] = [s.name]; + if (s.description) parts.push(s.description); + if (s.tags?.length) parts.push(s.tags.join(' ')); + return parts.join(' ').toLowerCase(); + }; + + const text = buildSkillText(skill); + + expect(text).toBe('minimal-skill'); + }); + }); +}); + +describe('EmbeddingService chunking', () => { + it('should split text into chunks', () => { + const chunkText = ( + text: string, + config = { maxTokens: 100, overlapPercent: 15, minChunkSize: 10 } + ) => { + const lines = text.split('\n'); + const chunks: { content: string; startLine: number; endLine: number }[] = []; + const avgCharsPerToken = 4; + const maxCharsPerChunk = config.maxTokens * avgCharsPerToken; + const overlapChars = Math.floor(maxCharsPerChunk * (config.overlapPercent / 100)); + + let currentChunk: string[] = []; + let currentChars = 0; + let startLine = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const lineChars = line.length + 1; + + if (currentChars + lineChars > maxCharsPerChunk && currentChunk.length > 0) { + const content = currentChunk.join('\n'); + if (content.length >= config.minChunkSize) { + chunks.push({ content, startLine, endLine: i - 1 }); + } + + const overlapLines: string[] = []; + let overlapCharsCount = 0; + for (let j = currentChunk.length - 1; j >= 0 && overlapCharsCount < overlapChars; j--) { + overlapLines.unshift(currentChunk[j]); + overlapCharsCount += currentChunk[j].length + 1; + } + + currentChunk = overlapLines; + currentChars = overlapCharsCount; + startLine = i - overlapLines.length; + } + + currentChunk.push(line); + currentChars += lineChars; + } + + if (currentChunk.length > 0) { + const content = currentChunk.join('\n'); + if (content.length >= config.minChunkSize) { + chunks.push({ content, startLine, endLine: lines.length - 1 }); + } + } + + return chunks; + }; + + const longText = Array(50).fill('This is a line of text for testing.').join('\n'); + const chunks = chunkText(longText); + + expect(chunks.length).toBeGreaterThan(1); + chunks.forEach((chunk) => { + expect(chunk.content.length).toBeLessThanOrEqual(400); + expect(chunk.startLine).toBeGreaterThanOrEqual(0); + expect(chunk.endLine).toBeGreaterThanOrEqual(chunk.startLine); + }); + }); + + it('should not chunk short text', () => { + const chunkText = ( + text: string, + config = { maxTokens: 800, overlapPercent: 15, minChunkSize: 100 } + ) => { + const lines = text.split('\n'); + if (text.length < config.maxTokens * 4) { + if (text.length >= config.minChunkSize) { + return [{ content: text, startLine: 0, endLine: lines.length - 1 }]; + } + return []; + } + return [{ content: text, startLine: 0, endLine: lines.length - 1 }]; + }; + + const shortText = 'This is a short text that should not be chunked.'; + const chunks = chunkText(shortText, { maxTokens: 800, overlapPercent: 15, minChunkSize: 10 }); + + expect(chunks.length).toBe(1); + expect(chunks[0].content).toBe(shortText); + }); +}); diff --git a/packages/core/src/search/__tests__/expansion.test.ts b/packages/core/src/search/__tests__/expansion.test.ts new file mode 100644 index 00000000..378c6efc --- /dev/null +++ b/packages/core/src/search/__tests__/expansion.test.ts @@ -0,0 +1,100 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { QueryExpander, expandQuerySimple } from '../expansion.js'; + +describe('QueryExpander', () => { + let expander: QueryExpander; + + beforeEach(() => { + expander = new QueryExpander(); + }); + + afterEach(() => { + expander.clearCache(); + }); + + describe('expandQuerySimple (without LLM)', () => { + it('should expand "auth" to authentication-related terms', () => { + const result = expandQuerySimple('auth'); + + expect(result.original).toBe('auth'); + expect(result.variations.length).toBeGreaterThan(0); + expect(result.variations.some((v) => v.includes('authentication'))).toBe(true); + }); + + it('should expand "api" to related terms', () => { + const result = expandQuerySimple('api'); + + expect(result.original).toBe('api'); + expect(result.variations.some((v) => v.includes('rest') || v.includes('endpoint'))).toBe(true); + }); + + it('should expand "test" to testing-related terms', () => { + const result = expandQuerySimple('testing'); + + expect(result.original).toBe('testing'); + expect(result.variations.some((v) => v.includes('test') || v.includes('unit'))).toBe(true); + }); + + it('should handle queries without synonyms', () => { + const result = expandQuerySimple('xyz123unique'); + + expect(result.original).toBe('xyz123unique'); + expect(result.variations).toEqual([]); + expect(result.weights).toEqual([2.0]); + }); + + it('should give original query weight of 2.0', () => { + const result = expandQuerySimple('auth'); + + expect(result.weights[0]).toBe(2.0); + for (let i = 1; i < result.weights.length; i++) { + expect(result.weights[i]).toBe(1.0); + } + }); + + it('should limit variations to 3', () => { + const result = expandQuerySimple('auth'); + expect(result.variations.length).toBeLessThanOrEqual(3); + }); + }); + + describe('expand (fallback without LLM)', () => { + it('should return expanded query without LLM initialization', async () => { + const result = await expander.expand('auth'); + + expect(result.original).toBe('auth'); + expect(result.variations.length).toBeGreaterThan(0); + }); + + it('should cache results', async () => { + const result1 = await expander.expand('auth'); + const result2 = await expander.expand('auth'); + + expect(result1).toEqual(result2); + }); + + it('should handle case-insensitive caching', async () => { + const result1 = await expander.expand('Auth'); + const result2 = await expander.expand('AUTH'); + + expect(result1.original).toBe('Auth'); + expect(result2.original).toBe('AUTH'); + }); + }); + + describe('clearCache', () => { + it('should clear the expansion cache', async () => { + await expander.expand('auth'); + expander.clearCache(); + + const result = await expander.expand('auth'); + expect(result).toBeDefined(); + }); + }); + + describe('isInitialized', () => { + it('should return false when not initialized with LLM', () => { + expect(expander.isInitialized()).toBe(false); + }); + }); +}); diff --git a/packages/core/src/search/__tests__/hybrid.test.ts b/packages/core/src/search/__tests__/hybrid.test.ts new file mode 100644 index 00000000..1a3f4041 --- /dev/null +++ b/packages/core/src/search/__tests__/hybrid.test.ts @@ -0,0 +1,244 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import type { SkillSummary, SkillIndex } from '../../recommend/types.js'; + +const mockSkills: SkillSummary[] = [ + { + name: 'react-auth', + description: 'Authentication utilities for React applications', + tags: ['react', 'auth', 'authentication'], + compatibility: { + frameworks: ['react'], + languages: ['typescript', 'javascript'], + libraries: ['next-auth'], + }, + popularity: 500, + quality: 80, + verified: true, + }, + { + name: 'vue-forms', + description: 'Form validation library for Vue', + tags: ['vue', 'forms', 'validation'], + compatibility: { + frameworks: ['vue'], + languages: ['typescript', 'javascript'], + libraries: [], + }, + popularity: 300, + quality: 75, + verified: false, + }, + { + name: 'api-testing', + description: 'API testing utilities for REST endpoints', + tags: ['testing', 'api', 'rest'], + compatibility: { + frameworks: [], + languages: ['typescript'], + libraries: ['jest', 'supertest'], + }, + popularity: 400, + quality: 85, + verified: true, + }, + { + name: 'login-helper', + description: 'Helper functions for login and authentication flows', + tags: ['auth', 'login', 'oauth'], + compatibility: { + frameworks: ['react', 'vue'], + languages: ['typescript'], + libraries: [], + }, + popularity: 200, + quality: 70, + verified: false, + }, +]; + +const mockIndex: SkillIndex = { + version: 1, + lastUpdated: new Date().toISOString(), + skills: mockSkills, + sources: [], +}; + +vi.mock('better-sqlite3', () => { + return { + default: vi.fn().mockImplementation(() => { + throw new Error("Cannot find module 'better-sqlite3'"); + }), + }; +}); + +vi.mock('sqlite-vec', () => { + return { + load: vi.fn(), + }; +}); + +vi.mock('node-llama-cpp', () => { + return { + getLlama: vi.fn().mockImplementation(() => { + throw new Error("Cannot find package 'node-llama-cpp'"); + }), + }; +}); + +describe('HybridSearchPipeline', () => { + let HybridSearchPipeline: typeof import('../hybrid.js').HybridSearchPipeline; + let createHybridSearchPipeline: typeof import('../hybrid.js').createHybridSearchPipeline; + let pipeline: InstanceType; + + beforeEach(async () => { + const module = await import('../hybrid.js'); + HybridSearchPipeline = module.HybridSearchPipeline; + createHybridSearchPipeline = module.createHybridSearchPipeline; + pipeline = createHybridSearchPipeline(); + }); + + afterEach(async () => { + if (pipeline) { + await pipeline.dispose(); + } + }); + + describe('initialization', () => { + it('should create a pipeline', () => { + expect(pipeline).toBeInstanceOf(HybridSearchPipeline); + }); + + it('should not be initialized before calling initialize()', () => { + expect(pipeline.isInitialized()).toBe(false); + }); + + it('should be initialized after calling initialize()', async () => { + await pipeline.initialize(); + expect(pipeline.isInitialized()).toBe(true); + }); + + it('should load skills index', async () => { + pipeline.loadSkillsIndex(mockIndex); + const stats = pipeline.getStats(); + expect(stats.skillCount).toBe(mockSkills.length); + }); + }); + + describe('search (keyword-only fallback)', () => { + beforeEach(async () => { + pipeline.loadSkillsIndex(mockIndex); + await pipeline.initialize(); + }); + + it('should find skills matching query', async () => { + const response = await pipeline.search({ query: 'authentication' }); + + expect(response.results.length).toBeGreaterThan(0); + expect(response.results.some((r) => r.skill.name === 'react-auth')).toBe(true); + }); + + it('should find skills by tag', async () => { + const response = await pipeline.search({ query: 'react' }); + + expect(response.results.length).toBeGreaterThan(0); + expect(response.results.some((r) => r.skill.tags?.includes('react'))).toBe(true); + }); + + it('should respect limit parameter', async () => { + const response = await pipeline.search({ query: 'auth', limit: 2 }); + + expect(response.results.length).toBeLessThanOrEqual(2); + }); + + it('should include timing information', async () => { + const response = await pipeline.search({ query: 'test' }); + + expect(typeof response.timing.totalMs).toBe('number'); + expect(response.timing.totalMs).toBeGreaterThanOrEqual(0); + expect(response.timing.keywordSearchMs).toBeGreaterThanOrEqual(0); + }); + + it('should include stats', async () => { + const response = await pipeline.search({ query: 'auth' }); + + expect(response.stats.candidatesFromKeyword).toBeGreaterThan(0); + expect(response.stats.totalMerged).toBeGreaterThan(0); + }); + + it('should return original query in response', async () => { + const response = await pipeline.search({ query: 'authentication' }); + + expect(response.query.original).toBe('authentication'); + }); + }); + + describe('search with expansion', () => { + beforeEach(async () => { + pipeline.loadSkillsIndex(mockIndex); + await pipeline.initialize(); + }); + + it('should expand query and find more results', async () => { + const response = await pipeline.search({ + query: 'auth', + enableExpansion: true, + }); + + expect(response.results.length).toBeGreaterThan(0); + expect(response.query.expanded).toBeDefined(); + }); + + it('should include expanded terms in response', async () => { + const response = await pipeline.search({ + query: 'auth', + enableExpansion: true, + }); + + if (response.query.expanded) { + expect(response.query.expanded.original).toBe('auth'); + expect(response.query.expanded.weights[0]).toBe(2.0); + } + }); + }); + + describe('getStats', () => { + it('should return stats before initialization', () => { + const stats = pipeline.getStats(); + expect(stats.initialized).toBe(false); + expect(stats.skillCount).toBe(0); + }); + + it('should return updated stats after loading index', async () => { + pipeline.loadSkillsIndex(mockIndex); + await pipeline.initialize(); + + const stats = pipeline.getStats(); + expect(stats.initialized).toBe(true); + expect(stats.skillCount).toBe(mockSkills.length); + }); + }); +}); + +describe('hybridSearch helper', () => { + let hybridSearch: typeof import('../hybrid.js').hybridSearch; + + beforeEach(async () => { + const module = await import('../hybrid.js'); + hybridSearch = module.hybridSearch; + }); + + it('should perform search on skills array', async () => { + const response = await hybridSearch(mockSkills, 'react'); + + expect(response.results.length).toBeGreaterThan(0); + expect(response.query.original).toBe('react'); + }); + + it('should respect options', async () => { + const response = await hybridSearch(mockSkills, 'auth', { + limit: 1, + }); + + expect(response.results.length).toBe(1); + }); +}); diff --git a/packages/core/src/search/__tests__/rrf.test.ts b/packages/core/src/search/__tests__/rrf.test.ts new file mode 100644 index 00000000..152392d7 --- /dev/null +++ b/packages/core/src/search/__tests__/rrf.test.ts @@ -0,0 +1,276 @@ +import { describe, it, expect } from 'vitest'; +import { + computeRRFScore, + fuseWithRRF, + normalizeScores, + weightedCombine, + applyPositionAwareBlending, + getRankFromScore, + mergeRankings, + type RankerResult, + type RRFInput, +} from '../rrf.js'; + +describe('RRF (Reciprocal Rank Fusion)', () => { + describe('computeRRFScore', () => { + it('should compute RRF score for a single rank', () => { + const score = computeRRFScore([1], 60); + expect(score).toBeCloseTo(1 / 61, 5); + }); + + it('should sum RRF scores for multiple ranks', () => { + const score = computeRRFScore([1, 2, 3], 60); + const expected = 1 / 61 + 1 / 62 + 1 / 63; + expect(score).toBeCloseTo(expected, 5); + }); + + it('should use custom k value', () => { + const scoreK60 = computeRRFScore([1], 60); + const scoreK30 = computeRRFScore([1], 30); + expect(scoreK30).toBeGreaterThan(scoreK60); + }); + + it('should return 0 for empty ranks', () => { + const score = computeRRFScore([]); + expect(score).toBe(0); + }); + }); + + describe('fuseWithRRF', () => { + it('should fuse results from multiple rankers', () => { + const inputs: RRFInput[] = [ + { + source: 'vector', + results: [ + { skillName: 'skill-a', score: 0.9 }, + { skillName: 'skill-b', score: 0.8 }, + { skillName: 'skill-c', score: 0.7 }, + ], + }, + { + source: 'keyword', + results: [ + { skillName: 'skill-b', score: 90 }, + { skillName: 'skill-a', score: 80 }, + { skillName: 'skill-d', score: 70 }, + ], + }, + ]; + + const rankings = fuseWithRRF(inputs, 60); + + expect(rankings.length).toBe(4); + expect(rankings[0].skillName).toBe('skill-a'); + expect(rankings[1].skillName).toBe('skill-b'); + }); + + it('should include rank information from each source', () => { + const inputs: RRFInput[] = [ + { + source: 'vector', + results: [{ skillName: 'skill-a', score: 0.9 }], + }, + { + source: 'keyword', + results: [{ skillName: 'skill-a', score: 90 }], + }, + ]; + + const rankings = fuseWithRRF(inputs); + + expect(rankings[0].ranks).toHaveLength(2); + expect(rankings[0].ranks.map((r) => r.source)).toContain('vector'); + expect(rankings[0].ranks.map((r) => r.source)).toContain('keyword'); + }); + + it('should handle skills appearing in only one ranker', () => { + const inputs: RRFInput[] = [ + { + source: 'vector', + results: [{ skillName: 'skill-a', score: 0.9 }], + }, + { + source: 'keyword', + results: [{ skillName: 'skill-b', score: 90 }], + }, + ]; + + const rankings = fuseWithRRF(inputs); + + expect(rankings).toHaveLength(2); + const skillAInfo = rankings.find((r) => r.skillName === 'skill-a'); + const skillBInfo = rankings.find((r) => r.skillName === 'skill-b'); + expect(skillAInfo?.ranks).toHaveLength(1); + expect(skillBInfo?.ranks).toHaveLength(1); + }); + }); + + describe('normalizeScores', () => { + it('should normalize scores to 0-1 range', () => { + const results: RankerResult[] = [ + { skillName: 'a', score: 100 }, + { skillName: 'b', score: 50 }, + { skillName: 'c', score: 0 }, + ]; + + const normalized = normalizeScores(results); + + expect(normalized[0].score).toBe(1); + expect(normalized[1].score).toBe(0.5); + expect(normalized[2].score).toBe(0); + }); + + it('should handle all same scores', () => { + const results: RankerResult[] = [ + { skillName: 'a', score: 50 }, + { skillName: 'b', score: 50 }, + ]; + + const normalized = normalizeScores(results); + + expect(normalized[0].score).toBe(1); + expect(normalized[1].score).toBe(1); + }); + + it('should return empty array for empty input', () => { + const normalized = normalizeScores([]); + expect(normalized).toEqual([]); + }); + }); + + describe('weightedCombine', () => { + it('should combine results with weights', () => { + const inputs: RRFInput[] = [ + { + source: 'vector', + results: [ + { skillName: 'a', score: 100 }, + { skillName: 'b', score: 50 }, + ], + }, + { + source: 'keyword', + results: [ + { skillName: 'b', score: 100 }, + { skillName: 'a', score: 50 }, + ], + }, + ]; + + const combined = weightedCombine(inputs, { vector: 0.6, keyword: 0.4 }); + + expect(combined).toHaveLength(2); + const skillA = combined.find((r) => r.skillName === 'a')!; + const skillB = combined.find((r) => r.skillName === 'b')!; + expect(skillA.score).toBeCloseTo(0.6 * 1 + 0.4 * 0, 5); + expect(skillB.score).toBeCloseTo(0.6 * 0 + 0.4 * 1, 5); + }); + + it('should default to weight 1 for unknown sources', () => { + const inputs: RRFInput[] = [ + { + source: 'unknown', + results: [{ skillName: 'a', score: 100 }], + }, + ]; + + const combined = weightedCombine(inputs, {}); + + expect(combined[0].score).toBe(1); + }); + }); + + describe('applyPositionAwareBlending', () => { + it('should apply different weights based on position', () => { + const retrievalScores = new Map([ + ['a', 0.9], + ['b', 0.8], + ['c', 0.7], + ['d', 0.6], + ]); + + const rerankerScores = new Map([ + ['a', 0.5], + ['b', 0.6], + ['c', 0.7], + ['d', 0.8], + ]); + + const sortedSkills = ['a', 'b', 'c', 'd']; + + const blended = applyPositionAwareBlending( + retrievalScores, + rerankerScores, + sortedSkills + ); + + const scoreA = blended.get('a')!; + expect(scoreA).toBeCloseTo(0.9 * 0.75 + 0.5 * 0.25, 5); + + const scoreD = blended.get('d')!; + expect(scoreD).toBeCloseTo(0.6 * 0.6 + 0.8 * 0.4, 5); + }); + + it('should handle missing scores', () => { + const retrievalScores = new Map([['a', 0.9]]); + const rerankerScores = new Map(); + const sortedSkills = ['a']; + + const blended = applyPositionAwareBlending( + retrievalScores, + rerankerScores, + sortedSkills + ); + + expect(blended.get('a')).toBeCloseTo(0.9 * 0.75, 5); + }); + }); + + describe('getRankFromScore', () => { + it('should return correct rank for a skill', () => { + const results: RankerResult[] = [ + { skillName: 'a', score: 90 }, + { skillName: 'b', score: 80 }, + { skillName: 'c', score: 70 }, + ]; + + expect(getRankFromScore('a', results)).toBe(1); + expect(getRankFromScore('b', results)).toBe(2); + expect(getRankFromScore('c', results)).toBe(3); + }); + + it('should return length+1 for missing skill', () => { + const results: RankerResult[] = [ + { skillName: 'a', score: 90 }, + { skillName: 'b', score: 80 }, + ]; + + expect(getRankFromScore('missing', results)).toBe(3); + }); + }); + + describe('mergeRankings', () => { + it('should merge primary and secondary rankings with weights', () => { + const primary = [ + { skillName: 'a', rrfScore: 0.1, ranks: [{ source: 'primary', rank: 1 }] }, + { skillName: 'b', rrfScore: 0.05, ranks: [{ source: 'primary', rank: 2 }] }, + ]; + + const secondary = [ + { skillName: 'b', rrfScore: 0.1, ranks: [{ source: 'secondary', rank: 1 }] }, + { skillName: 'c', rrfScore: 0.05, ranks: [{ source: 'secondary', rank: 2 }] }, + ]; + + const merged = mergeRankings(primary, secondary, 0.7); + + expect(merged).toHaveLength(3); + const skillA = merged.find((r) => r.skillName === 'a')!; + const skillB = merged.find((r) => r.skillName === 'b')!; + const skillC = merged.find((r) => r.skillName === 'c')!; + + expect(skillA.rrfScore).toBeCloseTo(0.1 * 0.7, 5); + expect(skillB.rrfScore).toBeCloseTo(0.05 * 0.7 + 0.1 * 0.3, 5); + expect(skillC.rrfScore).toBeCloseTo(0.05 * 0.3, 5); + }); + }); +}); diff --git a/packages/core/src/search/embeddings.ts b/packages/core/src/search/embeddings.ts new file mode 100644 index 00000000..d7b0010a --- /dev/null +++ b/packages/core/src/search/embeddings.ts @@ -0,0 +1,294 @@ +import type { SkillSummary } from '../recommend/types.js'; +import type { + SkillEmbedding, + IndexBuildCallback, +} from './types.js'; +import { DEFAULT_CHUNKING_CONFIG, MODEL_REGISTRY } from './types.js'; +import { LocalModelManager } from './local-models.js'; + +export class EmbeddingService { + private modelManager: LocalModelManager; + private model: unknown = null; + private context: unknown = null; + private initialized = false; + private dimensions: number = 768; + + constructor(modelManager?: LocalModelManager) { + this.modelManager = modelManager ?? new LocalModelManager(); + } + + async initialize(onProgress?: IndexBuildCallback): Promise { + if (this.initialized) return; + + onProgress?.({ + phase: 'loading', + current: 0, + total: 1, + message: 'Loading embedding model...', + }); + + const modelPath = await this.modelManager.ensureEmbedModel(onProgress); + + try { + // @ts-ignore - node-llama-cpp is an optional dependency + const llama = await import('node-llama-cpp'); + const { getLlama } = llama; + + const llamaInstance = await getLlama(); + this.model = await llamaInstance.loadModel({ modelPath }); + this.context = await (this.model as { createEmbeddingContext: () => Promise }).createEmbeddingContext(); + + const modelInfo = MODEL_REGISTRY.embeddings[ + this.modelManager.getConfig().embedModel as keyof typeof MODEL_REGISTRY.embeddings + ]; + this.dimensions = modelInfo?.dimensions ?? 768; + + this.initialized = true; + + onProgress?.({ + phase: 'loading', + current: 1, + total: 1, + message: 'Embedding model loaded successfully', + }); + } catch (error) { + if ( + error instanceof Error && + error.message.includes("Cannot find package 'node-llama-cpp'") + ) { + throw new Error( + 'node-llama-cpp is not installed. Install it with: pnpm add node-llama-cpp' + ); + } + throw error; + } + } + + async embed(text: string): Promise { + if (!this.initialized || !this.context) { + throw new Error('EmbeddingService not initialized. Call initialize() first.'); + } + + const ctx = this.context as { + getEmbeddingFor(text: string): Promise<{ vector: number[] }>; + }; + const result = await ctx.getEmbeddingFor(text); + return new Float32Array(result.vector); + } + + async embedBatch(texts: string[]): Promise { + const results: Float32Array[] = []; + for (const text of texts) { + results.push(await this.embed(text)); + } + return results; + } + + async embedSkill(skill: SkillSummary): Promise { + const textContent = this.buildSkillText(skill); + const vector = await this.embed(textContent); + + return { + skillName: skill.name, + vector, + textContent, + generatedAt: new Date().toISOString(), + }; + } + + async embedSkillWithChunks( + skill: SkillSummary, + fullContent?: string + ): Promise { + const textContent = this.buildSkillText(skill); + const mainVector = await this.embed(textContent); + + const chunks = fullContent ? this.chunkText(fullContent) : undefined; + let embeddedChunks: SkillEmbedding['chunks'] | undefined; + + if (chunks && chunks.length > 0) { + embeddedChunks = []; + for (const chunk of chunks) { + const chunkVector = await this.embed(chunk.content); + embeddedChunks.push({ + content: chunk.content, + vector: chunkVector, + startLine: chunk.startLine, + endLine: chunk.endLine, + }); + } + } + + return { + skillName: skill.name, + vector: mainVector, + textContent, + chunks: embeddedChunks, + generatedAt: new Date().toISOString(), + }; + } + + async embedSkills( + skills: SkillSummary[], + onProgress?: IndexBuildCallback + ): Promise { + await this.initialize(onProgress); + + const embeddings: SkillEmbedding[] = []; + const total = skills.length; + + for (let i = 0; i < skills.length; i++) { + const skill = skills[i]; + + onProgress?.({ + phase: 'embedding', + current: i + 1, + total, + skillName: skill.name, + message: `Embedding skill ${i + 1}/${total}: ${skill.name}`, + }); + + const embedding = await this.embedSkill(skill); + embeddings.push(embedding); + } + + onProgress?.({ + phase: 'complete', + current: total, + total, + message: `Embedded ${total} skills`, + }); + + return embeddings; + } + + private buildSkillText(skill: SkillSummary): string { + const parts: string[] = [skill.name]; + + if (skill.description) { + parts.push(skill.description); + } + + if (skill.tags && skill.tags.length > 0) { + parts.push(skill.tags.join(' ')); + } + + if (skill.compatibility) { + if (skill.compatibility.frameworks?.length) { + parts.push(skill.compatibility.frameworks.join(' ')); + } + if (skill.compatibility.languages?.length) { + parts.push(skill.compatibility.languages.join(' ')); + } + if (skill.compatibility.libraries?.length) { + parts.push(skill.compatibility.libraries.join(' ')); + } + } + + return parts.join(' ').toLowerCase(); + } + + private chunkText( + text: string, + config = DEFAULT_CHUNKING_CONFIG + ): { content: string; startLine: number; endLine: number }[] { + const lines = text.split('\n'); + const chunks: { content: string; startLine: number; endLine: number }[] = []; + + const avgCharsPerToken = 4; + const maxCharsPerChunk = config.maxTokens * avgCharsPerToken; + const overlapChars = Math.floor(maxCharsPerChunk * (config.overlapPercent / 100)); + + let currentChunk: string[] = []; + let currentChars = 0; + let startLine = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const lineChars = line.length + 1; + + if (currentChars + lineChars > maxCharsPerChunk && currentChunk.length > 0) { + const content = currentChunk.join('\n'); + if (content.length >= config.minChunkSize) { + chunks.push({ + content, + startLine, + endLine: i - 1, + }); + } + + const overlapLines: string[] = []; + let overlapCharsCount = 0; + for (let j = currentChunk.length - 1; j >= 0 && overlapCharsCount < overlapChars; j--) { + overlapLines.unshift(currentChunk[j]); + overlapCharsCount += currentChunk[j].length + 1; + } + + currentChunk = overlapLines; + currentChars = overlapCharsCount; + startLine = Math.max(0, i - overlapLines.length); + } + + currentChunk.push(line); + currentChars += lineChars; + } + + if (currentChunk.length > 0) { + const content = currentChunk.join('\n'); + if (content.length >= config.minChunkSize) { + chunks.push({ + content, + startLine, + endLine: lines.length - 1, + }); + } + } + + return chunks; + } + + cosineSimilarity(a: Float32Array, b: Float32Array): number { + if (a.length !== b.length) { + throw new Error(`Vector dimensions mismatch: ${a.length} vs ${b.length}`); + } + + let dotProduct = 0; + let normA = 0; + let normB = 0; + + for (let i = 0; i < a.length; i++) { + dotProduct += a[i] * b[i]; + normA += a[i] * a[i]; + normB += b[i] * b[i]; + } + + const magnitude = Math.sqrt(normA) * Math.sqrt(normB); + if (magnitude === 0) return 0; + + return dotProduct / magnitude; + } + + getDimensions(): number { + return this.dimensions; + } + + isInitialized(): boolean { + return this.initialized; + } + + async dispose(): Promise { + if (this.context && typeof (this.context as { dispose?: () => void }).dispose === 'function') { + (this.context as { dispose: () => void }).dispose(); + } + if (this.model && typeof (this.model as { dispose?: () => void }).dispose === 'function') { + (this.model as { dispose: () => void }).dispose(); + } + this.context = null; + this.model = null; + this.initialized = false; + } +} + +export function createEmbeddingService(modelManager?: LocalModelManager): EmbeddingService { + return new EmbeddingService(modelManager); +} diff --git a/packages/core/src/search/expansion.ts b/packages/core/src/search/expansion.ts new file mode 100644 index 00000000..550230c7 --- /dev/null +++ b/packages/core/src/search/expansion.ts @@ -0,0 +1,236 @@ +import type { ExpandedQuery, IndexBuildCallback } from './types.js'; +import { DEFAULT_HYBRID_CONFIG } from './types.js'; +import { LocalModelManager } from './local-models.js'; + +interface CacheEntry { + query: ExpandedQuery; + timestamp: number; +} + +const expansionCache = new Map(); + +export class QueryExpander { + private modelManager: LocalModelManager; + private model: unknown = null; + private context: unknown = null; + private initialized = false; + private cacheTtlMs: number; + + constructor(modelManager?: LocalModelManager, cacheTtlMs?: number) { + this.modelManager = modelManager ?? new LocalModelManager(); + this.cacheTtlMs = cacheTtlMs ?? DEFAULT_HYBRID_CONFIG.expansionCacheTtlMs; + } + + async initialize(onProgress?: IndexBuildCallback): Promise { + if (this.initialized) return; + + onProgress?.({ + phase: 'loading', + current: 0, + total: 1, + message: 'Loading LLM model for query expansion...', + }); + + const modelPath = await this.modelManager.ensureLlmModel(onProgress); + + try { + // @ts-ignore - node-llama-cpp is an optional dependency + const llamaModule = await import('node-llama-cpp'); + const { getLlama, LlamaChatSession } = llamaModule; + + const llama = await getLlama(); + this.model = await llama.loadModel({ modelPath }); + const ctx = await (this.model as { createContext: (opts?: { contextSize?: number }) => Promise<{ getSequence: () => unknown }> }).createContext({ contextSize: 2048 }); + this.context = new LlamaChatSession({ contextSequence: ctx.getSequence() as never }); + + this.initialized = true; + + onProgress?.({ + phase: 'loading', + current: 1, + total: 1, + message: 'LLM model loaded successfully', + }); + } catch (error) { + if ( + error instanceof Error && + error.message.includes("Cannot find package 'node-llama-cpp'") + ) { + throw new Error( + 'node-llama-cpp is not installed. Install it with: pnpm add node-llama-cpp' + ); + } + throw error; + } + } + + async expand(query: string, maxVariations: number = 3): Promise { + const cached = this.getFromCache(query); + if (cached) { + return cached; + } + + if (!this.initialized) { + return this.expandWithoutLLM(query); + } + + try { + const variations = await this.generateVariations(query, maxVariations); + const weights = [2.0, ...variations.map(() => 1.0)]; + + const expanded: ExpandedQuery = { + original: query, + variations, + weights, + }; + + this.addToCache(query, expanded); + return expanded; + } catch { + return this.expandWithoutLLM(query); + } + } + + private async generateVariations(query: string, maxVariations: number): Promise { + const session = this.context as { + prompt(text: string): Promise; + }; + + const prompt = `Generate ${maxVariations} alternative search queries for finding software development skills related to: "${query}" + +Rules: +- Each alternative should use different terminology but capture the same intent +- Keep each alternative concise (2-5 words) +- Focus on technical terms and synonyms +- Output only the alternatives, one per line, no numbering + +Example: +Query: "auth for react" +Alternatives: +authentication react +login react hooks +OAuth react integration + +Query: "${query}" +Alternatives:`; + + const response = await session.prompt(prompt); + + const variations = response + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length > 0 && line.length < 100) + .filter((line) => !line.startsWith('-') && !line.match(/^\d+\./)) + .slice(0, maxVariations); + + return variations; + } + + private expandWithoutLLM(query: string): ExpandedQuery { + const variations: string[] = []; + const queryLower = query.toLowerCase(); + + const synonyms: Record = { + auth: ['authentication', 'login', 'oauth', 'jwt'], + authentication: ['auth', 'login', 'oauth'], + login: ['auth', 'authentication', 'signin'], + api: ['rest', 'endpoint', 'http', 'graphql'], + rest: ['api', 'http', 'endpoint'], + db: ['database', 'sql', 'storage'], + database: ['db', 'sql', 'data storage'], + test: ['testing', 'unit test', 'spec'], + testing: ['test', 'unit test', 'e2e'], + ui: ['user interface', 'frontend', 'component'], + frontend: ['ui', 'client', 'browser'], + backend: ['server', 'api', 'service'], + form: ['input', 'validation', 'submit'], + validation: ['form validation', 'input check', 'verify'], + state: ['state management', 'store', 'data flow'], + animation: ['motion', 'transition', 'animate'], + style: ['styling', 'css', 'design'], + css: ['style', 'styling', 'tailwind'], + }; + + const terms = queryLower.split(/\s+/); + for (const term of terms) { + const termSynonyms = synonyms[term]; + if (termSynonyms) { + for (const syn of termSynonyms.slice(0, 2)) { + const variation = query.replace(new RegExp(term, 'i'), syn); + if (variation !== query && !variations.includes(variation)) { + variations.push(variation); + } + } + } + } + + const limitedVariations = variations.slice(0, 3); + const weights = [2.0, ...limitedVariations.map(() => 1.0)]; + + return { + original: query, + variations: limitedVariations, + weights, + }; + } + + private getFromCache(query: string): ExpandedQuery | null { + const entry = expansionCache.get(query.toLowerCase()); + if (!entry) return null; + + const now = Date.now(); + if (now - entry.timestamp > this.cacheTtlMs) { + expansionCache.delete(query.toLowerCase()); + return null; + } + + return entry.query; + } + + private addToCache(query: string, expanded: ExpandedQuery): void { + const maxCacheSize = 100; + if (expansionCache.size >= maxCacheSize) { + const oldestKey = expansionCache.keys().next().value; + if (oldestKey) { + expansionCache.delete(oldestKey); + } + } + + expansionCache.set(query.toLowerCase(), { + query: expanded, + timestamp: Date.now(), + }); + } + + clearCache(): void { + expansionCache.clear(); + } + + isInitialized(): boolean { + return this.initialized; + } + + async dispose(): Promise { + if (this.context && typeof (this.context as { dispose?: () => void }).dispose === 'function') { + (this.context as { dispose: () => void }).dispose(); + } + if (this.model && typeof (this.model as { dispose?: () => void }).dispose === 'function') { + (this.model as { dispose: () => void }).dispose(); + } + this.context = null; + this.model = null; + this.initialized = false; + } +} + +export function createQueryExpander( + modelManager?: LocalModelManager, + cacheTtlMs?: number +): QueryExpander { + return new QueryExpander(modelManager, cacheTtlMs); +} + +export function expandQuerySimple(query: string): ExpandedQuery { + const expander = new QueryExpander(); + return expander['expandWithoutLLM'](query); +} diff --git a/packages/core/src/search/hybrid.ts b/packages/core/src/search/hybrid.ts new file mode 100644 index 00000000..524ca3a1 --- /dev/null +++ b/packages/core/src/search/hybrid.ts @@ -0,0 +1,384 @@ +import type { SkillSummary, SkillIndex } from '../recommend/types.js'; +import type { + HybridSearchOptions, + HybridSearchResult, + HybridSearchResponse, + ExpandedQuery, + IndexBuildCallback, + RerankerInput, + RerankerOutput, + LocalModelConfig, +} from './types.js'; +import { DEFAULT_HYBRID_CONFIG } from './types.js'; +import { EmbeddingService, createEmbeddingService } from './embeddings.js'; +import { VectorStore, createVectorStore } from './vector-store.js'; +import { QueryExpander, createQueryExpander } from './expansion.js'; +import { LocalModelManager } from './local-models.js'; +import { + fuseWithRRF, + applyPositionAwareBlending, + type RankerResult, + type RRFInput, +} from './rrf.js'; +import { RecommendationEngine } from '../recommend/engine.js'; + +export class HybridSearchPipeline { + private embeddingService: EmbeddingService; + private vectorStore: VectorStore; + private queryExpander: QueryExpander; + private modelManager: LocalModelManager; + private recommendationEngine: RecommendationEngine; + private initialized = false; + private skillsMap: Map = new Map(); + + constructor(config: Partial = {}) { + this.modelManager = new LocalModelManager(config); + this.embeddingService = createEmbeddingService(this.modelManager); + this.vectorStore = createVectorStore(undefined, this.embeddingService); + this.queryExpander = createQueryExpander(this.modelManager); + this.recommendationEngine = new RecommendationEngine(); + } + + async initialize(onProgress?: IndexBuildCallback): Promise { + if (this.initialized) return; + + await this.vectorStore.initialize(); + this.initialized = true; + + onProgress?.({ + phase: 'complete', + current: 1, + total: 1, + message: 'Hybrid search pipeline initialized', + }); + } + + async buildIndex( + skills: SkillSummary[], + onProgress?: IndexBuildCallback + ): Promise { + await this.initialize(onProgress); + + this.skillsMap.clear(); + for (const skill of skills) { + this.skillsMap.set(skill.name, skill); + } + + const index: SkillIndex = { + version: 1, + lastUpdated: new Date().toISOString(), + skills, + sources: [], + }; + this.recommendationEngine.loadIndex(index); + + try { + await this.embeddingService.initialize(onProgress); + const embeddings = await this.embeddingService.embedSkills(skills, onProgress); + await this.vectorStore.storeBatch(embeddings, onProgress); + + onProgress?.({ + phase: 'complete', + current: skills.length, + total: skills.length, + message: `Indexed ${skills.length} skills with embeddings`, + }); + } catch { + // Embeddings not available - will use keyword-only search + onProgress?.({ + phase: 'complete', + current: skills.length, + total: skills.length, + message: `Indexed ${skills.length} skills (keyword-only, embeddings unavailable)`, + }); + } + } + + async search(options: HybridSearchOptions): Promise { + const startTime = Date.now(); + const timing: HybridSearchResponse['timing'] = { totalMs: 0 }; + const stats: HybridSearchResponse['stats'] = { + candidatesFromVector: 0, + candidatesFromKeyword: 0, + totalMerged: 0, + reranked: 0, + }; + + await this.initialize(); + + const { + query, + limit = 10, + enableExpansion = false, + enableReranking = false, + rrfK = DEFAULT_HYBRID_CONFIG.rrfK, + positionAwareBlending = true, + } = options; + + let expandedQuery: ExpandedQuery | undefined; + const searchQueries: string[] = [query]; + + if (enableExpansion) { + try { + expandedQuery = await this.queryExpander.expand(query); + searchQueries.push(...expandedQuery.variations); + } catch { + expandedQuery = { + original: query, + variations: [], + weights: [2.0], + }; + } + } + + const rankerInputs: RRFInput[] = []; + + const embeddingStart = Date.now(); + let queryVector: Float32Array | null = null; + try { + if (this.embeddingService.isInitialized()) { + queryVector = await this.embeddingService.embed(query); + } + } catch { + } + timing.embeddingMs = Date.now() - embeddingStart; + + if (queryVector) { + const vectorStart = Date.now(); + const vectorResults = await this.vectorStore.search(queryVector, limit * 3); + timing.vectorSearchMs = Date.now() - vectorStart; + + const vectorRankerResults: RankerResult[] = vectorResults.map((r) => ({ + skillName: r.skillName, + score: r.similarity, + })); + + rankerInputs.push({ + source: 'vector', + results: vectorRankerResults, + }); + + stats.candidatesFromVector = vectorResults.length; + } + + const keywordStart = Date.now(); + const keywordResults: RankerResult[] = []; + + for (let i = 0; i < searchQueries.length; i++) { + const searchQuery = searchQueries[i]; + const weight = expandedQuery?.weights[i] ?? 1; + + const results = this.recommendationEngine.search({ + query: searchQuery, + limit: limit * 3, + semantic: true, + }); + + for (const result of results) { + const existing = keywordResults.find( + (r) => r.skillName === result.skill.name + ); + if (existing) { + existing.score = Math.max(existing.score, result.relevance * weight); + } else { + keywordResults.push({ + skillName: result.skill.name, + score: result.relevance * weight, + }); + } + } + } + + timing.keywordSearchMs = Date.now() - keywordStart; + + rankerInputs.push({ + source: 'keyword', + results: keywordResults, + }); + + stats.candidatesFromKeyword = keywordResults.length; + + const fusionStart = Date.now(); + const rrfRankings = fuseWithRRF(rankerInputs, rrfK); + timing.fusionMs = Date.now() - fusionStart; + + stats.totalMerged = rrfRankings.length; + + let finalScores = new Map(); + for (const ranking of rrfRankings) { + finalScores.set(ranking.skillName, ranking.rrfScore); + } + + if (enableReranking && rrfRankings.length > 0) { + const rerankStart = Date.now(); + const topCandidates = rrfRankings.slice(0, DEFAULT_HYBRID_CONFIG.rerankTopN); + + const rerankerInputs: RerankerInput[] = topCandidates + .map((r) => { + const skill = this.skillsMap.get(r.skillName); + if (!skill) return null; + return { + query, + skillName: r.skillName, + skillDescription: skill.description ?? '', + skillTags: skill.tags ?? [], + }; + }) + .filter((r): r is RerankerInput => r !== null); + + const rerankerOutputs = await this.rerank(rerankerInputs); + timing.rerankingMs = Date.now() - rerankStart; + stats.reranked = rerankerOutputs.length; + + if (positionAwareBlending && rerankerOutputs.length > 0) { + const retrievalScores = new Map(); + const maxRrfScore = Math.max(...rrfRankings.map((r) => r.rrfScore)); + for (const ranking of topCandidates) { + retrievalScores.set( + ranking.skillName, + ranking.rrfScore / (maxRrfScore || 1) + ); + } + + const rerankerScores = new Map(); + const maxRerankerScore = Math.max(...rerankerOutputs.map((r) => r.score)); + for (const output of rerankerOutputs) { + rerankerScores.set( + output.skillName, + output.score / (maxRerankerScore || 1) + ); + } + + const sortedSkillNames = topCandidates.map((r) => r.skillName); + const blendedScores = applyPositionAwareBlending( + retrievalScores, + rerankerScores, + sortedSkillNames + ); + + for (const [skillName, score] of blendedScores) { + finalScores.set(skillName, score); + } + } + } + + const sortedResults = Array.from(finalScores.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, limit); + + const vectorInput = rankerInputs.find((r) => r.source === 'vector'); + const keywordInput = rankerInputs.find((r) => r.source === 'keyword'); + + const hybridResults: HybridSearchResult[] = []; + + for (const [skillName, hybridScore] of sortedResults) { + const skill = this.skillsMap.get(skillName); + if (!skill) continue; + + hybridResults.push({ + skill, + relevance: Math.round(hybridScore * 100), + hybridScore, + vectorSimilarity: vectorInput?.results.find((r) => r.skillName === skillName)?.score, + keywordScore: keywordInput?.results.find((r) => r.skillName === skillName)?.score, + rrfScore: rrfRankings.find((r) => r.skillName === skillName)?.rrfScore, + matchedTerms: [], + expandedTerms: expandedQuery?.variations, + }); + } + + timing.totalMs = Date.now() - startTime; + + return { + results: hybridResults, + query: { + original: query, + expanded: expandedQuery, + }, + timing, + stats, + }; + } + + private async rerank(inputs: RerankerInput[]): Promise { + const outputs: RerankerOutput[] = []; + + for (const input of inputs) { + const baseScore = + (input.skillDescription.toLowerCase().includes(input.query.toLowerCase()) + ? 0.5 + : 0) + + (input.skillTags.some((t) => + t.toLowerCase().includes(input.query.toLowerCase()) + ) + ? 0.3 + : 0) + + (input.skillName.toLowerCase().includes(input.query.toLowerCase()) + ? 0.2 + : 0); + + outputs.push({ + skillName: input.skillName, + score: Math.min(1, baseScore + 0.3), + }); + } + + return outputs; + } + + loadSkillsIndex(index: SkillIndex): void { + this.skillsMap.clear(); + for (const skill of index.skills) { + this.skillsMap.set(skill.name, skill); + } + this.recommendationEngine.loadIndex(index); + } + + getStats(): { + vectorStore: ReturnType; + initialized: boolean; + skillCount: number; + } { + return { + vectorStore: this.vectorStore.getStats(), + initialized: this.initialized, + skillCount: this.skillsMap.size, + }; + } + + isInitialized(): boolean { + return this.initialized; + } + + async dispose(): Promise { + await this.embeddingService.dispose(); + await this.queryExpander.dispose(); + await this.vectorStore.close(); + this.initialized = false; + } +} + +export function createHybridSearchPipeline( + config?: Partial +): HybridSearchPipeline { + return new HybridSearchPipeline(config); +} + +export async function hybridSearch( + skills: SkillSummary[], + query: string, + options: Partial = {} +): Promise { + const pipeline = createHybridSearchPipeline(); + + try { + await pipeline.buildIndex(skills); + + return await pipeline.search({ + query, + ...options, + }); + } finally { + await pipeline.dispose(); + } +} diff --git a/packages/core/src/search/index.ts b/packages/core/src/search/index.ts new file mode 100644 index 00000000..f459bc85 --- /dev/null +++ b/packages/core/src/search/index.ts @@ -0,0 +1,7 @@ +export * from './types.js'; +export * from './local-models.js'; +export * from './embeddings.js'; +export * from './vector-store.js'; +export * from './rrf.js'; +export * from './expansion.js'; +export * from './hybrid.js'; diff --git a/packages/core/src/search/local-models.ts b/packages/core/src/search/local-models.ts new file mode 100644 index 00000000..c88f39b0 --- /dev/null +++ b/packages/core/src/search/local-models.ts @@ -0,0 +1,243 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as os from 'node:os'; +import { createWriteStream } from 'node:fs'; +import type { LocalModelConfig, IndexBuildCallback } from './types.js'; +import { MODEL_REGISTRY } from './types.js'; + +const DEFAULT_MODEL_DIR = path.join(os.homedir(), '.skillkit', 'models'); + +type EmbeddingModelName = keyof typeof MODEL_REGISTRY.embeddings; +type LlmModelName = keyof typeof MODEL_REGISTRY.llm; + +interface ModelInfo { + url: string; + size: number; + description: string; + dimensions?: number; +} + +export class LocalModelManager { + private config: Required; + private embedModelPath: string | null = null; + private llmModelPath: string | null = null; + + constructor(config: Partial = {}) { + this.config = { + embedModel: config.embedModel ?? 'nomic-embed-text-v1.5.Q4_K_M.gguf', + llmModel: config.llmModel ?? 'gemma-2b-it-Q4_K_M.gguf', + modelDir: config.modelDir ?? DEFAULT_MODEL_DIR, + autoDownload: config.autoDownload ?? true, + gpuLayers: config.gpuLayers ?? 0, + }; + } + + async ensureModelsDirectory(): Promise { + if (!fs.existsSync(this.config.modelDir)) { + await fs.promises.mkdir(this.config.modelDir, { recursive: true }); + } + } + + getEmbedModelPath(): string { + return path.join(this.config.modelDir, this.config.embedModel); + } + + getLlmModelPath(): string { + return path.join(this.config.modelDir, this.config.llmModel); + } + + isEmbedModelAvailable(): boolean { + return fs.existsSync(this.getEmbedModelPath()); + } + + isLlmModelAvailable(): boolean { + return fs.existsSync(this.getLlmModelPath()); + } + + private getModelInfo(modelType: 'embed' | 'llm', modelName: string): ModelInfo | undefined { + if (modelType === 'embed') { + return MODEL_REGISTRY.embeddings[modelName as EmbeddingModelName] as ModelInfo | undefined; + } + return MODEL_REGISTRY.llm[modelName as LlmModelName] as ModelInfo | undefined; + } + + async downloadModel( + modelType: 'embed' | 'llm', + onProgress?: IndexBuildCallback + ): Promise { + await this.ensureModelsDirectory(); + + const modelName = modelType === 'embed' ? this.config.embedModel : this.config.llmModel; + const modelPath = modelType === 'embed' ? this.getEmbedModelPath() : this.getLlmModelPath(); + + if (fs.existsSync(modelPath)) { + return modelPath; + } + + const modelInfo = this.getModelInfo(modelType, modelName); + + if (!modelInfo) { + const availableModels = modelType === 'embed' + ? Object.keys(MODEL_REGISTRY.embeddings) + : Object.keys(MODEL_REGISTRY.llm); + throw new Error(`Unknown model: ${modelName}. Available models: ${availableModels.join(', ')}`); + } + + onProgress?.({ + phase: 'downloading', + current: 0, + total: modelInfo.size, + message: `Downloading ${modelName}...`, + }); + + const response = await fetch(modelInfo.url); + if (!response.ok) { + throw new Error(`Failed to download model: ${response.statusText}`); + } + + const contentLength = Number(response.headers.get('content-length')) || modelInfo.size; + let downloaded = 0; + + const tempPath = `${modelPath}.tmp`; + const fileStream = createWriteStream(tempPath); + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('Failed to get response body reader'); + } + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const canContinue = fileStream.write(Buffer.from(value)); + if (!canContinue) { + await new Promise((resolve) => fileStream.once('drain', resolve)); + } + downloaded += value.length; + + onProgress?.({ + phase: 'downloading', + current: downloaded, + total: contentLength, + message: `Downloading ${modelName}: ${Math.round((downloaded / contentLength) * 100)}%`, + }); + } + + fileStream.end(); + await new Promise((resolve, reject) => { + fileStream.on('finish', resolve); + fileStream.on('error', reject); + }); + + await fs.promises.rename(tempPath, modelPath); + return modelPath; + } catch (error) { + if (fs.existsSync(tempPath)) { + await fs.promises.unlink(tempPath); + } + throw error; + } + } + + async ensureEmbedModel(onProgress?: IndexBuildCallback): Promise { + if (this.embedModelPath && fs.existsSync(this.embedModelPath)) { + return this.embedModelPath; + } + + if (!this.isEmbedModelAvailable()) { + if (!this.config.autoDownload) { + throw new Error( + `Embedding model not found at ${this.getEmbedModelPath()}. ` + + 'Set autoDownload: true or manually download the model.' + ); + } + this.embedModelPath = await this.downloadModel('embed', onProgress); + } else { + this.embedModelPath = this.getEmbedModelPath(); + } + + return this.embedModelPath; + } + + async ensureLlmModel(onProgress?: IndexBuildCallback): Promise { + if (this.llmModelPath && fs.existsSync(this.llmModelPath)) { + return this.llmModelPath; + } + + if (!this.isLlmModelAvailable()) { + if (!this.config.autoDownload) { + throw new Error( + `LLM model not found at ${this.getLlmModelPath()}. ` + + 'Set autoDownload: true or manually download the model.' + ); + } + this.llmModelPath = await this.downloadModel('llm', onProgress); + } else { + this.llmModelPath = this.getLlmModelPath(); + } + + return this.llmModelPath; + } + + async ensureAllModels(onProgress?: IndexBuildCallback): Promise<{ + embedModel: string; + llmModel: string; + }> { + const embedModel = await this.ensureEmbedModel(onProgress); + const llmModel = await this.ensureLlmModel(onProgress); + return { embedModel, llmModel }; + } + + getPublicModelInfo(modelType: 'embed' | 'llm'): { + name: string; + path: string; + available: boolean; + size: number; + description: string; + } { + const modelName = modelType === 'embed' ? this.config.embedModel : this.config.llmModel; + const modelPath = modelType === 'embed' ? this.getEmbedModelPath() : this.getLlmModelPath(); + const modelInfo = this.getModelInfo(modelType, modelName); + + return { + name: modelName, + path: modelPath, + available: fs.existsSync(modelPath), + size: modelInfo?.size ?? 0, + description: modelInfo?.description ?? 'Unknown model', + }; + } + + async clearModels(): Promise { + const embedPath = this.getEmbedModelPath(); + const llmPath = this.getLlmModelPath(); + + if (fs.existsSync(embedPath)) { + await fs.promises.unlink(embedPath); + } + if (fs.existsSync(llmPath)) { + await fs.promises.unlink(llmPath); + } + + this.embedModelPath = null; + this.llmModelPath = null; + } + + getConfig(): Required { + return { ...this.config }; + } +} + +export function formatBytes(bytes: number): string { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; +} + +export function getDefaultModelDir(): string { + return DEFAULT_MODEL_DIR; +} diff --git a/packages/core/src/search/rrf.ts b/packages/core/src/search/rrf.ts new file mode 100644 index 00000000..a64a5675 --- /dev/null +++ b/packages/core/src/search/rrf.ts @@ -0,0 +1,177 @@ +import type { RRFRanking } from './types.js'; +import { DEFAULT_HYBRID_CONFIG } from './types.js'; + +export interface RankerResult { + skillName: string; + score: number; +} + +export interface RRFInput { + source: string; + results: RankerResult[]; +} + +export function computeRRFScore(ranks: number[], k: number = DEFAULT_HYBRID_CONFIG.rrfK): number { + return ranks.reduce((sum, rank) => sum + 1 / (k + rank), 0); +} + +export function fuseWithRRF( + rankerInputs: RRFInput[], + k: number = DEFAULT_HYBRID_CONFIG.rrfK +): RRFRanking[] { + const skillRanks = new Map(); + + for (const input of rankerInputs) { + const sortedResults = [...input.results].sort((a, b) => b.score - a.score); + + for (let i = 0; i < sortedResults.length; i++) { + const result = sortedResults[i]; + const rank = i + 1; + + if (!skillRanks.has(result.skillName)) { + skillRanks.set(result.skillName, []); + } + skillRanks.get(result.skillName)!.push({ + source: input.source, + rank, + }); + } + } + + const rankings: RRFRanking[] = []; + + for (const [skillName, ranks] of skillRanks) { + const rankValues = ranks.map((r) => r.rank); + const rrfScore = computeRRFScore(rankValues, k); + + rankings.push({ + skillName, + rrfScore, + ranks, + }); + } + + rankings.sort((a, b) => b.rrfScore - a.rrfScore); + + return rankings; +} + +export function normalizeScores(results: RankerResult[]): RankerResult[] { + if (results.length === 0) return []; + + const maxScore = Math.max(...results.map((r) => r.score)); + const minScore = Math.min(...results.map((r) => r.score)); + const range = maxScore - minScore; + + if (range === 0) { + return results.map((r) => ({ ...r, score: 1 })); + } + + return results.map((r) => ({ + ...r, + score: (r.score - minScore) / range, + })); +} + +export function weightedCombine( + results: RRFInput[], + weights: Record +): RankerResult[] { + const combinedScores = new Map(); + + for (const input of results) { + const weight = weights[input.source] ?? 1; + const normalizedResults = normalizeScores(input.results); + + for (const result of normalizedResults) { + const current = combinedScores.get(result.skillName) ?? 0; + combinedScores.set(result.skillName, current + result.score * weight); + } + } + + return Array.from(combinedScores, ([skillName, score]) => ({ skillName, score })) + .sort((a, b) => b.score - a.score); +} + +export function applyPositionAwareBlending( + retrievalScores: Map, + rerankerScores: Map, + sortedSkillNames: string[], + config = DEFAULT_HYBRID_CONFIG.positionBlending +): Map { + const blendedScores = new Map(); + + for (let i = 0; i < sortedSkillNames.length; i++) { + const skillName = sortedSkillNames[i]; + const rank = i + 1; + + let weights: { retrieval: number; reranker: number }; + if (rank <= 3) { + weights = config.top3; + } else if (rank <= 10) { + weights = config.top10; + } else { + weights = config.rest; + } + + const retrievalScore = retrievalScores.get(skillName) ?? 0; + const rerankerScore = rerankerScores.get(skillName) ?? 0; + + const blendedScore = + retrievalScore * weights.retrieval + rerankerScore * weights.reranker; + + blendedScores.set(skillName, blendedScore); + } + + return blendedScores; +} + +export function getRankFromScore( + skillName: string, + rankedResults: RankerResult[] +): number { + const sortedResults = [...rankedResults].sort((a, b) => b.score - a.score); + const index = sortedResults.findIndex((r) => r.skillName === skillName); + return index === -1 ? sortedResults.length + 1 : index + 1; +} + +export function mergeRankings( + primaryRankings: RRFRanking[], + secondaryRankings: RRFRanking[], + primaryWeight: number = 0.7 +): RRFRanking[] { + const secondaryWeight = 1 - primaryWeight; + const mergedScores = new Map(); + + for (const ranking of primaryRankings) { + mergedScores.set(ranking.skillName, { + score: ranking.rrfScore * primaryWeight, + ranks: [...ranking.ranks], + }); + } + + for (const ranking of secondaryRankings) { + const existing = mergedScores.get(ranking.skillName); + if (existing) { + existing.score += ranking.rrfScore * secondaryWeight; + existing.ranks = [...existing.ranks, ...ranking.ranks]; + } else { + mergedScores.set(ranking.skillName, { + score: ranking.rrfScore * secondaryWeight, + ranks: [...ranking.ranks], + }); + } + } + + const merged: RRFRanking[] = []; + for (const [skillName, data] of mergedScores) { + merged.push({ + skillName, + rrfScore: data.score, + ranks: data.ranks, + }); + } + + merged.sort((a, b) => b.rrfScore - a.rrfScore); + return merged; +} diff --git a/packages/core/src/search/types.ts b/packages/core/src/search/types.ts new file mode 100644 index 00000000..267fd785 --- /dev/null +++ b/packages/core/src/search/types.ts @@ -0,0 +1,177 @@ +import { z } from 'zod'; +import type { SearchResult } from '../recommend/types.js'; + +export const LocalModelConfigSchema = z.object({ + embedModel: z.string().default('nomic-embed-text-v1.5.Q4_K_M.gguf'), + llmModel: z.string().default('gemma-2b-it-Q4_K_M.gguf'), + modelDir: z.string().optional(), + autoDownload: z.boolean().default(true), + gpuLayers: z.number().default(0), +}); +export type LocalModelConfig = z.infer; + +export const SkillChunkSchema = z.object({ + content: z.string(), + startLine: z.number(), + endLine: z.number(), + tokenCount: z.number(), +}); +export type SkillChunk = z.infer; + +export interface SkillEmbedding { + skillName: string; + vector: Float32Array; + textContent: string; + chunks?: { + content: string; + vector: Float32Array; + startLine: number; + endLine: number; + }[]; + generatedAt: string; +} + +export interface VectorSearchResult { + skillName: string; + similarity: number; + matchedChunk?: { + content: string; + startLine: number; + }; +} + +export interface ExpandedQuery { + original: string; + variations: string[]; + weights: number[]; +} + +export interface RRFRanking { + skillName: string; + rrfScore: number; + ranks: { + source: string; + rank: number; + }[]; +} + +export interface HybridSearchOptions { + query: string; + limit?: number; + enableExpansion?: boolean; + enableReranking?: boolean; + semanticWeight?: number; + keywordWeight?: number; + rrfK?: number; + positionAwareBlending?: boolean; +} + +export interface HybridSearchResult extends SearchResult { + hybridScore: number; + vectorSimilarity?: number; + keywordScore?: number; + rrfScore?: number; + rerankerScore?: number; + expandedTerms?: string[]; + blendingWeights?: { + retrieval: number; + reranker: number; + }; +} + +export interface HybridSearchResponse { + results: HybridSearchResult[]; + query: { + original: string; + expanded?: ExpandedQuery; + }; + timing: { + totalMs: number; + embeddingMs?: number; + vectorSearchMs?: number; + keywordSearchMs?: number; + fusionMs?: number; + rerankingMs?: number; + }; + stats: { + candidatesFromVector: number; + candidatesFromKeyword: number; + totalMerged: number; + reranked: number; + }; +} + +export interface EmbeddingServiceStats { + totalSkillsIndexed: number; + totalChunks: number; + indexSizeBytes: number; + lastIndexedAt: string; + modelName: string; + embeddingDimensions: number; +} + +export interface VectorStoreConfig { + dbPath: string; + tableName?: string; + dimensions?: number; +} + +export interface IndexBuildProgress { + phase: 'downloading' | 'loading' | 'embedding' | 'storing' | 'complete'; + current: number; + total: number; + skillName?: string; + message?: string; +} + +export type IndexBuildCallback = (progress: IndexBuildProgress) => void; + +export const MODEL_REGISTRY = { + embeddings: { + 'nomic-embed-text-v1.5.Q4_K_M.gguf': { + url: 'https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/nomic-embed-text-v1.5.Q4_K_M.gguf', + size: 547000000, + dimensions: 768, + description: 'Nomic Embed Text v1.5 - High quality embeddings', + }, + }, + llm: { + 'gemma-2b-it-Q4_K_M.gguf': { + url: 'https://huggingface.co/bartowski/gemma-2-2b-it-GGUF/resolve/main/gemma-2-2b-it-Q4_K_M.gguf', + size: 1500000000, + description: 'Gemma 2 2B Instruct - Fast query expansion and reranking', + }, + }, +} as const; + +export const DEFAULT_CHUNKING_CONFIG = { + maxTokens: 800, + overlapPercent: 15, + minChunkSize: 100, +} as const; + +export const DEFAULT_HYBRID_CONFIG = { + rrfK: 60, + semanticWeight: 0.5, + keywordWeight: 0.5, + rerankTopN: 30, + positionBlending: { + top3: { retrieval: 0.75, reranker: 0.25 }, + top10: { retrieval: 0.6, reranker: 0.4 }, + rest: { retrieval: 0.4, reranker: 0.6 }, + }, + expansionCacheTtlMs: 5 * 60 * 1000, +} as const; + +export interface RerankerInput { + query: string; + skillName: string; + skillDescription: string; + skillTags: string[]; +} + +export interface RerankerOutput { + skillName: string; + score: number; + reasoning?: string; +} diff --git a/packages/core/src/search/vector-store.ts b/packages/core/src/search/vector-store.ts new file mode 100644 index 00000000..58d7dd9e --- /dev/null +++ b/packages/core/src/search/vector-store.ts @@ -0,0 +1,510 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as os from 'node:os'; +import type { + SkillEmbedding, + VectorSearchResult, + VectorStoreConfig, + EmbeddingServiceStats, + IndexBuildCallback, +} from './types.js'; +import { EmbeddingService } from './embeddings.js'; + +const DEFAULT_DB_PATH = path.join(os.homedir(), '.skillkit', 'search.db'); +const DEFAULT_TABLE_NAME = 'skill_embeddings'; +const DEFAULT_DIMENSIONS = 768; + +export class VectorStore { + private config: Required; + private db: unknown = null; + private initialized = false; + private usingSqliteVec = false; + private embeddings: Map = new Map(); + private embeddingService: EmbeddingService; + + constructor(config: Partial = {}, embeddingService?: EmbeddingService) { + const tableName = config.tableName ?? DEFAULT_TABLE_NAME; + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) { + throw new Error(`Invalid table name: ${tableName}. Must be a valid SQL identifier.`); + } + this.config = { + dbPath: config.dbPath ?? DEFAULT_DB_PATH, + tableName, + dimensions: config.dimensions ?? DEFAULT_DIMENSIONS, + }; + this.embeddingService = embeddingService ?? new EmbeddingService(); + } + + async initialize(): Promise { + if (this.initialized) return; + + const dbDir = path.dirname(this.config.dbPath); + if (!fs.existsSync(dbDir)) { + await fs.promises.mkdir(dbDir, { recursive: true }); + } + + try { + const BetterSqlite3Module = await import('better-sqlite3'); + const BetterSqlite3 = BetterSqlite3Module.default || BetterSqlite3Module; + this.db = new BetterSqlite3(this.config.dbPath); + + try { + const sqliteVec = await import('sqlite-vec'); + sqliteVec.load(this.db as Parameters[0]); + this.usingSqliteVec = true; + await this.initializeSqliteVecTables(); + } catch { + this.usingSqliteVec = false; + await this.initializeFallbackTables(); + } + + await this.loadEmbeddingsFromDb(); + this.initialized = true; + } catch (error) { + if ( + error instanceof Error && + (error.message.includes("Cannot find package 'better-sqlite3'") || + error.message.includes("Cannot find module 'better-sqlite3'")) + ) { + this.initialized = true; + this.usingSqliteVec = false; + return; + } + throw error; + } + } + + private async initializeSqliteVecTables(): Promise { + const db = this.db as { + exec(sql: string): void; + prepare(sql: string): { run(...args: unknown[]): void }; + }; + + db.exec(` + CREATE TABLE IF NOT EXISTS ${this.config.tableName}_meta ( + skill_name TEXT PRIMARY KEY, + text_content TEXT NOT NULL, + generated_at TEXT NOT NULL, + chunk_count INTEGER DEFAULT 0 + ) + `); + + db.exec(` + CREATE VIRTUAL TABLE IF NOT EXISTS ${this.config.tableName}_vec USING vec0( + skill_name TEXT PRIMARY KEY, + embedding float[${this.config.dimensions}] + ) + `); + + db.exec(` + CREATE TABLE IF NOT EXISTS ${this.config.tableName}_chunks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + skill_name TEXT NOT NULL, + content TEXT NOT NULL, + start_line INTEGER NOT NULL, + end_line INTEGER NOT NULL, + FOREIGN KEY (skill_name) REFERENCES ${this.config.tableName}_meta(skill_name) + ) + `); + + db.exec(` + CREATE VIRTUAL TABLE IF NOT EXISTS ${this.config.tableName}_chunks_vec USING vec0( + chunk_id INTEGER PRIMARY KEY, + embedding float[${this.config.dimensions}] + ) + `); + } + + private async initializeFallbackTables(): Promise { + const db = this.db as { exec(sql: string): void }; + + db.exec(` + CREATE TABLE IF NOT EXISTS ${this.config.tableName} ( + skill_name TEXT PRIMARY KEY, + text_content TEXT NOT NULL, + vector BLOB NOT NULL, + generated_at TEXT NOT NULL + ) + `); + + db.exec(` + CREATE TABLE IF NOT EXISTS ${this.config.tableName}_chunks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + skill_name TEXT NOT NULL, + content TEXT NOT NULL, + vector BLOB NOT NULL, + start_line INTEGER NOT NULL, + end_line INTEGER NOT NULL + ) + `); + + db.exec(` + CREATE INDEX IF NOT EXISTS idx_${this.config.tableName}_skill + ON ${this.config.tableName}_chunks(skill_name) + `); + } + + private async loadEmbeddingsFromDb(): Promise { + if (!this.db) return; + + const db = this.db as { + prepare(sql: string): { all(): Array<{ + skill_name: string; + text_content: string; + vector?: Buffer; + generated_at: string; + }> }; + }; + + try { + if (this.usingSqliteVec) { + const rows = db.prepare(` + SELECT skill_name, text_content, generated_at + FROM ${this.config.tableName}_meta + `).all(); + + for (const row of rows) { + this.embeddings.set(row.skill_name, { + skillName: row.skill_name, + textContent: row.text_content, + vector: new Float32Array(this.config.dimensions), + generatedAt: row.generated_at, + }); + } + } else { + const rows = db.prepare(` + SELECT skill_name, text_content, vector, generated_at + FROM ${this.config.tableName} + `).all(); + + for (const row of rows) { + if (row.vector) { + this.embeddings.set(row.skill_name, { + skillName: row.skill_name, + textContent: row.text_content, + vector: new Float32Array(row.vector.buffer), + generatedAt: row.generated_at, + }); + } + } + } + } catch { + // Failed to load embeddings from DB - will start with empty in-memory store + } + } + + async store(embedding: SkillEmbedding): Promise { + await this.initialize(); + this.embeddings.set(embedding.skillName, embedding); + + if (!this.db) return; + + const db = this.db as { + prepare(sql: string): { + run(...args: unknown[]): { lastInsertRowid?: number }; + }; + }; + + if (this.usingSqliteVec) { + db.prepare(` + INSERT OR REPLACE INTO ${this.config.tableName}_meta + (skill_name, text_content, generated_at, chunk_count) + VALUES (?, ?, ?, ?) + `).run( + embedding.skillName, + embedding.textContent, + embedding.generatedAt, + embedding.chunks?.length ?? 0 + ); + + db.prepare(` + INSERT OR REPLACE INTO ${this.config.tableName}_vec + (skill_name, embedding) + VALUES (?, ?) + `).run(embedding.skillName, Buffer.from(embedding.vector.buffer)); + + if (embedding.chunks) { + db.prepare(` + DELETE FROM ${this.config.tableName}_chunks WHERE skill_name = ? + `).run(embedding.skillName); + + for (const chunk of embedding.chunks) { + const result = db.prepare(` + INSERT INTO ${this.config.tableName}_chunks + (skill_name, content, start_line, end_line) + VALUES (?, ?, ?, ?) + `).run(embedding.skillName, chunk.content, chunk.startLine, chunk.endLine); + + const rowId = result.lastInsertRowid ?? 0; + db.prepare(` + INSERT INTO ${this.config.tableName}_chunks_vec + (chunk_id, embedding) + VALUES (?, ?) + `).run(rowId, Buffer.from(chunk.vector.buffer)); + } + } + } else { + db.prepare(` + INSERT OR REPLACE INTO ${this.config.tableName} + (skill_name, text_content, vector, generated_at) + VALUES (?, ?, ?, ?) + `).run( + embedding.skillName, + embedding.textContent, + Buffer.from(embedding.vector.buffer), + embedding.generatedAt + ); + + if (embedding.chunks) { + db.prepare(` + DELETE FROM ${this.config.tableName}_chunks WHERE skill_name = ? + `).run(embedding.skillName); + + for (const chunk of embedding.chunks) { + db.prepare(` + INSERT INTO ${this.config.tableName}_chunks + (skill_name, content, vector, start_line, end_line) + VALUES (?, ?, ?, ?, ?) + `).run( + embedding.skillName, + chunk.content, + Buffer.from(chunk.vector.buffer), + chunk.startLine, + chunk.endLine + ); + } + } + } + } + + async storeBatch( + embeddings: SkillEmbedding[], + onProgress?: IndexBuildCallback + ): Promise { + await this.initialize(); + + const total = embeddings.length; + for (let i = 0; i < embeddings.length; i++) { + const embedding = embeddings[i]; + + onProgress?.({ + phase: 'storing', + current: i + 1, + total, + skillName: embedding.skillName, + message: `Storing embedding ${i + 1}/${total}: ${embedding.skillName}`, + }); + + await this.store(embedding); + } + + onProgress?.({ + phase: 'complete', + current: total, + total, + message: `Stored ${total} embeddings`, + }); + } + + async search( + queryVector: Float32Array, + limit: number = 10 + ): Promise { + await this.initialize(); + + if (this.usingSqliteVec && this.db) { + return this.searchWithSqliteVec(queryVector, limit); + } + + return this.searchInMemory(queryVector, limit); + } + + private async searchWithSqliteVec( + queryVector: Float32Array, + limit: number + ): Promise { + const db = this.db as { + prepare(sql: string): { + all(...args: unknown[]): Array<{ skill_name: string; distance: number }>; + }; + }; + + const rows = db.prepare(` + SELECT skill_name, distance + FROM ${this.config.tableName}_vec + WHERE embedding MATCH ? + ORDER BY distance + LIMIT ? + `).all(Buffer.from(queryVector.buffer), limit); + + return rows.map((row) => ({ + skillName: row.skill_name, + similarity: 1 - row.distance, + })); + } + + private searchInMemory( + queryVector: Float32Array, + limit: number + ): VectorSearchResult[] { + const results: VectorSearchResult[] = []; + + for (const [skillName, embedding] of this.embeddings) { + const similarity = this.embeddingService.cosineSimilarity(queryVector, embedding.vector); + results.push({ skillName, similarity }); + } + + results.sort((a, b) => b.similarity - a.similarity); + return results.slice(0, limit); + } + + async searchChunks( + queryVector: Float32Array, + limit: number = 10 + ): Promise { + await this.initialize(); + + const results: VectorSearchResult[] = []; + + for (const [skillName, embedding] of this.embeddings) { + if (!embedding.chunks || embedding.chunks.length === 0) { + const similarity = this.embeddingService.cosineSimilarity(queryVector, embedding.vector); + results.push({ skillName, similarity }); + continue; + } + + let bestChunk: { similarity: number; content: string; startLine: number } | null = null; + + for (const chunk of embedding.chunks) { + const similarity = this.embeddingService.cosineSimilarity(queryVector, chunk.vector); + if (!bestChunk || similarity > bestChunk.similarity) { + bestChunk = { + similarity, + content: chunk.content, + startLine: chunk.startLine, + }; + } + } + + if (bestChunk) { + results.push({ + skillName, + similarity: bestChunk.similarity, + matchedChunk: { + content: bestChunk.content, + startLine: bestChunk.startLine, + }, + }); + } + } + + results.sort((a, b) => b.similarity - a.similarity); + return results.slice(0, limit); + } + + has(skillName: string): boolean { + return this.embeddings.has(skillName); + } + + get(skillName: string): SkillEmbedding | undefined { + return this.embeddings.get(skillName); + } + + async delete(skillName: string): Promise { + await this.initialize(); + this.embeddings.delete(skillName); + + if (!this.db) return; + + const db = this.db as { + prepare(sql: string): { run(...args: unknown[]): void }; + }; + + if (this.usingSqliteVec) { + db.prepare(`DELETE FROM ${this.config.tableName}_meta WHERE skill_name = ?`).run(skillName); + db.prepare(`DELETE FROM ${this.config.tableName}_vec WHERE skill_name = ?`).run(skillName); + db.prepare(` + DELETE FROM ${this.config.tableName}_chunks_vec + WHERE chunk_id IN ( + SELECT id FROM ${this.config.tableName}_chunks WHERE skill_name = ? + ) + `).run(skillName); + } else { + db.prepare(`DELETE FROM ${this.config.tableName} WHERE skill_name = ?`).run(skillName); + } + + db.prepare(`DELETE FROM ${this.config.tableName}_chunks WHERE skill_name = ?`).run(skillName); + } + + async clear(): Promise { + await this.initialize(); + this.embeddings.clear(); + + if (!this.db) return; + + const db = this.db as { exec(sql: string): void }; + + if (this.usingSqliteVec) { + db.exec(`DELETE FROM ${this.config.tableName}_meta`); + db.exec(`DELETE FROM ${this.config.tableName}_vec`); + db.exec(`DELETE FROM ${this.config.tableName}_chunks_vec`); + } else { + db.exec(`DELETE FROM ${this.config.tableName}`); + } + + db.exec(`DELETE FROM ${this.config.tableName}_chunks`); + } + + getStats(): EmbeddingServiceStats { + let indexSizeBytes = 0; + let totalChunks = 0; + + for (const embedding of this.embeddings.values()) { + indexSizeBytes += embedding.vector.byteLength; + indexSizeBytes += embedding.textContent.length * 2; + if (embedding.chunks) { + totalChunks += embedding.chunks.length; + for (const chunk of embedding.chunks) { + indexSizeBytes += chunk.vector.byteLength; + indexSizeBytes += chunk.content.length * 2; + } + } + } + + return { + totalSkillsIndexed: this.embeddings.size, + totalChunks, + indexSizeBytes, + lastIndexedAt: new Date().toISOString(), + modelName: 'nomic-embed-text-v1.5', + embeddingDimensions: this.config.dimensions, + }; + } + + isInitialized(): boolean { + return this.initialized; + } + + isUsingSqliteVec(): boolean { + return this.usingSqliteVec; + } + + getEmbeddingCount(): number { + return this.embeddings.size; + } + + async close(): Promise { + if (this.db && typeof (this.db as { close?: () => void }).close === 'function') { + (this.db as { close: () => void }).close(); + } + this.db = null; + this.initialized = false; + } +} + +export function createVectorStore( + config?: Partial, + embeddingService?: EmbeddingService +): VectorStore { + return new VectorStore(config, embeddingService); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 240a7c8a..e8dac132 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -161,7 +161,20 @@ importers: zod: specifier: ^3.24.1 version: 3.25.76 + optionalDependencies: + better-sqlite3: + specifier: ^12.0.0 + version: 12.6.2 + node-llama-cpp: + specifier: ^3.15.0 + version: 3.15.1(typescript@5.9.3) + sqlite-vec: + specifier: ^0.1.6 + version: 0.1.6 devDependencies: + '@types/better-sqlite3': + specifier: ^7.6.11 + version: 7.6.13 '@types/node': specifier: ^22.10.5 version: 22.19.7 @@ -789,6 +802,10 @@ packages: resolution: {integrity: sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==} engines: {node: '>=18'} + '@huggingface/jinja@0.5.5': + resolution: {integrity: sha512-xRlzazC+QZwr6z4ixEqYHo9fgwhTZ3xNSdljlKfUFGZSdlvt166DljRELFUfFytlYOYvo3vTisA/AFOuOAzFQQ==} + engines: {node: '>=18'} + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -929,6 +946,12 @@ packages: '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@mapbox/node-pre-gyp@1.0.11': resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true @@ -958,6 +981,191 @@ packages: resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} + '@node-llama-cpp/linux-arm64@3.15.1': + resolution: {integrity: sha512-g7JC/WwDyyBSmkIjSvRF2XLW+YA0z2ZVBSAKSv106mIPO4CzC078woTuTaPsykWgIaKcQRyXuW5v5XQMcT1OOA==} + engines: {node: '>=20.0.0'} + cpu: [arm64, x64] + os: [linux] + + '@node-llama-cpp/linux-armv7l@3.15.1': + resolution: {integrity: sha512-MSxR3A0vFSVWbmVSkNqNXQnI45L2Vg7/PRgJukcjChk7YzRxs9L+oQMeycVW3BsQ03mIZ0iORsZ9MNIBEbdS3g==} + engines: {node: '>=20.0.0'} + cpu: [arm, x64] + os: [linux] + + '@node-llama-cpp/linux-x64-cuda-ext@3.15.1': + resolution: {integrity: sha512-toepvLcXjgaQE/QGIThHBD58jbHGBWT1jhblJkCjYBRHfVOO+6n/PmVsJt+yMfu5Z93A2gF8YOvVyZXNXmGo5g==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + + '@node-llama-cpp/linux-x64-cuda@3.15.1': + resolution: {integrity: sha512-kngwoq1KdrqSr/b6+tn5jbtGHI0tZnW5wofKssZy+Il2ge3eN9FowKbXG4FH452g6qSSVoDccAoTvYOxyLyX+w==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + + '@node-llama-cpp/linux-x64-vulkan@3.15.1': + resolution: {integrity: sha512-CMsyQkGKpHKeOH9+ZPxo0hO0usg8jabq5/aM3JwdX9CiuXhXUa3nu3NH4RObiNi596Zwn/zWzlps0HRwcpL8rw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + + '@node-llama-cpp/linux-x64@3.15.1': + resolution: {integrity: sha512-w4SdxJaA9eJLVYWX+Jv48hTP4oO79BJQIFURMi7hXIFXbxyyOov/r6sVaQ1WiL83nVza37U5Qg4L9Gb/KRdNWQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + + '@node-llama-cpp/mac-arm64-metal@3.15.1': + resolution: {integrity: sha512-ePTweqohcy6Gjs1agXWy4FxAw5W4Avr7NeqqiFWJ5ngZ1U3ZXdruUHB8L/vDxyn3FzKvstrFyN7UScbi0pzXrA==} + engines: {node: '>=20.0.0'} + cpu: [arm64, x64] + os: [darwin] + + '@node-llama-cpp/mac-x64@3.15.1': + resolution: {integrity: sha512-NAetSQONxpNXTBnEo7oOkKZ84wO2avBy6V9vV9ntjJLb/07g7Rar8s/jVaicc/rVl6C+8ljZNwqJeynirgAC5w==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] + + '@node-llama-cpp/win-arm64@3.15.1': + resolution: {integrity: sha512-1O9tNSUgvgLL5hqgEuYiz7jRdA3+9yqzNJyPW1jExlQo442OA0eIpHBmeOtvXLwMkY7qv7wE75FdOPR7NVEnvg==} + engines: {node: '>=20.0.0'} + cpu: [arm64, x64] + os: [win32] + + '@node-llama-cpp/win-x64-cuda-ext@3.15.1': + resolution: {integrity: sha512-mO3Tf6D3UlFkjQF5J96ynTkjdF7dac/f5f61cEh6oU4D3hdx+cwnmBWT1gVhDSLboJYzCHtx7U2EKPP6n8HoWA==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@node-llama-cpp/win-x64-cuda@3.15.1': + resolution: {integrity: sha512-swoyx0/dY4ixiu3mEWrIAinx0ffHn9IncELDNREKG+iIXfx6w0OujOMQ6+X+lGj+sjE01yMUP/9fv6GEp2pmBw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@node-llama-cpp/win-x64-vulkan@3.15.1': + resolution: {integrity: sha512-BPBjUEIkFTdcHSsQyblP0v/aPPypi6uqQIq27mo4A49CYjX22JDmk4ncdBLk6cru+UkvwEEe+F2RomjoMt32aQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@node-llama-cpp/win-x64@3.15.1': + resolution: {integrity: sha512-jtoXBa6h+VPsQgefrO7HDjYv4WvxfHtUO30ABwCUDuEgM0e05YYhxMZj1z2Ns47UrquNvd/LUPCyjHKqHUN+5Q==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@octokit/app@16.1.2': + resolution: {integrity: sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==} + engines: {node: '>= 20'} + + '@octokit/auth-app@8.1.2': + resolution: {integrity: sha512-db8VO0PqXxfzI6GdjtgEFHY9tzqUql5xMFXYA12juq8TeTgPAuiiP3zid4h50lwlIP457p5+56PnJOgd2GGBuw==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-app@9.0.3': + resolution: {integrity: sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-device@8.0.3': + resolution: {integrity: sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-user@6.0.2': + resolution: {integrity: sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==} + engines: {node: '>= 20'} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/auth-unauthenticated@7.0.3': + resolution: {integrity: sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.2': + resolution: {integrity: sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + engines: {node: '>= 20'} + + '@octokit/oauth-app@8.0.3': + resolution: {integrity: sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==} + engines: {node: '>= 20'} + + '@octokit/oauth-authorization-url@8.0.0': + resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==} + engines: {node: '>= 20'} + + '@octokit/oauth-methods@6.0.2': + resolution: {integrity: sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/openapi-webhooks-types@12.1.0': + resolution: {integrity: sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==} + + '@octokit/plugin-paginate-graphql@6.0.0': + resolution: {integrity: sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@17.0.0': + resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@8.0.3': + resolution: {integrity: sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=7' + + '@octokit/plugin-throttling@11.0.3': + resolution: {integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': ^7.0.0 + + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.7': + resolution: {integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==} + engines: {node: '>= 20'} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + + '@octokit/webhooks-methods@6.0.0': + resolution: {integrity: sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==} + engines: {node: '>= 20'} + + '@octokit/webhooks@14.2.0': + resolution: {integrity: sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==} + engines: {node: '>= 20'} + '@opentui/core-darwin-arm64@0.1.75': resolution: {integrity: sha512-gGaGZjkFpqcXJk6321JzhRl66pM2VxBlI470L8W4DQUW4S6iDT1R9L7awSzGB4Cn9toUl7DTV8BemaXZYXV4SA==} cpu: [arm64] @@ -1028,6 +1236,58 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@reflink/reflink-darwin-arm64@0.1.19': + resolution: {integrity: sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@reflink/reflink-darwin-x64@0.1.19': + resolution: {integrity: sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + resolution: {integrity: sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@reflink/reflink-linux-arm64-musl@0.1.19': + resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@reflink/reflink-linux-x64-gnu@0.1.19': + resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@reflink/reflink-linux-x64-musl@0.1.19': + resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@reflink/reflink-win32-x64-msvc@0.1.19': + resolution: {integrity: sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@reflink/reflink@0.1.19': + resolution: {integrity: sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==} + engines: {node: '>= 10'} + '@rollup/rollup-android-arm-eabi@4.56.0': resolution: {integrity: sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==} cpu: [arm] @@ -1163,9 +1423,19 @@ packages: resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} + '@tinyhttp/content-disposition@2.2.4': + resolution: {integrity: sha512-5Kc5CM2Ysn3vTTArBs2vESUt0AQiWZA86yc1TI3B+lxXmtEq133C1nxXNOgnzhrivdPZIh3zLj5gDnZjoLL5GA==} + engines: {node: '>=12.17.0'} + '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@types/aws-lambda@8.10.160': + resolution: {integrity: sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==} + + '@types/better-sqlite3@7.6.13': + resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1275,6 +1545,10 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ansi-escapes@6.2.1: + resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} + engines: {node: '>=14.16'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1283,10 +1557,18 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + any-base@1.1.0: resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} @@ -1301,6 +1583,11 @@ packages: engines: {node: '>=10'} deprecated: This package is no longer supported. + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -1311,6 +1598,12 @@ packages: async-lock@1.4.1: resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1319,6 +1612,9 @@ packages: resolution: {integrity: sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==} engines: {node: '>=6.0.0'} + axios@1.13.4: + resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} + b4a@1.7.3: resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} peerDependencies: @@ -1392,6 +1688,16 @@ packages: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + + better-sqlite3@12.6.2: + resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1402,6 +1708,9 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1507,6 +1816,9 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} + chmodrp@1.0.2: + resolution: {integrity: sha512-TdngOlFV1FLTzU0o1w8MB6/BFywhtLC0SzRTGJU7T9lmdjlCWeMRt1iVo0Ki+ldwNk0BqNiKoc8xpLZEQ8mY1w==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -1518,6 +1830,10 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + clean-git-ref@2.0.1: resolution: {integrity: sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==} @@ -1534,6 +1850,15 @@ packages: peerDependencies: typanion: '*' + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + cmake-js@7.4.0: + resolution: {integrity: sha512-Lw0JxEHrmk+qNj1n9W9d4IvkDdYTBn7l2BW6XmtLj7WPpIo2shvxUy+YokfjMxAAOELNonQwX3stkPhM5xSC2Q==} + engines: {node: '>= 14.15.0'} + hasBin: true + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1552,6 +1877,14 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1640,6 +1973,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -1689,6 +2026,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + env-var@7.5.0: + resolution: {integrity: sha512-mKZOzLRN0ETzau2W2QXefbFjo5EF4yWq28OyKb9ICdeNhHJlOE/pHHnz4hdYJ9cNZXcJHo5xN4OT4pzuSHSNvA==} + engines: {node: '>=10'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1704,6 +2045,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild-plugin-solid@0.6.0: resolution: {integrity: sha512-V1FvDALwLDX6K0XNYM9CMRAnMzA0+Ecu55qBUT9q/eAJh1KIDsTMFoOzMSgyHqbOfvrVfO3Mws3z7TW2GVnIZA==} peerDependencies: @@ -1738,6 +2083,9 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} @@ -1778,6 +2126,9 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1800,6 +2151,17 @@ packages: resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} engines: {node: '>=10'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + filename-reserved-regex@3.0.0: + resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + filenamify@6.0.0: + resolution: {integrity: sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==} + engines: {node: '>=16'} + finalhandler@2.1.1: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} @@ -1817,6 +2179,15 @@ packages: flatbuffers@1.12.0: resolution: {integrity: sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -1825,6 +2196,10 @@ packages: resolution: {integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==} engines: {node: '>= 18'} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1836,6 +2211,10 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} + engines: {node: '>=14.14'} + fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -1856,10 +2235,19 @@ packages: engines: {node: '>=10'} deprecated: This package is no longer supported. + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.4.0: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} @@ -1905,6 +2293,9 @@ packages: resolution: {integrity: sha512-QLV1qeYSo5l13mQzWgP/y0LbMr5Plr5fJilgAIwgnwseproEbtNym8xpLsDzeZ6MWXgNE6kdWGBjdh3zT/Qerg==} engines: {node: '>=20'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + guid-typescript@1.0.9: resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} @@ -1963,6 +2354,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + image-q@4.0.0: resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} @@ -1980,6 +2375,11 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + ipull@3.9.3: + resolution: {integrity: sha512-ZMkxaopfwKHwmEuGDYx7giNBdLxbHbRCWcQVA1D2eqE4crUguupfxej6s7UqbidYEwT69dkyumYkY8DPHIxF9g==} + engines: {node: '>=18.0.0'} + hasBin: true + is-arrayish@0.3.4: resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} @@ -1995,6 +2395,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} @@ -2028,6 +2432,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + isomorphic-git@1.36.1: resolution: {integrity: sha512-fC8SRT8MwoaXDK8G4z5biPEbqf2WyEJUb2MJ2ftSd39/UIlsnoZxLGux+lae0poLZO4AEcx6aUVOh5bV+P8zFA==} engines: {node: '>=14.17'} @@ -2076,9 +2484,18 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + keyv@5.6.0: resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + lifecycle-utils@2.1.0: + resolution: {integrity: sha512-AnrXnE2/OF9PHCyFg0RSqsnQTzV991XaZA/buhFDoc58xU7rhSCDgCz/09Lqpsn4MpoPHt7TRAXV1kWZypFVsA==} + + lifecycle-utils@3.0.1: + resolution: {integrity: sha512-Qt/Jl5dsNIsyCAZsHB6x3mbwHFn0HJbdmvF49sVX/bHgX2cW7+G+U+I67Zw+TPM1Sr21Gb2nfJMd2g6iUcI1EQ==} + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -2168,10 +2585,17 @@ packages: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + log-symbols@6.0.0: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} @@ -2181,6 +2605,10 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lowdb@7.0.1: + resolution: {integrity: sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==} + engines: {node: '>=18'} + lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2211,6 +2639,9 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} + memory-stream@1.0.0: + resolution: {integrity: sha512-Wm13VcsPIMdG96dzILfij09PvuS3APtcKNh7M28FsCA/w6+1mjR7hhPmfFNoilX9xU7wTdhsH5lJAm6XNzdtww==} + merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -2218,10 +2649,18 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mime-types@3.0.2: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} @@ -2306,6 +2745,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + engines: {node: ^18 || >=20} + hasBin: true + napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -2320,6 +2764,13 @@ packages: node-addon-api@6.1.0: resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + node-addon-api@8.5.0: + resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} + engines: {node: ^18 || ^20 || >= 21} + + node-api-headers@1.8.0: + resolution: {integrity: sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2329,6 +2780,16 @@ packages: encoding: optional: true + node-llama-cpp@3.15.1: + resolution: {integrity: sha512-/fBNkuLGR2Q8xj2eeV12KXKZ9vCS2+o6aP11lW40pB9H6f0B3wOALi/liFrjhHukAoiH6C9wFTPzv6039+5DRA==} + engines: {node: '>=20.0.0'} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + peerDependenciesMeta: + typescript: + optional: true + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2349,6 +2810,11 @@ packages: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2357,6 +2823,10 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + octokit@5.0.5: + resolution: {integrity: sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==} + engines: {node: '>= 20'} + omggif@1.0.10: resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} @@ -2424,6 +2894,14 @@ packages: parse-bmfont-xml@1.1.6: resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} + parse-ms@3.0.0: + resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} + engines: {node: '>=12'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -2552,14 +3030,29 @@ packages: engines: {node: '>=10'} hasBin: true + pretty-bytes@6.1.1: + resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} + engines: {node: ^14.13.1 || >=16.0.0} + pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-ms@8.0.0: + resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} + engines: {node: '>=14.16'} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + protobufjs@6.11.4: resolution: {integrity: sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==} hasBin: true @@ -2568,6 +3061,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -2610,6 +3106,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -2637,6 +3137,14 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -2750,6 +3258,9 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-git@3.30.0: + resolution: {integrity: sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==} + simple-swizzle@0.2.4: resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} @@ -2760,6 +3271,13 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + sleep-promise@9.1.0: + resolution: {integrity: sha512-UHYzVpz9Xn8b+jikYSD6bqvf754xL2uBUzDFwiU6NcdZeifPr6UfgU43xpkPu67VMS88+TI2PSI7Eohgqf2fKA==} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + solid-js@1.9.11: resolution: {integrity: sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q==} @@ -2771,6 +3289,29 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + sqlite-vec-darwin-arm64@0.1.6: + resolution: {integrity: sha512-5duw/xhM3xE6BCQd//eAkyHgBp9FIwK6veldRhPG96dT6Zpjov3bG02RuE7JAQj0SVJYRW8bJwZ4LxqW0+Q7Dw==} + cpu: [arm64] + os: [darwin] + + sqlite-vec-darwin-x64@0.1.6: + resolution: {integrity: sha512-MFkKjNfJ5pamFHhyTsrqdxALrjuvpSEZdu6Q/0vMxFa6sr5YlfabeT5xGqEbuH0iobsT1Hca5EZxLhLy0jHYkw==} + cpu: [x64] + os: [darwin] + + sqlite-vec-linux-x64@0.1.6: + resolution: {integrity: sha512-411tWPswywIzdkp+zsAUav4A03f0FjnNfpZFlOw8fBebFR74RSFkwM8Xryf18gLHiYAXUBI4mjY9+/xjwBjKpg==} + cpu: [x64] + os: [linux] + + sqlite-vec-windows-x64@0.1.6: + resolution: {integrity: sha512-Dy9/KlKJDrjuQ/RRkBqGkMZuSh5bTJDMMOFZft9VJZaXzpYxb5alpgdvD4bbKegpDdfPi2BT4+PBivsNJSlMoQ==} + cpu: [x64] + os: [win32] + + sqlite-vec@0.1.6: + resolution: {integrity: sha512-hQZU700TU2vWPXZYDULODjKXeMio6rKX7UpPN7Tq9qjPW671IEgURGrcC5LDBMl0q9rBvAuzmcmJmImMqVibYQ==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -2789,6 +3330,14 @@ packages: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} + stdout-update@4.0.1: + resolution: {integrity: sha512-wiS21Jthlvl1to+oorePvcyrIkiG/6M3D3VTmDUlJm7Cy6SbFhKkAvX+YBuHLxck/tO3mrdpC/cNesigQc3+UQ==} + engines: {node: '>=16.0.0'} + + steno@4.0.2: + resolution: {integrity: sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==} + engines: {node: '>=18'} + streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -2903,6 +3452,10 @@ packages: resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} engines: {node: '>= 0.4'} + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -3007,6 +3560,16 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + universal-github-app-jwt@2.2.2: + resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -3017,12 +3580,19 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + utif2@4.1.0: resolution: {integrity: sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + validate-npm-package-name@6.0.2: + resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==} + engines: {node: ^18.17.0 || >=20.5.0} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3141,6 +3711,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -3149,6 +3724,10 @@ packages: wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -3175,7 +3754,11 @@ packages: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} - yallist@3.1.1: + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yallist@4.0.0: @@ -3186,10 +3769,22 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@1.2.2: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} @@ -3565,6 +4160,9 @@ snapshots: '@huggingface/jinja@0.2.2': {} + '@huggingface/jinja@0.5.5': + optional: true + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -3780,6 +4378,16 @@ snapshots: '@keyv/serialize@1.1.1': {} + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + '@kwsites/promise-deferred@1.1.1': + optional: true + '@mapbox/node-pre-gyp@1.0.11': dependencies: detect-libc: 2.1.2 @@ -3827,6 +4435,217 @@ snapshots: '@noble/hashes@1.8.0': {} + '@node-llama-cpp/linux-arm64@3.15.1': + optional: true + + '@node-llama-cpp/linux-armv7l@3.15.1': + optional: true + + '@node-llama-cpp/linux-x64-cuda-ext@3.15.1': + optional: true + + '@node-llama-cpp/linux-x64-cuda@3.15.1': + optional: true + + '@node-llama-cpp/linux-x64-vulkan@3.15.1': + optional: true + + '@node-llama-cpp/linux-x64@3.15.1': + optional: true + + '@node-llama-cpp/mac-arm64-metal@3.15.1': + optional: true + + '@node-llama-cpp/mac-x64@3.15.1': + optional: true + + '@node-llama-cpp/win-arm64@3.15.1': + optional: true + + '@node-llama-cpp/win-x64-cuda-ext@3.15.1': + optional: true + + '@node-llama-cpp/win-x64-cuda@3.15.1': + optional: true + + '@node-llama-cpp/win-x64-vulkan@3.15.1': + optional: true + + '@node-llama-cpp/win-x64@3.15.1': + optional: true + + '@octokit/app@16.1.2': + dependencies: + '@octokit/auth-app': 8.1.2 + '@octokit/auth-unauthenticated': 7.0.3 + '@octokit/core': 7.0.6 + '@octokit/oauth-app': 8.0.3 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/types': 16.0.0 + '@octokit/webhooks': 14.2.0 + optional: true + + '@octokit/auth-app@8.1.2': + dependencies: + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.7 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + toad-cache: 3.7.0 + universal-github-app-jwt: 2.2.2 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/auth-oauth-app@9.0.3': + dependencies: + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/auth-oauth-device@8.0.3': + dependencies: + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/auth-oauth-user@6.0.2': + dependencies: + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/auth-token@6.0.0': + optional: true + + '@octokit/auth-unauthenticated@7.0.3': + dependencies: + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + optional: true + + '@octokit/core@7.0.6': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.7 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/endpoint@11.0.2': + dependencies: + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/graphql@9.0.3': + dependencies: + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/oauth-app@8.0.3': + dependencies: + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/auth-unauthenticated': 7.0.3 + '@octokit/core': 7.0.6 + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/oauth-methods': 6.0.2 + '@types/aws-lambda': 8.10.160 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/oauth-authorization-url@8.0.0': + optional: true + + '@octokit/oauth-methods@6.0.2': + dependencies: + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/request': 10.0.7 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + optional: true + + '@octokit/openapi-types@27.0.0': + optional: true + + '@octokit/openapi-webhooks-types@12.1.0': + optional: true + + '@octokit/plugin-paginate-graphql@6.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + optional: true + + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + optional: true + + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + optional: true + + '@octokit/plugin-retry@8.0.3(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + bottleneck: 2.19.5 + optional: true + + '@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + bottleneck: 2.19.5 + optional: true + + '@octokit/request-error@7.1.0': + dependencies: + '@octokit/types': 16.0.0 + optional: true + + '@octokit/request@10.0.7': + dependencies: + '@octokit/endpoint': 11.0.2 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + fast-content-type-parse: 3.0.0 + universal-user-agent: 7.0.3 + optional: true + + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + optional: true + + '@octokit/webhooks-methods@6.0.0': + optional: true + + '@octokit/webhooks@14.2.0': + dependencies: + '@octokit/openapi-webhooks-types': 12.1.0 + '@octokit/request-error': 7.1.0 + '@octokit/webhooks-methods': 6.0.0 + optional: true + '@opentui/core-darwin-arm64@0.1.75': optional: true @@ -3906,6 +4725,42 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@reflink/reflink-darwin-arm64@0.1.19': + optional: true + + '@reflink/reflink-darwin-x64@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-musl@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-musl@0.1.19': + optional: true + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + optional: true + + '@reflink/reflink-win32-x64-msvc@0.1.19': + optional: true + + '@reflink/reflink@0.1.19': + optionalDependencies: + '@reflink/reflink-darwin-arm64': 0.1.19 + '@reflink/reflink-darwin-x64': 0.1.19 + '@reflink/reflink-linux-arm64-gnu': 0.1.19 + '@reflink/reflink-linux-arm64-musl': 0.1.19 + '@reflink/reflink-linux-x64-gnu': 0.1.19 + '@reflink/reflink-linux-x64-musl': 0.1.19 + '@reflink/reflink-win32-arm64-msvc': 0.1.19 + '@reflink/reflink-win32-x64-msvc': 0.1.19 + optional: true + '@rollup/rollup-android-arm-eabi@4.56.0': optional: true @@ -3987,8 +4842,18 @@ snapshots: '@sindresorhus/is@7.2.0': {} + '@tinyhttp/content-disposition@2.2.4': + optional: true + '@tokenizer/token@0.3.0': {} + '@types/aws-lambda@8.10.160': + optional: true + + '@types/better-sqlite3@7.6.13': + dependencies: + '@types/node': 22.19.7 + '@types/estree@1.0.8': {} '@types/http-cache-semantics@4.0.4': {} @@ -4130,12 +4995,23 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-escapes@6.2.1: + optional: true + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + optional: true + ansi-styles@5.2.0: {} + ansi-styles@6.2.3: + optional: true + any-base@1.1.0: {} any-promise@1.3.0: {} @@ -4147,18 +5023,41 @@ snapshots: delegates: 1.0.0 readable-stream: 3.6.2 + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + optional: true + assertion-error@1.1.0: {} assertion-error@2.0.1: {} async-lock@1.4.1: {} + async-retry@1.3.3: + dependencies: + retry: 0.13.1 + optional: true + + asynckit@0.4.0: + optional: true + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 await-to-js@3.0.0: {} + axios@1.13.4(debug@4.4.3): + dependencies: + follow-redirects: 1.15.11(debug@4.4.3) + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + optional: true + b4a@1.7.3: {} babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.28.0): @@ -4228,6 +5127,20 @@ snapshots: baseline-browser-mapping@2.9.19: {} + before-after-hook@4.0.0: + optional: true + + better-sqlite3@12.6.2: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + optional: true + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + optional: true + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -4250,6 +5163,9 @@ snapshots: transitivePeerDependencies: - supports-color + bottleneck@2.19.5: + optional: true + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -4371,6 +5287,9 @@ snapshots: check-error@2.1.3: {} + chmodrp@1.0.2: + optional: true + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -4379,6 +5298,9 @@ snapshots: chownr@2.0.0: {} + ci-info@4.4.0: + optional: true + clean-git-ref@2.0.1: {} cli-cursor@5.0.0: @@ -4391,6 +5313,31 @@ snapshots: dependencies: typanion: 3.14.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + optional: true + + cmake-js@7.4.0: + dependencies: + axios: 1.13.4(debug@4.4.3) + debug: 4.4.3 + fs-extra: 11.3.3 + memory-stream: 1.0.0 + node-api-headers: 1.8.0 + npmlog: 6.0.2 + rc: 1.2.8 + semver: 7.7.3 + tar: 6.2.1 + url-join: 4.0.1 + which: 2.0.2 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + optional: true + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -4409,6 +5356,14 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + optional: true + + commander@10.0.1: + optional: true + commander@4.1.1: {} concat-map@0.0.1: {} @@ -4477,6 +5432,9 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + delayed-stream@1.0.0: + optional: true + delegates@1.0.0: {} depd@2.0.0: {} @@ -4511,6 +5469,9 @@ snapshots: entities@6.0.1: {} + env-var@7.5.0: + optional: true + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -4521,6 +5482,14 @@ snapshots: dependencies: es-errors: 1.3.0 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + optional: true + esbuild-plugin-solid@0.6.0(esbuild@0.27.2)(solid-js@1.9.11): dependencies: '@babel/core': 7.28.0 @@ -4598,6 +5567,9 @@ snapshots: event-target-shim@5.0.1: {} + eventemitter3@5.0.4: + optional: true + events-universal@1.0.1: dependencies: bare-events: 2.8.2 @@ -4667,6 +5639,9 @@ snapshots: transitivePeerDependencies: - supports-color + fast-content-type-parse@3.0.0: + optional: true + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -4683,6 +5658,17 @@ snapshots: strtok3: 6.3.0 token-types: 4.2.1 + file-uri-to-path@1.0.0: + optional: true + + filename-reserved-regex@3.0.0: + optional: true + + filenamify@6.0.0: + dependencies: + filename-reserved-regex: 3.0.0 + optional: true + finalhandler@2.1.1: dependencies: debug: 4.4.3 @@ -4710,18 +5696,39 @@ snapshots: flatbuffers@1.12.0: {} + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 + optional: true + for-each@0.3.5: dependencies: is-callable: 1.2.7 form-data-encoder@4.1.0: {} + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + optional: true + forwarded@0.2.0: {} fresh@2.0.0: {} fs-constants@1.0.0: {} + fs-extra@11.3.3: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + optional: true + fs-minipass@2.1.0: dependencies: minipass: 3.3.6 @@ -4745,8 +5752,23 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 + gauge@4.0.4: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + optional: true + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: + optional: true + get-east-asian-width@1.4.0: {} get-func-name@2.0.2: {} @@ -4816,6 +5838,9 @@ snapshots: responselike: 4.0.2 type-fest: 4.41.0 + graceful-fs@4.2.11: + optional: true + guid-typescript@1.0.9: {} has-property-descriptors@1.0.2: @@ -4870,6 +5895,9 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: + optional: true + image-q@4.0.0: dependencies: '@types/node': 16.9.1 @@ -4885,6 +5913,31 @@ snapshots: ipaddr.js@1.9.1: {} + ipull@3.9.3: + dependencies: + '@tinyhttp/content-disposition': 2.2.4 + async-retry: 1.3.3 + chalk: 5.6.2 + ci-info: 4.4.0 + cli-spinners: 2.9.2 + commander: 10.0.1 + eventemitter3: 5.0.4 + filenamify: 6.0.0 + fs-extra: 11.3.3 + is-unicode-supported: 2.1.0 + lifecycle-utils: 2.1.0 + lodash.debounce: 4.0.8 + lowdb: 7.0.1 + pretty-bytes: 6.1.1 + pretty-ms: 8.0.0 + sleep-promise: 9.1.0 + slice-ansi: 7.1.2 + stdout-update: 4.0.1 + strip-ansi: 7.1.2 + optionalDependencies: + '@reflink/reflink': 0.1.19 + optional: true + is-arrayish@0.3.4: {} is-callable@1.2.7: {} @@ -4895,6 +5948,11 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + optional: true + is-interactive@2.0.0: {} is-promise@4.0.0: {} @@ -4915,6 +5973,9 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: + optional: true + isomorphic-git@1.36.1: dependencies: async-lock: 1.4.1 @@ -4982,10 +6043,23 @@ snapshots: json5@2.2.3: {} + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + optional: true + keyv@5.6.0: dependencies: '@keyv/serialize': 1.1.1 + lifecycle-utils@2.1.0: + optional: true + + lifecycle-utils@3.0.1: + optional: true + lightningcss-android-arm64@1.30.2: optional: true @@ -5052,11 +6126,20 @@ snapshots: p-locate: 3.0.0 path-exists: 3.0.0 + lodash.debounce@4.0.8: + optional: true + log-symbols@6.0.0: dependencies: chalk: 5.6.2 is-unicode-supported: 1.3.0 + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.1.2 + optional: true + long@4.0.0: {} loupe@2.3.7: @@ -5065,6 +6148,11 @@ snapshots: loupe@3.2.1: {} + lowdb@7.0.1: + dependencies: + steno: 4.0.2 + optional: true + lowercase-keys@3.0.0: {} lru-cache@10.4.3: {} @@ -5087,12 +6175,25 @@ snapshots: media-typer@1.1.0: {} + memory-stream@1.0.0: + dependencies: + readable-stream: 3.6.2 + optional: true + merge-descriptors@2.0.0: {} merge-stream@2.0.0: {} + mime-db@1.52.0: + optional: true + mime-db@1.54.0: {} + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + optional: true + mime-types@3.0.2: dependencies: mime-db: 1.54.0 @@ -5163,6 +6264,9 @@ snapshots: nanoid@3.3.11: {} + nanoid@5.1.6: + optional: true + napi-build-utils@2.0.0: {} negotiator@1.0.0: {} @@ -5173,10 +6277,66 @@ snapshots: node-addon-api@6.1.0: {} + node-addon-api@8.5.0: + optional: true + + node-api-headers@1.8.0: + optional: true + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-llama-cpp@3.15.1(typescript@5.9.3): + dependencies: + '@huggingface/jinja': 0.5.5 + async-retry: 1.3.3 + bytes: 3.1.2 + chalk: 5.6.2 + chmodrp: 1.0.2 + cmake-js: 7.4.0 + cross-spawn: 7.0.6 + env-var: 7.5.0 + filenamify: 6.0.0 + fs-extra: 11.3.3 + ignore: 7.0.5 + ipull: 3.9.3 + is-unicode-supported: 2.1.0 + lifecycle-utils: 3.0.1 + log-symbols: 7.0.1 + nanoid: 5.1.6 + node-addon-api: 8.5.0 + octokit: 5.0.5 + ora: 8.2.0 + pretty-ms: 9.3.0 + proper-lockfile: 4.1.2 + semver: 7.7.3 + simple-git: 3.30.0 + slice-ansi: 7.1.2 + stdout-update: 4.0.1 + strip-ansi: 7.1.2 + validate-npm-package-name: 6.0.2 + which: 5.0.0 + yargs: 17.7.2 + optionalDependencies: + '@node-llama-cpp/linux-arm64': 3.15.1 + '@node-llama-cpp/linux-armv7l': 3.15.1 + '@node-llama-cpp/linux-x64': 3.15.1 + '@node-llama-cpp/linux-x64-cuda': 3.15.1 + '@node-llama-cpp/linux-x64-cuda-ext': 3.15.1 + '@node-llama-cpp/linux-x64-vulkan': 3.15.1 + '@node-llama-cpp/mac-arm64-metal': 3.15.1 + '@node-llama-cpp/mac-x64': 3.15.1 + '@node-llama-cpp/win-arm64': 3.15.1 + '@node-llama-cpp/win-x64': 3.15.1 + '@node-llama-cpp/win-x64-cuda': 3.15.1 + '@node-llama-cpp/win-x64-cuda-ext': 3.15.1 + '@node-llama-cpp/win-x64-vulkan': 3.15.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + optional: true + node-releases@2.0.27: {} nopt@5.0.0: @@ -5196,10 +6356,33 @@ snapshots: gauge: 3.0.2 set-blocking: 2.0.0 + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + optional: true + object-assign@4.1.1: {} object-inspect@1.13.4: {} + octokit@5.0.5: + dependencies: + '@octokit/app': 16.1.2 + '@octokit/core': 7.0.6 + '@octokit/oauth-app': 8.0.3 + '@octokit/plugin-paginate-graphql': 6.0.0(@octokit/core@7.0.6) + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + '@octokit/plugin-retry': 8.0.3(@octokit/core@7.0.6) + '@octokit/plugin-throttling': 11.0.3(@octokit/core@7.0.6) + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + '@octokit/webhooks': 14.2.0 + optional: true + omggif@1.0.10: {} on-finished@2.4.1: @@ -5277,6 +6460,12 @@ snapshots: xml-parse-from-string: 1.0.1 xml2js: 0.5.0 + parse-ms@3.0.0: + optional: true + + parse-ms@4.0.0: + optional: true + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -5376,14 +6565,34 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 + pretty-bytes@6.1.1: + optional: true + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.3.1 + pretty-ms@8.0.0: + dependencies: + parse-ms: 3.0.0 + optional: true + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + optional: true + process@0.11.10: {} + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + optional: true + protobufjs@6.11.4: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -5405,6 +6614,9 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: + optional: true + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -5454,6 +6666,9 @@ snapshots: readdirp@4.1.2: {} + require-directory@2.1.1: + optional: true + require-from-string@2.0.2: {} reselect@4.1.8: {} @@ -5477,6 +6692,12 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 + retry@0.12.0: + optional: true + + retry@0.13.1: + optional: true + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -5647,6 +6868,15 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + simple-git@3.30.0: + dependencies: + '@kwsites/file-exists': 1.1.1 + '@kwsites/promise-deferred': 1.1.1 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + simple-swizzle@0.2.4: dependencies: is-arrayish: 0.3.4 @@ -5655,6 +6885,15 @@ snapshots: sisteransi@1.0.5: {} + sleep-promise@9.1.0: + optional: true + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + optional: true + solid-js@1.9.11: dependencies: csstype: 3.2.3 @@ -5665,6 +6904,26 @@ snapshots: source-map@0.7.6: {} + sqlite-vec-darwin-arm64@0.1.6: + optional: true + + sqlite-vec-darwin-x64@0.1.6: + optional: true + + sqlite-vec-linux-x64@0.1.6: + optional: true + + sqlite-vec-windows-x64@0.1.6: + optional: true + + sqlite-vec@0.1.6: + optionalDependencies: + sqlite-vec-darwin-arm64: 0.1.6 + sqlite-vec-darwin-x64: 0.1.6 + sqlite-vec-linux-x64: 0.1.6 + sqlite-vec-windows-x64: 0.1.6 + optional: true + stackback@0.0.2: {} stage-js@1.0.0-alpha.17: @@ -5676,6 +6935,17 @@ snapshots: stdin-discarder@0.2.2: {} + stdout-update@4.0.1: + dependencies: + ansi-escapes: 6.2.1 + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + optional: true + + steno@4.0.2: + optional: true + streamx@2.23.0: dependencies: events-universal: 1.0.1 @@ -5823,6 +7093,9 @@ snapshots: safe-buffer: 5.2.1 typed-array-buffer: 1.0.3 + toad-cache@3.7.0: + optional: true + toidentifier@1.0.1: {} token-types@4.2.1: @@ -5919,6 +7192,15 @@ snapshots: undici-types@6.21.0: {} + universal-github-app-jwt@2.2.2: + optional: true + + universal-user-agent@7.0.3: + optional: true + + universalify@2.0.1: + optional: true + unpipe@1.0.0: {} update-browserslist-db@1.2.3(browserslist@4.28.1): @@ -5927,12 +7209,18 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + url-join@4.0.1: + optional: true + utif2@4.1.0: dependencies: pako: 1.0.11 util-deprecate@1.0.2: {} + validate-npm-package-name@6.0.2: + optional: true + vary@1.1.2: {} vite-node@1.6.1(@types/node@22.19.7)(lightningcss@1.30.2): @@ -6073,6 +7361,11 @@ snapshots: dependencies: isexe: 2.0.0 + which@5.0.0: + dependencies: + isexe: 3.1.1 + optional: true + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -6082,6 +7375,13 @@ snapshots: dependencies: string-width: 4.2.3 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + optional: true + wrappy@1.0.2: {} ws@8.19.0: {} @@ -6095,14 +7395,34 @@ snapshots: xmlbuilder@11.0.1: {} + y18n@5.0.8: + optional: true + yallist@3.1.1: {} yallist@4.0.0: {} yaml@2.8.2: {} + yargs-parser@21.1.1: + optional: true + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + optional: true + yocto-queue@1.2.2: {} + yoctocolors@2.1.2: + optional: true + yoga-layout@3.2.1: {} zod-to-json-schema@3.25.1(zod@3.25.76):