kirograph 0.28.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/architecture/index.js +4 -1
- package/dist/architecture/index.js.map +2 -2
- package/dist/architecture/manifest/index.js +13 -2
- package/dist/architecture/manifest/index.js.map +2 -2
- package/dist/architecture/manifest/python.js +2 -2
- package/dist/architecture/manifest/python.js.map +2 -2
- package/dist/bin/commands/help.js +3 -1
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/commands/install.js +2 -1
- package/dist/bin/commands/install.js.map +2 -2
- package/dist/bin/commands/memory.js +18 -8
- package/dist/bin/commands/memory.js.map +2 -2
- package/dist/bin/commands/reachability.js +7 -2
- package/dist/bin/commands/reachability.js.map +2 -2
- package/dist/bin/commands/status.js +2 -1
- package/dist/bin/commands/status.js.map +2 -2
- package/dist/bin/commands/uninit.js +2 -1
- package/dist/bin/commands/uninit.js.map +2 -2
- package/dist/bin/commands/vulns.js +26 -6
- package/dist/bin/commands/vulns.js.map +2 -2
- package/dist/bin/commands/wiki.js +56 -0
- package/dist/bin/commands/wiki.js.map +2 -2
- package/dist/bin/installer/common.js +7 -1
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/detect.js +10 -0
- package/dist/bin/installer/detect.js.map +2 -2
- package/dist/bin/installer/hooks.js +13 -8
- package/dist/bin/installer/hooks.js.map +2 -2
- package/dist/bin/installer/steering.js +9 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/antigravity.js +1 -1
- package/dist/bin/installer/targets/antigravity.js.map +2 -2
- package/dist/bin/installer/targets/claude.js +1 -1
- package/dist/bin/installer/targets/claude.js.map +2 -2
- package/dist/bin/installer/targets/codex.js +1 -1
- package/dist/bin/installer/targets/codex.js.map +2 -2
- package/dist/bin/installer/targets/commandcode.js +84 -0
- package/dist/bin/installer/targets/commandcode.js.map +7 -0
- package/dist/bin/installer/targets/copilot.js +1 -1
- package/dist/bin/installer/targets/copilot.js.map +2 -2
- package/dist/bin/installer/targets/cursor.js +2 -2
- package/dist/bin/installer/targets/cursor.js.map +2 -2
- package/dist/bin/installer/targets/devin.js +1 -1
- package/dist/bin/installer/targets/devin.js.map +2 -2
- package/dist/bin/installer/targets/gemini-cli.js +1 -1
- package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
- package/dist/bin/installer/targets/index.js +10 -0
- package/dist/bin/installer/targets/index.js.map +2 -2
- package/dist/bin/installer/targets/opencode.js +1 -1
- package/dist/bin/installer/targets/opencode.js.map +2 -2
- package/dist/bin/installer/targets/windsurf.js +1 -1
- package/dist/bin/installer/targets/windsurf.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/bin/kirograph.js.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +18 -6
- package/dist/core/pipeline.js.map +2 -2
- package/dist/extraction/extractor.js +88 -26
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/grammars.js +38 -3
- package/dist/extraction/grammars.js.map +2 -2
- package/dist/extraction/notebook.js +15 -7
- package/dist/extraction/notebook.js.map +2 -2
- package/dist/extraction/wasm/tree-sitter-bash.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-csharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-css.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elisp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elixir.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elm.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embeddedtemplate.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-html.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-json.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-objc.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ocaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ql.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-solidity.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-toml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vue.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-yaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-zig.wasm +0 -0
- package/dist/frameworks/index.js +68 -7
- package/dist/frameworks/index.js.map +2 -2
- package/dist/jev/client.js +99 -0
- package/dist/jev/client.js.map +7 -0
- package/dist/jev/types.js +17 -0
- package/dist/jev/types.js.map +7 -0
- package/dist/mcp/handlers/memory.js +14 -8
- package/dist/mcp/handlers/memory.js.map +2 -2
- package/dist/mcp/handlers/security.js +5 -3
- package/dist/mcp/handlers/security.js.map +2 -2
- package/dist/mcp/tools.js +1 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/memory/index.js +13 -1
- package/dist/memory/index.js.map +3 -3
- package/dist/memory/relation-jev.js +59 -0
- package/dist/memory/relation-jev.js.map +7 -0
- package/dist/memory/schema.js +57 -0
- package/dist/memory/schema.js.map +7 -0
- package/dist/security/auth-detection-jev.js +47 -0
- package/dist/security/auth-detection-jev.js.map +7 -0
- package/dist/security/manifest/adapter.js +34 -5
- package/dist/security/manifest/adapter.js.map +3 -3
- package/dist/security/manifest/parser.js +2 -2
- package/dist/security/manifest/parser.js.map +2 -2
- package/dist/security/manifest/plugins/cargo.js +23 -0
- package/dist/security/manifest/plugins/cargo.js.map +2 -2
- package/dist/security/manifest/plugins/composer.js +12 -0
- package/dist/security/manifest/plugins/composer.js.map +2 -2
- package/dist/security/manifest/plugins/gradle.js +12 -0
- package/dist/security/manifest/plugins/gradle.js.map +2 -2
- package/dist/security/manifest/plugins/hex.js +12 -0
- package/dist/security/manifest/plugins/hex.js.map +2 -2
- package/dist/security/manifest/plugins/maven.js +68 -1
- package/dist/security/manifest/plugins/maven.js.map +2 -2
- package/dist/security/manifest/plugins/npm.js +86 -0
- package/dist/security/manifest/plugins/npm.js.map +2 -2
- package/dist/security/manifest/plugins/nuget.js +13 -0
- package/dist/security/manifest/plugins/nuget.js.map +2 -2
- package/dist/security/manifest/plugins/pubspec.js +12 -0
- package/dist/security/manifest/plugins/pubspec.js.map +2 -2
- package/dist/security/manifest/plugins/pyproject.js +12 -0
- package/dist/security/manifest/plugins/pyproject.js.map +2 -2
- package/dist/security/manifest/plugins/rubygems.js +12 -0
- package/dist/security/manifest/plugins/rubygems.js.map +2 -2
- package/dist/security/pipeline.js +5 -2
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +115 -50
- package/dist/security/reachability.js.map +2 -2
- package/dist/security/types.js.map +1 -1
- package/dist/security/vuln/client.js +45 -1
- package/dist/security/vuln/client.js.map +2 -2
- package/dist/security/vuln/epss-client.js +1 -1
- package/dist/security/vuln/epss-client.js.map +1 -1
- package/dist/security/vuln/osv-adapter.js +144 -32
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- package/dist/sync/index.js +9 -2
- package/dist/sync/index.js.map +2 -2
- package/dist/vectors/libsql-index.js +287 -0
- package/dist/vectors/libsql-index.js.map +7 -0
- package/dist/vectors/turso-index.js +307 -0
- package/dist/vectors/turso-index.js.map +7 -0
- package/dist/wiki/contradiction-jev.js +49 -0
- package/dist/wiki/contradiction-jev.js.map +7 -0
- package/dist/wiki/index.js +13 -0
- package/dist/wiki/index.js.map +2 -2
- package/dist/wiki/ingest.js +2 -0
- package/dist/wiki/ingest.js.map +2 -2
- package/dist/wiki/links.js +53 -0
- package/dist/wiki/links.js.map +7 -0
- package/dist/wiki/lint.js +3 -6
- package/dist/wiki/lint.js.map +2 -2
- package/dist/wiki/rename.js +103 -0
- package/dist/wiki/rename.js.map +7 -0
- package/package.json +3 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/security/vuln/osv-adapter.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * OSV API Adapter\n *\n * Queries the OSV (Open Source Vulnerabilities) database for known CVEs\n * affecting project dependencies. Implements the VulnDatabaseAdapter interface.\n */\n\nimport { CVERecord, VersionRange } from '../types';\nimport type { VulnDatabaseAdapter, BatchQuery } from './types';\nimport { VulnDatabaseError } from '../errors';\nimport { logWarn, logError } from '../../errors';\n\nexport type { VulnDatabaseAdapter, BatchQuery };\n\n// \u2500\u2500 Ecosystem Mapping \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst ECOSYSTEM_MAP: Record<string, string> = {\n npm: 'npm',\n maven: 'Maven',\n go: 'Go',\n pypi: 'PyPI',\n cargo: 'crates.io',\n pyproject: 'PyPI', // pyproject.toml (Poetry/Hatch/PDM/PEP 621)\n nuget: 'NuGet',\n gradle: 'Maven', // Gradle projects use Maven Central\n rubygems: 'RubyGems',\n composer: 'Packagist',\n swift: 'SwiftURL',\n pub: 'Pub',\n hex: 'Hex',\n};\n\n// \u2500\u2500 OSV Request/Response Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface OsvSeverity {\n type: string;\n score: string;\n}\n\ninterface OsvAffectedRange {\n type: string;\n events: Array<{ introduced?: string; fixed?: string; last_affected?: string }>;\n}\n\ninterface OsvAffected {\n package?: { name?: string; ecosystem?: string };\n ranges?: OsvAffectedRange[];\n versions?: string[];\n}\n\ninterface OsvVulnerability {\n id: string;\n aliases?: string[];\n summary?: string;\n details?: string;\n severity?: OsvSeverity[];\n affected?: OsvAffected[];\n}\n\ninterface OsvQueryResponse {\n vulns?: OsvVulnerability[];\n}\n\ninterface OsvBatchQueryItem {\n package: { name: string; ecosystem: string };\n version: string;\n}\n\ninterface OsvBatchResponse {\n results: Array<{ vulns?: OsvVulnerability[] }>;\n}\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst OSV_API_URL = 'https://api.osv.dev/v1/query';\nconst OSV_BATCH_API_URL = 'https://api.osv.dev/v1/querybatch';\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst OSV_BATCH_MAX_QUERIES = 1000;\nconst MAX_SUMMARY_LENGTH = 500;\n\n// \u2500\u2500 OsvAdapter Implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class OsvAdapter implements VulnDatabaseAdapter {\n public readonly name = 'OSV';\n\n private readonly apiUrl: string;\n private readonly batchApiUrl: string;\n private readonly timeoutMs: number;\n\n constructor(options?: { apiUrl?: string; batchApiUrl?: string; timeoutMs?: number }) {\n this.apiUrl = options?.apiUrl ?? OSV_API_URL;\n this.batchApiUrl = options?.batchApiUrl ?? OSV_BATCH_API_URL;\n this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Batch query up to OSV_BATCH_MAX_QUERIES packages in a single HTTP request.\n * Results are returned in the same order as the input queries.\n * Queries for unsupported ecosystems produce empty results without an HTTP call.\n */\n async queryBatch(\n queries: BatchQuery[],\n signal?: AbortSignal,\n ): Promise<Array<CVERecord[]>> {\n if (queries.length === 0) return [];\n\n // Map each query to its OSV ecosystem \u2014 track indices of unsupported ones\n const osvQueries: OsvBatchQueryItem[] = [];\n const indexMap: Array<number | null> = []; // index into osvQueries, or null if unsupported\n\n for (const q of queries) {\n const osvEcosystem = ECOSYSTEM_MAP[q.ecosystem.toLowerCase()];\n if (!osvEcosystem) {\n logWarn(`OSV adapter: unsupported ecosystem \"${q.ecosystem}\", skipping batch entry`);\n indexMap.push(null);\n } else {\n indexMap.push(osvQueries.length);\n osvQueries.push({\n package: { name: q.packageName, ecosystem: osvEcosystem },\n version: q.version,\n });\n }\n }\n\n if (osvQueries.length === 0) {\n return queries.map(() => []);\n }\n\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);\n const combinedSignal = signal\n ? combineAbortSignals(signal, timeoutController.signal)\n : timeoutController.signal;\n\n let batchResults: Array<{ vulns?: OsvVulnerability[] }>;\n\n try {\n const response = await fetch(this.batchApiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ queries: osvQueries }),\n signal: combinedSignal,\n });\n\n if (!response.ok) {\n throw new VulnDatabaseError(\n `OSV batch API returned HTTP ${response.status}: ${response.statusText}`,\n 'OSV',\n response.status,\n );\n }\n\n const data = (await response.json()) as OsvBatchResponse;\n batchResults = data.results ?? [];\n } catch (error: unknown) {\n if (error instanceof VulnDatabaseError) throw error;\n if (isAbortError(error)) {\n const msg = timeoutController.signal.aborted\n ? `OSV batch query timed out after ${this.timeoutMs}ms (${osvQueries.length} packages)`\n : `OSV batch query aborted`;\n logError(msg);\n throw new VulnDatabaseError(msg, 'OSV');\n }\n const msg = error instanceof Error ? error.message : String(error);\n logError(`OSV batch query failed: ${msg}`);\n throw new VulnDatabaseError(`Network error in OSV batch query: ${msg}`, 'OSV');\n } finally {\n clearTimeout(timeoutId);\n }\n\n // Rebuild full results array aligned with input queries\n return queries.map((_, i) => {\n const osvIdx = indexMap[i];\n if (osvIdx === null) return [];\n const resultEntry = batchResults[osvIdx];\n if (!resultEntry) return [];\n return this.parseResponse({ vulns: resultEntry.vulns });\n });\n }\n\n async query(\n ecosystem: string,\n packageName: string,\n version: string,\n signal?: AbortSignal,\n ): Promise<CVERecord[]> {\n const osvEcosystem = ECOSYSTEM_MAP[ecosystem.toLowerCase()];\n if (!osvEcosystem) {\n logWarn(`OSV adapter: unsupported ecosystem \"${ecosystem}\", skipping query`);\n return [];\n }\n\n const body = JSON.stringify({\n package: {\n name: packageName,\n ecosystem: osvEcosystem,\n },\n version,\n });\n\n // Create a timeout abort signal and combine with any external signal\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);\n\n const combinedSignal = signal\n ? combineAbortSignals(signal, timeoutController.signal)\n : timeoutController.signal;\n\n try {\n const response = await fetch(this.apiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n signal: combinedSignal,\n });\n\n if (!response.ok) {\n throw new VulnDatabaseError(\n `OSV API returned HTTP ${response.status}: ${response.statusText}`,\n 'OSV',\n response.status,\n );\n }\n\n const data = (await response.json()) as OsvQueryResponse;\n return this.parseResponse(data);\n } catch (error: unknown) {\n if (error instanceof VulnDatabaseError) {\n throw error;\n }\n\n if (isAbortError(error)) {\n const isTimeout = timeoutController.signal.aborted;\n const message = isTimeout\n ? `OSV query timed out after ${this.timeoutMs}ms for ${packageName}@${version}`\n : `OSV query aborted for ${packageName}@${version}`;\n logError(message);\n throw new VulnDatabaseError(message, 'OSV');\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n logError(`OSV query failed for ${packageName}@${version}: ${errorMessage}`);\n throw new VulnDatabaseError(\n `Network error querying OSV for ${packageName}@${version}: ${errorMessage}`,\n 'OSV',\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Parse the OSV API response into CVERecord objects.\n */\n private parseResponse(data: OsvQueryResponse): CVERecord[] {\n if (!data.vulns || data.vulns.length === 0) {\n return [];\n }\n\n const records: CVERecord[] = [];\n\n for (const vuln of data.vulns) {\n const cveId = this.extractCveId(vuln);\n if (!cveId) {\n // Skip vulnerabilities without a CVE identifier\n continue;\n }\n\n const severity = this.extractSeverity(vuln);\n const { ranges, fixedVersion } = this.extractAffectedRanges(vuln);\n const summary = this.extractSummary(vuln);\n\n records.push({\n id: cveId,\n severity,\n affectedVersionRanges: ranges,\n fixedVersion,\n summary,\n });\n }\n\n return records;\n }\n\n /**\n * Extract CVE ID from the vulnerability's aliases or use the OSV ID.\n * Prefers CVE-* identifiers from the aliases array.\n */\n private extractCveId(vuln: OsvVulnerability): string | undefined {\n // Look for a CVE alias first\n if (vuln.aliases) {\n const cveAlias = vuln.aliases.find((alias) => alias.startsWith('CVE-'));\n if (cveAlias) {\n return cveAlias;\n }\n }\n\n // Fall back to the OSV ID (e.g., GHSA-xxxx-xxxx-xxxx)\n return vuln.id;\n }\n\n /**\n * Extract CVSS v3.1 base score from the severity array.\n * Returns 0 if no CVSS score is available.\n */\n private extractSeverity(vuln: OsvVulnerability): number {\n if (!vuln.severity || vuln.severity.length === 0) {\n return 0;\n }\n\n for (const sev of vuln.severity) {\n if (sev.type === 'CVSS_V3') {\n const score = this.parseCvssScore(sev.score);\n if (score !== null) {\n return score;\n }\n }\n }\n\n // Try any severity type as fallback\n for (const sev of vuln.severity) {\n const score = this.parseCvssScore(sev.score);\n if (score !== null) {\n return score;\n }\n }\n\n return 0;\n }\n\n /**\n * Parse a CVSS vector string or numeric score.\n * CVSS vectors look like: \"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\"\n * The base score is not directly in the vector \u2014 we extract it if it's a plain number,\n * or return null if we can't parse it.\n */\n private parseCvssScore(scoreStr: string): number | null {\n // If it's a plain number\n const num = parseFloat(scoreStr);\n if (!isNaN(num) && num >= 0 && num <= 10) {\n return num;\n }\n\n // CVSS vector strings don't contain the base score directly.\n // OSV sometimes provides the score as a plain number in the score field.\n return null;\n }\n\n /**\n * Extract affected version ranges and the first fixed version from the vulnerability.\n */\n private extractAffectedRanges(vuln: OsvVulnerability): {\n ranges: VersionRange[];\n fixedVersion?: string;\n } {\n const ranges: VersionRange[] = [];\n let fixedVersion: string | undefined;\n\n if (!vuln.affected) {\n return { ranges, fixedVersion };\n }\n\n for (const affected of vuln.affected) {\n if (!affected.ranges) {\n continue;\n }\n\n for (const range of affected.ranges) {\n if (!range.events || range.events.length === 0) {\n continue;\n }\n\n const versionRange: VersionRange = {};\n\n for (const event of range.events) {\n if (event.introduced) {\n versionRange.introduced = event.introduced;\n }\n if (event.fixed) {\n versionRange.fixed = event.fixed;\n // Capture the first fixed version we encounter\n if (!fixedVersion) {\n fixedVersion = event.fixed;\n }\n }\n if (event.last_affected) {\n versionRange.lastAffected = event.last_affected;\n }\n }\n\n // Only add ranges that have at least one meaningful field\n if (versionRange.introduced || versionRange.fixed || versionRange.lastAffected) {\n ranges.push(versionRange);\n }\n }\n }\n\n return { ranges, fixedVersion };\n }\n\n /**\n * Extract and truncate the summary from the vulnerability.\n * Prefers the summary field, falls back to details.\n */\n private extractSummary(vuln: OsvVulnerability): string {\n const text = vuln.summary || vuln.details || '';\n if (text.length <= MAX_SUMMARY_LENGTH) {\n return text;\n }\n return text.slice(0, MAX_SUMMARY_LENGTH - 3) + '...';\n }\n}\n\n// \u2500\u2500 Utility Functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Combine multiple AbortSignals into one that aborts when any of them aborts.\n */\nfunction combineAbortSignals(...signals: AbortSignal[]): AbortSignal {\n const controller = new AbortController();\n\n for (const signal of signals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n return controller.signal;\n }\n\n signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });\n }\n\n return controller.signal;\n}\n\n/**\n * Check if an error is an abort/timeout error.\n */\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof Error) {\n return error.name === 'AbortError' || error.name === 'TimeoutError';\n }\n return false;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,oBAAkC;AAClC,IAAAA,iBAAkC;AAMlC,MAAM,gBAAwC;AAAA,EAC5C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA;AAAA,EACX,OAAO;AAAA,EACP,QAAQ;AAAA;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,OAAO;AAAA,EACP,KAAK;AAAA,EACL,KAAK;AACP;
|
|
4
|
+
"sourcesContent": ["/**\n * OSV API Adapter\n *\n * Queries the OSV (Open Source Vulnerabilities) database for known CVEs\n * affecting project dependencies. Implements the VulnDatabaseAdapter interface.\n */\n\nimport { CVERecord, VersionRange } from '../types';\nimport type { VulnDatabaseAdapter, BatchQuery } from './types';\nimport { VulnDatabaseError } from '../errors';\nimport { logWarn, logError } from '../../errors';\n\nexport type { VulnDatabaseAdapter, BatchQuery };\n\n// \u2500\u2500 Ecosystem Mapping \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst ECOSYSTEM_MAP: Record<string, string> = {\n npm: 'npm',\n maven: 'Maven',\n go: 'Go',\n pypi: 'PyPI',\n python: 'PyPI', // manifest parser sends 'python' for requirements.txt\n cargo: 'crates.io',\n pyproject: 'PyPI', // pyproject.toml (Poetry/Hatch/PDM/PEP 621)\n nuget: 'NuGet',\n csproj: 'NuGet', // manifest parser sends 'csproj' for .csproj files\n gradle: 'Maven', // Gradle projects use Maven Central\n rubygems: 'RubyGems',\n composer: 'Packagist',\n swift: 'SwiftURL',\n pub: 'Pub',\n hex: 'Hex',\n};\n\n// \u2500\u2500 OSV Request/Response Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface OsvSeverity {\n type: string;\n score: string;\n}\n\ninterface OsvAffectedRange {\n type: string;\n events: Array<{ introduced?: string; fixed?: string; last_affected?: string }>;\n}\n\ninterface OsvAffected {\n package?: { name?: string; ecosystem?: string };\n ranges?: OsvAffectedRange[];\n versions?: string[];\n}\n\ninterface OsvVulnerability {\n id: string;\n aliases?: string[];\n summary?: string;\n details?: string;\n severity?: OsvSeverity[];\n affected?: OsvAffected[];\n /** OSV per-database extras. GHSA records expose a coarse qualitative severity here. */\n database_specific?: { severity?: string;[key: string]: unknown };\n}\n\ninterface OsvQueryResponse {\n vulns?: OsvVulnerability[];\n}\n\ninterface OsvBatchQueryItem {\n package: { name: string; ecosystem: string };\n version: string;\n}\n\ninterface OsvBatchResponse {\n results: Array<{ vulns?: OsvVulnerability[] }>;\n}\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst OSV_API_URL = 'https://api.osv.dev/v1/query';\nconst OSV_BATCH_API_URL = 'https://api.osv.dev/v1/querybatch';\nconst OSV_VULN_DETAIL_URL = 'https://api.osv.dev/v1/vulns';\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst OSV_BATCH_MAX_QUERIES = 1000;\nconst OSV_DETAIL_FETCH_CONCURRENCY = 10;\nconst MAX_SUMMARY_LENGTH = 500;\n\n// \u2500\u2500 CVSS parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Parse an OSV severity `score` field into a CVSS base score (0.0\u201310.0).\n *\n * The field is either a plain number (rare) or a CVSS vector string such as\n * \"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\". For v3.0/3.1 vectors we compute\n * the base score from the metrics per the FIRST CVSS specification. Returns null\n * when the value can't be interpreted.\n */\nexport function parseCvssScore(scoreStr: string | undefined): number | null {\n if (!scoreStr) return null;\n\n const trimmed = scoreStr.trim();\n\n // Plain numeric score.\n const num = parseFloat(trimmed);\n if (!isNaN(num) && String(num) === trimmed && num >= 0 && num <= 10) {\n return num;\n }\n\n // CVSS vector string.\n if (/^CVSS:3\\.[01]\\//i.test(trimmed)) {\n return computeCvssV3BaseScore(trimmed);\n }\n\n return null;\n}\n\n/** Round up to one decimal place, per the CVSS spec's roundup() function. */\nfunction cvssRoundUp(value: number): number {\n const intInput = Math.round(value * 100000);\n if (intInput % 10000 === 0) return intInput / 100000;\n return (Math.floor(intInput / 10000) + 1) / 10;\n}\n\n/**\n * Compute the CVSS v3.0/3.1 base score from a vector string.\n * Implements the base-score equations from the FIRST CVSS v3.1 specification.\n * Returns null if required base metrics are missing.\n */\nexport function computeCvssV3BaseScore(vector: string): number | null {\n const metrics: Record<string, string> = {};\n for (const part of vector.split('/')) {\n const [k, v] = part.split(':');\n if (k && v) metrics[k.toUpperCase()] = v.toUpperCase();\n }\n\n // Required base metrics.\n const AV = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 }[metrics.AV];\n const AC = { L: 0.77, H: 0.44 }[metrics.AC];\n const UI = { N: 0.85, R: 0.62 }[metrics.UI];\n const scopeChanged = metrics.S === 'C';\n // Privileges Required depends on Scope.\n const PR = ((): number | undefined => {\n if (metrics.PR === 'N') return 0.85;\n if (metrics.PR === 'L') return scopeChanged ? 0.68 : 0.62;\n if (metrics.PR === 'H') return scopeChanged ? 0.5 : 0.27;\n return undefined;\n })();\n const impactVal = { H: 0.56, L: 0.22, N: 0.0 };\n const C = impactVal[metrics.C as keyof typeof impactVal];\n const I = impactVal[metrics.I as keyof typeof impactVal];\n const A = impactVal[metrics.A as keyof typeof impactVal];\n\n if ([AV, AC, UI, PR, C, I, A].some(v => v === undefined)) {\n return null;\n }\n\n const iscBase = 1 - (1 - C!) * (1 - I!) * (1 - A!);\n const impact = scopeChanged\n ? 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15)\n : 6.42 * iscBase;\n const exploitability = 8.22 * AV! * AC! * PR! * UI!;\n\n if (impact <= 0) return 0.0;\n\n const raw = scopeChanged\n ? Math.min(1.08 * (impact + exploitability), 10)\n : Math.min(impact + exploitability, 10);\n\n return cvssRoundUp(raw);\n}\n\n/**\n * Map a coarse qualitative severity label (GHSA `database_specific.severity`,\n * or a CVSS qualitative rating) to a representative numeric score. Used only as\n * a fallback when no CVSS vector is available. Returns null for unknown labels.\n */\nexport function qualitativeToScore(label: string | undefined): number | null {\n if (!label) return null;\n switch (label.trim().toUpperCase()) {\n case 'CRITICAL': return 9.8;\n case 'HIGH': return 7.5;\n case 'MODERATE':\n case 'MEDIUM': return 5.5;\n case 'LOW': return 3.1;\n default: return null;\n }\n}\n\n// \u2500\u2500 OsvAdapter Implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class OsvAdapter implements VulnDatabaseAdapter {\n public readonly name = 'OSV';\n\n private readonly apiUrl: string;\n private readonly batchApiUrl: string;\n private readonly vulnDetailUrl: string;\n private readonly timeoutMs: number;\n\n constructor(options?: { apiUrl?: string; batchApiUrl?: string; vulnDetailUrl?: string; timeoutMs?: number }) {\n this.apiUrl = options?.apiUrl ?? OSV_API_URL;\n this.batchApiUrl = options?.batchApiUrl ?? OSV_BATCH_API_URL;\n this.vulnDetailUrl = options?.vulnDetailUrl ?? OSV_VULN_DETAIL_URL;\n this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Batch query up to OSV_BATCH_MAX_QUERIES packages in a single HTTP request.\n * Results are returned in the same order as the input queries.\n * Queries for unsupported ecosystems produce empty results without an HTTP call.\n */\n async queryBatch(\n queries: BatchQuery[],\n signal?: AbortSignal,\n ): Promise<Array<CVERecord[]>> {\n if (queries.length === 0) return [];\n\n // Map each query to its OSV ecosystem \u2014 track indices of unsupported ones\n const osvQueries: OsvBatchQueryItem[] = [];\n const indexMap: Array<number | null> = []; // index into osvQueries, or null if unsupported\n\n for (const q of queries) {\n const osvEcosystem = ECOSYSTEM_MAP[q.ecosystem.toLowerCase()];\n if (!osvEcosystem) {\n logWarn(`OSV adapter: unsupported ecosystem \"${q.ecosystem}\", skipping batch entry`);\n indexMap.push(null);\n } else {\n indexMap.push(osvQueries.length);\n osvQueries.push({\n package: { name: q.packageName, ecosystem: osvEcosystem },\n version: q.version,\n });\n }\n }\n\n if (osvQueries.length === 0) {\n return queries.map(() => []);\n }\n\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);\n const combinedSignal = signal\n ? combineAbortSignals(signal, timeoutController.signal)\n : timeoutController.signal;\n\n let batchResults: Array<{ vulns?: OsvVulnerability[] }>;\n\n try {\n const response = await fetch(this.batchApiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ queries: osvQueries }),\n signal: combinedSignal,\n });\n\n if (!response.ok) {\n throw new VulnDatabaseError(\n `OSV batch API returned HTTP ${response.status}: ${response.statusText}`,\n 'OSV',\n response.status,\n );\n }\n\n const data = (await response.json()) as OsvBatchResponse;\n batchResults = data.results ?? [];\n } catch (error: unknown) {\n if (error instanceof VulnDatabaseError) throw error;\n if (isAbortError(error)) {\n const msg = timeoutController.signal.aborted\n ? `OSV batch query timed out after ${this.timeoutMs}ms (${osvQueries.length} packages)`\n : `OSV batch query aborted`;\n logError(msg);\n throw new VulnDatabaseError(msg, 'OSV');\n }\n const msg = error instanceof Error ? error.message : String(error);\n logError(`OSV batch query failed: ${msg}`);\n throw new VulnDatabaseError(`Network error in OSV batch query: ${msg}`, 'OSV');\n } finally {\n clearTimeout(timeoutId);\n }\n\n // OSV's batch endpoint only ever returns { id, modified } stubs \u2014 no\n // severity, affected ranges, summary, or fixed version. Fetch full\n // details for every unique vulnerability ID found, deduplicated since\n // many packages in the same batch often share the same advisory.\n const uniqueIds = new Set<string>();\n for (const result of batchResults) {\n for (const vuln of result.vulns ?? []) {\n uniqueIds.add(vuln.id);\n }\n }\n const detailsById = await this.fetchVulnDetails([...uniqueIds], combinedSignal);\n\n // Rebuild full results array aligned with input queries\n return queries.map((_, i) => {\n const osvIdx = indexMap[i];\n if (osvIdx === null) return [];\n const resultEntry = batchResults[osvIdx];\n if (!resultEntry) return [];\n const fullVulns = (resultEntry.vulns ?? []).map(stub => detailsById.get(stub.id) ?? stub);\n return this.parseResponse({ vulns: fullVulns });\n });\n }\n\n /**\n * Fetch full vulnerability records for a set of IDs via OSV's per-ID detail\n * endpoint (the batch endpoint doesn't include this data). Runs with\n * bounded concurrency; a failure on one ID is logged and that ID falls\n * back to its stub (still usable, just without severity/EPSS-relevant data)\n * rather than failing the whole batch.\n */\n private async fetchVulnDetails(\n ids: string[],\n signal: AbortSignal,\n ): Promise<Map<string, OsvVulnerability>> {\n const results = new Map<string, OsvVulnerability>();\n if (ids.length === 0) return results;\n\n let cursor = 0;\n const worker = async (): Promise<void> => {\n while (cursor < ids.length) {\n const id = ids[cursor++];\n try {\n const response = await fetch(`${this.vulnDetailUrl}/${encodeURIComponent(id)}`, { signal });\n if (!response.ok) {\n logWarn(`OSV: failed to fetch details for ${id}: HTTP ${response.status}`);\n continue;\n }\n const vuln = (await response.json()) as OsvVulnerability;\n results.set(id, vuln);\n } catch (error: unknown) {\n if (isAbortError(error)) return;\n const msg = error instanceof Error ? error.message : String(error);\n logWarn(`OSV: failed to fetch details for ${id}: ${msg}`);\n }\n }\n };\n\n const workerCount = Math.min(OSV_DETAIL_FETCH_CONCURRENCY, ids.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n return results;\n }\n\n async query(\n ecosystem: string,\n packageName: string,\n version: string,\n signal?: AbortSignal,\n ): Promise<CVERecord[]> {\n const osvEcosystem = ECOSYSTEM_MAP[ecosystem.toLowerCase()];\n if (!osvEcosystem) {\n logWarn(`OSV adapter: unsupported ecosystem \"${ecosystem}\", skipping query`);\n return [];\n }\n\n const body = JSON.stringify({\n package: {\n name: packageName,\n ecosystem: osvEcosystem,\n },\n version,\n });\n\n // Create a timeout abort signal and combine with any external signal\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);\n\n const combinedSignal = signal\n ? combineAbortSignals(signal, timeoutController.signal)\n : timeoutController.signal;\n\n try {\n const response = await fetch(this.apiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n signal: combinedSignal,\n });\n\n if (!response.ok) {\n throw new VulnDatabaseError(\n `OSV API returned HTTP ${response.status}: ${response.statusText}`,\n 'OSV',\n response.status,\n );\n }\n\n const data = (await response.json()) as OsvQueryResponse;\n return this.parseResponse(data);\n } catch (error: unknown) {\n if (error instanceof VulnDatabaseError) {\n throw error;\n }\n\n if (isAbortError(error)) {\n const isTimeout = timeoutController.signal.aborted;\n const message = isTimeout\n ? `OSV query timed out after ${this.timeoutMs}ms for ${packageName}@${version}`\n : `OSV query aborted for ${packageName}@${version}`;\n logError(message);\n throw new VulnDatabaseError(message, 'OSV');\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n logError(`OSV query failed for ${packageName}@${version}: ${errorMessage}`);\n throw new VulnDatabaseError(\n `Network error querying OSV for ${packageName}@${version}: ${errorMessage}`,\n 'OSV',\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Parse the OSV API response into CVERecord objects.\n */\n private parseResponse(data: OsvQueryResponse): CVERecord[] {\n if (!data.vulns || data.vulns.length === 0) {\n return [];\n }\n\n const records: CVERecord[] = [];\n\n for (const vuln of data.vulns) {\n const cveId = this.extractCveId(vuln);\n if (!cveId) {\n // Skip vulnerabilities without a CVE identifier\n continue;\n }\n\n const severity = this.extractSeverity(vuln);\n const { ranges, fixedVersion } = this.extractAffectedRanges(vuln);\n const summary = this.extractSummary(vuln);\n\n records.push({\n id: cveId,\n severity,\n affectedVersionRanges: ranges,\n fixedVersion,\n summary,\n });\n }\n\n return records;\n }\n\n /**\n * Extract CVE ID from the vulnerability's aliases or use the OSV ID.\n * Prefers CVE-* identifiers from the aliases array.\n */\n private extractCveId(vuln: OsvVulnerability): string | undefined {\n // Look for a CVE alias first\n if (vuln.aliases) {\n const cveAlias = vuln.aliases.find((alias) => alias.startsWith('CVE-'));\n if (cveAlias) {\n return cveAlias;\n }\n }\n\n // Fall back to the OSV ID (e.g., GHSA-xxxx-xxxx-xxxx)\n return vuln.id;\n }\n\n /**\n * Extract a CVSS base score (0.0\u201310.0) for the vulnerability.\n *\n * OSV encodes severity as an array of `{ type, score }`. For CVSS_V3/CVSS_V4\n * the `score` field is a *vector string* (e.g. \"CVSS:3.1/AV:N/AC:L/...\") \u2014 NOT\n * a plain number \u2014 so the base score must be computed from the vector.\n *\n * Resolution order:\n * 1. CVSS_V3 vector \u2192 computed base score\n * 2. CVSS_V4 vector \u2192 computed base score\n * 3. Any severity entry that happens to be a plain number\n * 4. Coarse qualitative `database_specific.severity` (GHSA) \u2192 representative score\n *\n * Returns null when no severity data is available (so callers can distinguish\n * \"unknown\" from a genuine 0.0 score).\n */\n private extractSeverity(vuln: OsvVulnerability): number | null {\n if (vuln.severity && vuln.severity.length > 0) {\n // Prefer CVSS v3, then v4, then any parseable entry.\n const byType = (type: string) =>\n vuln.severity!.filter(s => s.type === type);\n\n for (const sev of [...byType('CVSS_V3'), ...byType('CVSS_V4')]) {\n const score = parseCvssScore(sev.score);\n if (score !== null) return score;\n }\n for (const sev of vuln.severity) {\n const score = parseCvssScore(sev.score);\n if (score !== null) return score;\n }\n }\n\n // Fallback: GHSA advisories expose a coarse qualitative severity here even\n // when a CVSS vector is absent.\n const qualitative = qualitativeToScore(vuln.database_specific?.severity);\n if (qualitative !== null) return qualitative;\n\n return null;\n }\n\n /**\n * Extract affected version ranges and the first fixed version from the vulnerability.\n */\n private extractAffectedRanges(vuln: OsvVulnerability): {\n ranges: VersionRange[];\n fixedVersion?: string;\n } {\n const ranges: VersionRange[] = [];\n let fixedVersion: string | undefined;\n\n if (!vuln.affected) {\n return { ranges, fixedVersion };\n }\n\n for (const affected of vuln.affected) {\n if (!affected.ranges) {\n continue;\n }\n\n for (const range of affected.ranges) {\n if (!range.events || range.events.length === 0) {\n continue;\n }\n\n const versionRange: VersionRange = {};\n\n for (const event of range.events) {\n if (event.introduced) {\n versionRange.introduced = event.introduced;\n }\n if (event.fixed) {\n versionRange.fixed = event.fixed;\n // Capture the first fixed version we encounter\n if (!fixedVersion) {\n fixedVersion = event.fixed;\n }\n }\n if (event.last_affected) {\n versionRange.lastAffected = event.last_affected;\n }\n }\n\n // Only add ranges that have at least one meaningful field\n if (versionRange.introduced || versionRange.fixed || versionRange.lastAffected) {\n ranges.push(versionRange);\n }\n }\n }\n\n return { ranges, fixedVersion };\n }\n\n /**\n * Extract and truncate the summary from the vulnerability.\n * Prefers the summary field, falls back to details.\n */\n private extractSummary(vuln: OsvVulnerability): string {\n const text = vuln.summary || vuln.details || '';\n if (text.length <= MAX_SUMMARY_LENGTH) {\n return text;\n }\n return text.slice(0, MAX_SUMMARY_LENGTH - 3) + '...';\n }\n}\n\n// \u2500\u2500 Utility Functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Combine multiple AbortSignals into one that aborts when any of them aborts.\n */\nfunction combineAbortSignals(...signals: AbortSignal[]): AbortSignal {\n const controller = new AbortController();\n\n for (const signal of signals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n return controller.signal;\n }\n\n signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });\n }\n\n return controller.signal;\n}\n\n/**\n * Check if an error is an abort/timeout error.\n */\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof Error) {\n return error.name === 'AbortError' || error.name === 'TimeoutError';\n }\n return false;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,oBAAkC;AAClC,IAAAA,iBAAkC;AAMlC,MAAM,gBAAwC;AAAA,EAC5C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,QAAQ;AAAA;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA;AAAA,EACX,OAAO;AAAA,EACP,QAAQ;AAAA;AAAA,EACR,QAAQ;AAAA;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,OAAO;AAAA,EACP,KAAK;AAAA,EACL,KAAK;AACP;AA8CA,MAAM,cAAc;AACpB,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,wBAAwB;AAC9B,MAAM,+BAA+B;AACrC,MAAM,qBAAqB;AAYpB,SAAS,eAAe,UAA6C;AAC1E,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,UAAU,SAAS,KAAK;AAG9B,QAAM,MAAM,WAAW,OAAO;AAC9B,MAAI,CAAC,MAAM,GAAG,KAAK,OAAO,GAAG,MAAM,WAAW,OAAO,KAAK,OAAO,IAAI;AACnE,WAAO;AAAA,EACT;AAGA,MAAI,mBAAmB,KAAK,OAAO,GAAG;AACpC,WAAO,uBAAuB,OAAO;AAAA,EACvC;AAEA,SAAO;AACT;AAGA,SAAS,YAAY,OAAuB;AAC1C,QAAM,WAAW,KAAK,MAAM,QAAQ,GAAM;AAC1C,MAAI,WAAW,QAAU,EAAG,QAAO,WAAW;AAC9C,UAAQ,KAAK,MAAM,WAAW,GAAK,IAAI,KAAK;AAC9C;AAOO,SAAS,uBAAuB,QAA+B;AACpE,QAAM,UAAkC,CAAC;AACzC,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,GAAG;AAC7B,QAAI,KAAK,EAAG,SAAQ,EAAE,YAAY,CAAC,IAAI,EAAE,YAAY;AAAA,EACvD;AAGA,QAAM,KAAK,EAAE,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE;AAC3D,QAAM,KAAK,EAAE,GAAG,MAAM,GAAG,KAAK,EAAE,QAAQ,EAAE;AAC1C,QAAM,KAAK,EAAE,GAAG,MAAM,GAAG,KAAK,EAAE,QAAQ,EAAE;AAC1C,QAAM,eAAe,QAAQ,MAAM;AAEnC,QAAM,MAAM,MAA0B;AACpC,QAAI,QAAQ,OAAO,IAAK,QAAO;AAC/B,QAAI,QAAQ,OAAO,IAAK,QAAO,eAAe,OAAO;AACrD,QAAI,QAAQ,OAAO,IAAK,QAAO,eAAe,MAAM;AACpD,WAAO;AAAA,EACT,GAAG;AACH,QAAM,YAAY,EAAE,GAAG,MAAM,GAAG,MAAM,GAAG,EAAI;AAC7C,QAAM,IAAI,UAAU,QAAQ,CAA2B;AACvD,QAAM,IAAI,UAAU,QAAQ,CAA2B;AACvD,QAAM,IAAI,UAAU,QAAQ,CAA2B;AAEvD,MAAI,CAAC,IAAI,IAAI,IAAI,IAAI,GAAG,GAAG,CAAC,EAAE,KAAK,OAAK,MAAM,MAAS,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,KAAK,IAAI,MAAO,IAAI,MAAO,IAAI;AAC/C,QAAM,SAAS,eACX,QAAQ,UAAU,SAAS,OAAO,KAAK,IAAI,UAAU,MAAM,EAAE,IAC7D,OAAO;AACX,QAAM,iBAAiB,OAAO,KAAM,KAAM,KAAM;AAEhD,MAAI,UAAU,EAAG,QAAO;AAExB,QAAM,MAAM,eACR,KAAK,IAAI,QAAQ,SAAS,iBAAiB,EAAE,IAC7C,KAAK,IAAI,SAAS,gBAAgB,EAAE;AAExC,SAAO,YAAY,GAAG;AACxB;AAOO,SAAS,mBAAmB,OAA0C;AAC3E,MAAI,CAAC,MAAO,QAAO;AACnB,UAAQ,MAAM,KAAK,EAAE,YAAY,GAAG;AAAA,IAClC,KAAK;AAAY,aAAO;AAAA,IACxB,KAAK;AAAQ,aAAO;AAAA,IACpB,KAAK;AAAA,IACL,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAO,aAAO;AAAA,IACnB;AAAS,aAAO;AAAA,EAClB;AACF;AAIO,MAAM,WAA0C;AAAA,EAQrD,YAAY,SAAiG;AAP7G,SAAgB,OAAO;AAQrB,SAAK,SAAS,SAAS,UAAU;AACjC,SAAK,cAAc,SAAS,eAAe;AAC3C,SAAK,gBAAgB,SAAS,iBAAiB;AAC/C,SAAK,YAAY,SAAS,aAAa;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,WACJ,SACA,QAC6B;AAC7B,QAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAGlC,UAAM,aAAkC,CAAC;AACzC,UAAM,WAAiC,CAAC;AAExC,eAAW,KAAK,SAAS;AACvB,YAAM,eAAe,cAAc,EAAE,UAAU,YAAY,CAAC;AAC5D,UAAI,CAAC,cAAc;AACjB,oCAAQ,uCAAuC,EAAE,SAAS,yBAAyB;AACnF,iBAAS,KAAK,IAAI;AAAA,MACpB,OAAO;AACL,iBAAS,KAAK,WAAW,MAAM;AAC/B,mBAAW,KAAK;AAAA,UACd,SAAS,EAAE,MAAM,EAAE,aAAa,WAAW,aAAa;AAAA,UACxD,SAAS,EAAE;AAAA,QACb,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,aAAO,QAAQ,IAAI,MAAM,CAAC,CAAC;AAAA,IAC7B;AAEA,UAAM,oBAAoB,IAAI,gBAAgB;AAC9C,UAAM,YAAY,WAAW,MAAM,kBAAkB,MAAM,GAAG,KAAK,SAAS;AAC5E,UAAM,iBAAiB,SACnB,oBAAoB,QAAQ,kBAAkB,MAAM,IACpD,kBAAkB;AAEtB,QAAI;AAEJ,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK,aAAa;AAAA,QAC7C,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,WAAW,CAAC;AAAA,QAC5C,QAAQ;AAAA,MACV,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,+BAA+B,SAAS,MAAM,KAAK,SAAS,UAAU;AAAA,UACtE;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,qBAAe,KAAK,WAAW,CAAC;AAAA,IAClC,SAAS,OAAgB;AACvB,UAAI,iBAAiB,gCAAmB,OAAM;AAC9C,UAAI,aAAa,KAAK,GAAG;AACvB,cAAMC,OAAM,kBAAkB,OAAO,UACjC,mCAAmC,KAAK,SAAS,OAAO,WAAW,MAAM,eACzE;AACJ,qCAASA,IAAG;AACZ,cAAM,IAAI,gCAAkBA,MAAK,KAAK;AAAA,MACxC;AACA,YAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,mCAAS,2BAA2B,GAAG,EAAE;AACzC,YAAM,IAAI,gCAAkB,qCAAqC,GAAG,IAAI,KAAK;AAAA,IAC/E,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAMA,UAAM,YAAY,oBAAI,IAAY;AAClC,eAAW,UAAU,cAAc;AACjC,iBAAW,QAAQ,OAAO,SAAS,CAAC,GAAG;AACrC,kBAAU,IAAI,KAAK,EAAE;AAAA,MACvB;AAAA,IACF;AACA,UAAM,cAAc,MAAM,KAAK,iBAAiB,CAAC,GAAG,SAAS,GAAG,cAAc;AAG9E,WAAO,QAAQ,IAAI,CAAC,GAAG,MAAM;AAC3B,YAAM,SAAS,SAAS,CAAC;AACzB,UAAI,WAAW,KAAM,QAAO,CAAC;AAC7B,YAAM,cAAc,aAAa,MAAM;AACvC,UAAI,CAAC,YAAa,QAAO,CAAC;AAC1B,YAAM,aAAa,YAAY,SAAS,CAAC,GAAG,IAAI,UAAQ,YAAY,IAAI,KAAK,EAAE,KAAK,IAAI;AACxF,aAAO,KAAK,cAAc,EAAE,OAAO,UAAU,CAAC;AAAA,IAChD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,iBACZ,KACA,QACwC;AACxC,UAAM,UAAU,oBAAI,IAA8B;AAClD,QAAI,IAAI,WAAW,EAAG,QAAO;AAE7B,QAAI,SAAS;AACb,UAAM,SAAS,YAA2B;AACxC,aAAO,SAAS,IAAI,QAAQ;AAC1B,cAAM,KAAK,IAAI,QAAQ;AACvB,YAAI;AACF,gBAAM,WAAW,MAAM,MAAM,GAAG,KAAK,aAAa,IAAI,mBAAmB,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC;AAC1F,cAAI,CAAC,SAAS,IAAI;AAChB,wCAAQ,oCAAoC,EAAE,UAAU,SAAS,MAAM,EAAE;AACzE;AAAA,UACF;AACA,gBAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,kBAAQ,IAAI,IAAI,IAAI;AAAA,QACtB,SAAS,OAAgB;AACvB,cAAI,aAAa,KAAK,EAAG;AACzB,gBAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,sCAAQ,oCAAoC,EAAE,KAAK,GAAG,EAAE;AAAA,QAC1D;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAAc,KAAK,IAAI,8BAA8B,IAAI,MAAM;AACrE,UAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,YAAY,GAAG,MAAM,OAAO,CAAC,CAAC;AACrE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,MACJ,WACA,aACA,SACA,QACsB;AACtB,UAAM,eAAe,cAAc,UAAU,YAAY,CAAC;AAC1D,QAAI,CAAC,cAAc;AACjB,kCAAQ,uCAAuC,SAAS,mBAAmB;AAC3E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,OAAO,KAAK,UAAU;AAAA,MAC1B,SAAS;AAAA,QACP,MAAM;AAAA,QACN,WAAW;AAAA,MACb;AAAA,MACA;AAAA,IACF,CAAC;AAGD,UAAM,oBAAoB,IAAI,gBAAgB;AAC9C,UAAM,YAAY,WAAW,MAAM,kBAAkB,MAAM,GAAG,KAAK,SAAS;AAE5E,UAAM,iBAAiB,SACnB,oBAAoB,QAAQ,kBAAkB,MAAM,IACpD,kBAAkB;AAEtB,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK,QAAQ;AAAA,QACxC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,yBAAyB,SAAS,MAAM,KAAK,SAAS,UAAU;AAAA,UAChE;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAEA,YAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,aAAO,KAAK,cAAc,IAAI;AAAA,IAChC,SAAS,OAAgB;AACvB,UAAI,iBAAiB,iCAAmB;AACtC,cAAM;AAAA,MACR;AAEA,UAAI,aAAa,KAAK,GAAG;AACvB,cAAM,YAAY,kBAAkB,OAAO;AAC3C,cAAM,UAAU,YACZ,6BAA6B,KAAK,SAAS,UAAU,WAAW,IAAI,OAAO,KAC3E,yBAAyB,WAAW,IAAI,OAAO;AACnD,qCAAS,OAAO;AAChB,cAAM,IAAI,gCAAkB,SAAS,KAAK;AAAA,MAC5C;AAEA,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,mCAAS,wBAAwB,WAAW,IAAI,OAAO,KAAK,YAAY,EAAE;AAC1E,YAAM,IAAI;AAAA,QACR,kCAAkC,WAAW,IAAI,OAAO,KAAK,YAAY;AAAA,QACzE;AAAA,MACF;AAAA,IACF,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,MAAqC;AACzD,QAAI,CAAC,KAAK,SAAS,KAAK,MAAM,WAAW,GAAG;AAC1C,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,UAAuB,CAAC;AAE9B,eAAW,QAAQ,KAAK,OAAO;AAC7B,YAAM,QAAQ,KAAK,aAAa,IAAI;AACpC,UAAI,CAAC,OAAO;AAEV;AAAA,MACF;AAEA,YAAM,WAAW,KAAK,gBAAgB,IAAI;AAC1C,YAAM,EAAE,QAAQ,aAAa,IAAI,KAAK,sBAAsB,IAAI;AAChE,YAAM,UAAU,KAAK,eAAe,IAAI;AAExC,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ;AAAA,QACA,uBAAuB;AAAA,QACvB;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,MAA4C;AAE/D,QAAI,KAAK,SAAS;AAChB,YAAM,WAAW,KAAK,QAAQ,KAAK,CAAC,UAAU,MAAM,WAAW,MAAM,CAAC;AACtE,UAAI,UAAU;AACZ,eAAO;AAAA,MACT;AAAA,IACF;AAGA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBQ,gBAAgB,MAAuC;AAC7D,QAAI,KAAK,YAAY,KAAK,SAAS,SAAS,GAAG;AAE7C,YAAM,SAAS,CAAC,SACd,KAAK,SAAU,OAAO,OAAK,EAAE,SAAS,IAAI;AAE5C,iBAAW,OAAO,CAAC,GAAG,OAAO,SAAS,GAAG,GAAG,OAAO,SAAS,CAAC,GAAG;AAC9D,cAAM,QAAQ,eAAe,IAAI,KAAK;AACtC,YAAI,UAAU,KAAM,QAAO;AAAA,MAC7B;AACA,iBAAW,OAAO,KAAK,UAAU;AAC/B,cAAM,QAAQ,eAAe,IAAI,KAAK;AACtC,YAAI,UAAU,KAAM,QAAO;AAAA,MAC7B;AAAA,IACF;AAIA,UAAM,cAAc,mBAAmB,KAAK,mBAAmB,QAAQ;AACvE,QAAI,gBAAgB,KAAM,QAAO;AAEjC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,MAG5B;AACA,UAAM,SAAyB,CAAC;AAChC,QAAI;AAEJ,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,EAAE,QAAQ,aAAa;AAAA,IAChC;AAEA,eAAW,YAAY,KAAK,UAAU;AACpC,UAAI,CAAC,SAAS,QAAQ;AACpB;AAAA,MACF;AAEA,iBAAW,SAAS,SAAS,QAAQ;AACnC,YAAI,CAAC,MAAM,UAAU,MAAM,OAAO,WAAW,GAAG;AAC9C;AAAA,QACF;AAEA,cAAM,eAA6B,CAAC;AAEpC,mBAAW,SAAS,MAAM,QAAQ;AAChC,cAAI,MAAM,YAAY;AACpB,yBAAa,aAAa,MAAM;AAAA,UAClC;AACA,cAAI,MAAM,OAAO;AACf,yBAAa,QAAQ,MAAM;AAE3B,gBAAI,CAAC,cAAc;AACjB,6BAAe,MAAM;AAAA,YACvB;AAAA,UACF;AACA,cAAI,MAAM,eAAe;AACvB,yBAAa,eAAe,MAAM;AAAA,UACpC;AAAA,QACF;AAGA,YAAI,aAAa,cAAc,aAAa,SAAS,aAAa,cAAc;AAC9E,iBAAO,KAAK,YAAY;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,QAAQ,aAAa;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,MAAgC;AACrD,UAAM,OAAO,KAAK,WAAW,KAAK,WAAW;AAC7C,QAAI,KAAK,UAAU,oBAAoB;AACrC,aAAO;AAAA,IACT;AACA,WAAO,KAAK,MAAM,GAAG,qBAAqB,CAAC,IAAI;AAAA,EACjD;AACF;AAOA,SAAS,uBAAuB,SAAqC;AACnE,QAAM,aAAa,IAAI,gBAAgB;AAEvC,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,SAAS;AAClB,iBAAW,MAAM,OAAO,MAAM;AAC9B,aAAO,WAAW;AAAA,IACpB;AAEA,WAAO,iBAAiB,SAAS,MAAM,WAAW,MAAM,OAAO,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,EACxF;AAEA,SAAO,WAAW;AACpB;AAKA,SAAS,aAAa,OAAyB;AAC7C,MAAI,iBAAiB,OAAO;AAC1B,WAAO,MAAM,SAAS,gBAAgB,MAAM,SAAS;AAAA,EACvD;AACA,SAAO;AACT;",
|
|
6
6
|
"names": ["import_errors", "msg"]
|
|
7
7
|
}
|
package/dist/sync/index.js
CHANGED
|
@@ -61,6 +61,13 @@ function buildIncludeMatcher(config) {
|
|
|
61
61
|
});
|
|
62
62
|
return (s) => matchers.some((m) => m(s));
|
|
63
63
|
}
|
|
64
|
+
function exceedsMaxFileSize(absPath, config) {
|
|
65
|
+
try {
|
|
66
|
+
return fs.statSync(absPath).size > config.maxFileSize;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
64
71
|
function scanDirectoryWalk(root, config, signal, visited = /* @__PURE__ */ new Set()) {
|
|
65
72
|
const results = [];
|
|
66
73
|
const walk = (dir) => {
|
|
@@ -88,7 +95,7 @@ function scanDirectoryWalk(root, config, signal, visited = /* @__PURE__ */ new S
|
|
|
88
95
|
if (entry.isDirectory()) {
|
|
89
96
|
walk(full);
|
|
90
97
|
} else if (entry.isFile()) {
|
|
91
|
-
if (shouldIncludeFile(rel, config)) {
|
|
98
|
+
if (shouldIncludeFile(rel, config) && !exceedsMaxFileSize(full, config)) {
|
|
92
99
|
const lang = (0, import_languages.detectLanguage)(full);
|
|
93
100
|
if (lang !== "unknown") results.push(full);
|
|
94
101
|
}
|
|
@@ -150,7 +157,7 @@ async function scanDirectory(root, config, signal) {
|
|
|
150
157
|
if (signal?.aborted) return allFiles;
|
|
151
158
|
const files = output.split("\n").filter(Boolean).map((rel) => path.join(gitRoot, rel)).filter((abs) => {
|
|
152
159
|
const rel = path.relative(root, abs).replace(/\\/g, "/");
|
|
153
|
-
return shouldIncludeFile(rel, config) && (0, import_languages.detectLanguage)(abs) !== "unknown";
|
|
160
|
+
return shouldIncludeFile(rel, config) && (0, import_languages.detectLanguage)(abs) !== "unknown" && !exceedsMaxFileSize(abs, config);
|
|
154
161
|
});
|
|
155
162
|
allFiles.push(...files);
|
|
156
163
|
} catch {
|
package/dist/sync/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/sync/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Sync Module \u2014 file discovery and change detection\n *\n * Extracted from KiroGraph class private methods in src/index.ts.\n * Provides scanDirectory, hashContent, shouldIncludeFile, and getChangedFiles.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport * as crypto from 'crypto';\nimport { execFileSync } from 'child_process';\nimport picomatch from 'picomatch';\nimport type { KiroGraphConfig } from '../config';\nimport { detectLanguage } from '../extraction/languages';\n\n// \u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Returns picomatch matchers for all exclude patterns plus .kirographignore root file lines.\n */\nfunction buildExcludeMatchers(config: KiroGraphConfig): ((s: string) => boolean)[] {\n const patterns = [...config.exclude];\n\n return patterns.map(p => {\n try {\n return picomatch(p);\n } catch {\n return () => false;\n }\n });\n}\n\n/**\n * Returns a combined picomatch matcher or null if no include patterns.\n */\nfunction buildIncludeMatcher(config: KiroGraphConfig): ((s: string) => boolean) | null {\n if (!config.include || config.include.length === 0) return null;\n const matchers = config.include.map(p => {\n try {\n return picomatch(p);\n } catch {\n return () => false;\n }\n });\n return (s: string) => matchers.some(m => m(s));\n}\n\n/**\n * Recursive filesystem walk. Checks .kirographignore, guards against symlink cycles,\n * respects AbortSignal, and filters files through shouldIncludeFile.\n */\nfunction scanDirectoryWalk(\n root: string,\n config: KiroGraphConfig,\n signal?: AbortSignal,\n visited: Set<string> = new Set()\n): string[] {\n const results: string[] = [];\n\n const walk = (dir: string) => {\n if (signal?.aborted) return;\n\n // Guard against symlink cycles\n let realDir: string;\n try { realDir = fs.realpathSync(dir); } catch { return; }\n if (visited.has(realDir)) return;\n visited.add(realDir);\n\n // Check for .kirographignore in this directory\n const ignoreFile = path.join(dir, '.kirographignore');\n if (fs.existsSync(ignoreFile)) return;\n\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n\n for (const entry of entries) {\n if (signal?.aborted) return;\n\n const full = path.join(dir, entry.name);\n const rel = path.relative(root, full).replace(/\\\\/g, '/');\n\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.isFile()) {\n if (shouldIncludeFile(rel, config)) {\n const lang = detectLanguage(full);\n if (lang !== 'unknown') results.push(full);\n }\n }\n }\n };\n\n walk(root);\n return results;\n}\n\n// \u2500\u2500 Git root helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Finds all git roots at or under `root` (including root itself).\n * Returns paths sorted shallowest-first so parent roots are processed before children.\n */\nfunction findGitRoots(root: string): string[] {\n const roots: string[] = [];\n\n const walk = (dir: string) => {\n if (fs.existsSync(path.join(dir, '.git'))) {\n roots.push(dir);\n return; // don't recurse into nested git repos\n }\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n for (const entry of entries) {\n if (entry.isDirectory()) walk(path.join(dir, entry.name));\n }\n };\n\n walk(root);\n return roots.length > 0 ? roots : [root];\n}\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Returns true when relPath passes the include/exclude filter from config.\n * Exclude patterns are checked first; include patterns (if any) are checked second.\n */\nexport function shouldIncludeFile(relPath: string, config: KiroGraphConfig): boolean {\n const excludeMatchers = buildExcludeMatchers(config);\n if (excludeMatchers.some(m => {\n try { return m(relPath) || m(relPath + '/'); } catch { return false; }\n })) return false;\n\n const includeMatcher = buildIncludeMatcher(config);\n if (includeMatcher && !includeMatcher(relPath)) return false;\n\n return true;\n}\n\n/**\n * SHA-256 hex digest of the given content.\n */\nexport function hashContent(content: Buffer | string): string {\n return crypto.createHash('sha256').update(content).digest('hex');\n}\n\n/**\n * Returns absolute paths of all indexable files under root.\n * Handles monorepos with multiple nested git roots by running git ls-files\n * from each sub-repo root independently, then merging results.\n * Falls back to filesystem walk if no git roots found.\n * Respects AbortSignal.\n */\nexport async function scanDirectory(\n root: string,\n config: KiroGraphConfig,\n signal?: AbortSignal\n): Promise<string[]> {\n if (signal?.aborted) return [];\n\n const gitRoots = findGitRoots(root);\n const allFiles: string[] = [];\n\n for (const gitRoot of gitRoots) {\n if (signal?.aborted) return allFiles;\n try {\n const output = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard'], {\n cwd: gitRoot,\n encoding: 'utf8',\n timeout: 10_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n if (signal?.aborted) return allFiles;\n const files = output.split('\\n').filter(Boolean)\n .map(rel => path.join(gitRoot, rel))\n .filter(abs => {\n const rel = path.relative(root, abs).replace(/\\\\/g, '/');\n return shouldIncludeFile(rel, config)
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,WAAsB;AACtB,SAAoB;AACpB,aAAwB;AACxB,2BAA6B;AAC7B,uBAAsB;AAEtB,uBAA+B;AAO/B,SAAS,qBAAqB,QAAqD;AACjF,QAAM,WAAW,CAAC,GAAG,OAAO,OAAO;AAEnC,SAAO,SAAS,IAAI,OAAK;AACvB,QAAI;AACF,iBAAO,iBAAAA,SAAU,CAAC;AAAA,IACpB,QAAQ;AACN,aAAO,MAAM;AAAA,IACf;AAAA,EACF,CAAC;AACH;AAKA,SAAS,oBAAoB,QAA0D;AACrF,MAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,WAAW,EAAG,QAAO;AAC3D,QAAM,WAAW,OAAO,QAAQ,IAAI,OAAK;AACvC,QAAI;AACF,iBAAO,iBAAAA,SAAU,CAAC;AAAA,IACpB,QAAQ;AACN,aAAO,MAAM;AAAA,IACf;AAAA,EACF,CAAC;AACD,SAAO,CAAC,MAAc,SAAS,KAAK,OAAK,EAAE,CAAC,CAAC;AAC/C;AAMA,SAAS,kBACP,MACA,QACA,QACA,UAAuB,oBAAI,IAAI,GACrB;AACV,QAAM,UAAoB,CAAC;AAE3B,QAAM,OAAO,CAAC,QAAgB;AAC5B,QAAI,QAAQ,QAAS;AAGrB,QAAI;AACJ,QAAI;AAAE,gBAAU,GAAG,aAAa,GAAG;AAAA,IAAG,QAAQ;AAAE;AAAA,IAAQ;AACxD,QAAI,QAAQ,IAAI,OAAO,EAAG;AAC1B,YAAQ,IAAI,OAAO;AAGnB,UAAM,aAAa,KAAK,KAAK,KAAK,kBAAkB;AACpD,QAAI,GAAG,WAAW,UAAU,EAAG;AAE/B,QAAI;AACJ,QAAI;AAAE,gBAAU,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAAG,QAAQ;AAAE;AAAA,IAAQ;AAEhF,eAAW,SAAS,SAAS;AAC3B,UAAI,QAAQ,QAAS;AAErB,YAAM,OAAO,KAAK,KAAK,KAAK,MAAM,IAAI;AACtC,YAAM,MAAM,KAAK,SAAS,MAAM,IAAI,EAAE,QAAQ,OAAO,GAAG;AAExD,UAAI,MAAM,YAAY,GAAG;AACvB,aAAK,IAAI;AAAA,MACX,WAAW,MAAM,OAAO,GAAG;AACzB,YAAI,kBAAkB,KAAK,MAAM,GAAG;
|
|
4
|
+
"sourcesContent": ["/**\n * Sync Module \u2014 file discovery and change detection\n *\n * Extracted from KiroGraph class private methods in src/index.ts.\n * Provides scanDirectory, hashContent, shouldIncludeFile, and getChangedFiles.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport * as crypto from 'crypto';\nimport { execFileSync } from 'child_process';\nimport picomatch from 'picomatch';\nimport type { KiroGraphConfig } from '../config';\nimport { detectLanguage } from '../extraction/languages';\n\n// \u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Returns picomatch matchers for all exclude patterns plus .kirographignore root file lines.\n */\nfunction buildExcludeMatchers(config: KiroGraphConfig): ((s: string) => boolean)[] {\n const patterns = [...config.exclude];\n\n return patterns.map(p => {\n try {\n return picomatch(p);\n } catch {\n return () => false;\n }\n });\n}\n\n/**\n * Returns a combined picomatch matcher or null if no include patterns.\n */\nfunction buildIncludeMatcher(config: KiroGraphConfig): ((s: string) => boolean) | null {\n if (!config.include || config.include.length === 0) return null;\n const matchers = config.include.map(p => {\n try {\n return picomatch(p);\n } catch {\n return () => false;\n }\n });\n return (s: string) => matchers.some(m => m(s));\n}\n\n/**\n * Returns true when the file at absPath exceeds config.maxFileSize.\n *\n * Uses fs.statSync so the size is checked WITHOUT reading the file into memory.\n * This is what prevents multi-MB compiled bundles from reaching the tree-sitter\n * parser, where the WASM runtime aborts the whole process (issue #33).\n * A stat failure is treated as \"does not exceed\" so the normal read path can\n * handle (and gracefully skip) an unreadable file.\n */\nfunction exceedsMaxFileSize(absPath: string, config: KiroGraphConfig): boolean {\n try {\n return fs.statSync(absPath).size > config.maxFileSize;\n } catch {\n return false;\n }\n}\n\n/**\n * Recursive filesystem walk. Checks .kirographignore, guards against symlink cycles,\n * respects AbortSignal, and filters files through shouldIncludeFile.\n */\nfunction scanDirectoryWalk(\n root: string,\n config: KiroGraphConfig,\n signal?: AbortSignal,\n visited: Set<string> = new Set()\n): string[] {\n const results: string[] = [];\n\n const walk = (dir: string) => {\n if (signal?.aborted) return;\n\n // Guard against symlink cycles\n let realDir: string;\n try { realDir = fs.realpathSync(dir); } catch { return; }\n if (visited.has(realDir)) return;\n visited.add(realDir);\n\n // Check for .kirographignore in this directory\n const ignoreFile = path.join(dir, '.kirographignore');\n if (fs.existsSync(ignoreFile)) return;\n\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n\n for (const entry of entries) {\n if (signal?.aborted) return;\n\n const full = path.join(dir, entry.name);\n const rel = path.relative(root, full).replace(/\\\\/g, '/');\n\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.isFile()) {\n if (shouldIncludeFile(rel, config) && !exceedsMaxFileSize(full, config)) {\n const lang = detectLanguage(full);\n if (lang !== 'unknown') results.push(full);\n }\n }\n }\n };\n\n walk(root);\n return results;\n}\n\n// \u2500\u2500 Git root helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Finds all git roots at or under `root` (including root itself).\n * Returns paths sorted shallowest-first so parent roots are processed before children.\n */\nfunction findGitRoots(root: string): string[] {\n const roots: string[] = [];\n\n const walk = (dir: string) => {\n if (fs.existsSync(path.join(dir, '.git'))) {\n roots.push(dir);\n return; // don't recurse into nested git repos\n }\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n for (const entry of entries) {\n if (entry.isDirectory()) walk(path.join(dir, entry.name));\n }\n };\n\n walk(root);\n return roots.length > 0 ? roots : [root];\n}\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Returns true when relPath passes the include/exclude filter from config.\n * Exclude patterns are checked first; include patterns (if any) are checked second.\n */\nexport function shouldIncludeFile(relPath: string, config: KiroGraphConfig): boolean {\n const excludeMatchers = buildExcludeMatchers(config);\n if (excludeMatchers.some(m => {\n try { return m(relPath) || m(relPath + '/'); } catch { return false; }\n })) return false;\n\n const includeMatcher = buildIncludeMatcher(config);\n if (includeMatcher && !includeMatcher(relPath)) return false;\n\n return true;\n}\n\n/**\n * SHA-256 hex digest of the given content.\n */\nexport function hashContent(content: Buffer | string): string {\n return crypto.createHash('sha256').update(content).digest('hex');\n}\n\n/**\n * Returns absolute paths of all indexable files under root.\n * Handles monorepos with multiple nested git roots by running git ls-files\n * from each sub-repo root independently, then merging results.\n * Falls back to filesystem walk if no git roots found.\n * Respects AbortSignal.\n */\nexport async function scanDirectory(\n root: string,\n config: KiroGraphConfig,\n signal?: AbortSignal\n): Promise<string[]> {\n if (signal?.aborted) return [];\n\n const gitRoots = findGitRoots(root);\n const allFiles: string[] = [];\n\n for (const gitRoot of gitRoots) {\n if (signal?.aborted) return allFiles;\n try {\n const output = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard'], {\n cwd: gitRoot,\n encoding: 'utf8',\n timeout: 10_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n if (signal?.aborted) return allFiles;\n const files = output.split('\\n').filter(Boolean)\n .map(rel => path.join(gitRoot, rel))\n .filter(abs => {\n const rel = path.relative(root, abs).replace(/\\\\/g, '/');\n return shouldIncludeFile(rel, config)\n && detectLanguage(abs) !== 'unknown'\n && !exceedsMaxFileSize(abs, config);\n });\n allFiles.push(...files);\n } catch {\n // git unavailable for this root \u2014 fall back to walk\n allFiles.push(...scanDirectoryWalk(gitRoot, config, signal));\n }\n }\n\n return allFiles;\n}\n\n/**\n * Classifies git-changed files into added / modified / removed across all nested git roots.\n * Returns absolute paths. Returns empty arrays if git is unavailable.\n * Excludes paths whose detected language is 'unknown'.\n */\nexport async function getChangedFiles(\n root: string,\n config: KiroGraphConfig\n): Promise<{ added: string[]; modified: string[]; removed: string[] }> {\n const added: string[] = [];\n const modified: string[] = [];\n const removed: string[] = [];\n\n const gitRoots = findGitRoots(root);\n\n for (const gitRoot of gitRoots) {\n try {\n const output = execFileSync('git', ['status', '--porcelain', '--no-renames'], {\n cwd: gitRoot,\n encoding: 'utf8',\n timeout: 10_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n\n for (const line of output.split('\\n').filter(Boolean)) {\n const statusCode = line.slice(0, 2);\n const relPath = line.slice(3).trim();\n const absPath = path.join(gitRoot, relPath);\n if (detectLanguage(absPath) === 'unknown') continue;\n if (statusCode === '??') {\n added.push(absPath);\n } else if (statusCode[0] === 'D' || statusCode[1] === 'D') {\n removed.push(absPath);\n } else {\n modified.push(absPath);\n }\n }\n } catch {\n // git unavailable for this root \u2014 skip\n }\n }\n\n return { added, modified, removed };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["picomatch"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var libsql_index_exports = {};
|
|
30
|
+
__export(libsql_index_exports, {
|
|
31
|
+
LibSQLIndex: () => LibSQLIndex
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(libsql_index_exports);
|
|
34
|
+
var path = __toESM(require("path"));
|
|
35
|
+
var import_errors = require("../errors");
|
|
36
|
+
const DEFAULT_DIM = 768;
|
|
37
|
+
const DB_FILE = "libsql.db";
|
|
38
|
+
const VECTOR_WEIGHT = 0.7;
|
|
39
|
+
const FTS_WEIGHT = 0.3;
|
|
40
|
+
class LibSQLIndex {
|
|
41
|
+
constructor(kirographDir, dim = DEFAULT_DIM) {
|
|
42
|
+
this.kirographDir = kirographDir;
|
|
43
|
+
this.dim = dim;
|
|
44
|
+
this.db = null;
|
|
45
|
+
this._available = false;
|
|
46
|
+
this.dbPath = path.join(kirographDir, DB_FILE);
|
|
47
|
+
}
|
|
48
|
+
isAvailable() {
|
|
49
|
+
return this._available;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Load the libsql package, open the database, and apply the schema.
|
|
53
|
+
* Silent no-op when the optional dep is missing.
|
|
54
|
+
*/
|
|
55
|
+
async initialize() {
|
|
56
|
+
if (this._available) return;
|
|
57
|
+
let Database;
|
|
58
|
+
try {
|
|
59
|
+
const mod = require("libsql");
|
|
60
|
+
Database = mod.Database ?? mod.default ?? mod;
|
|
61
|
+
} catch {
|
|
62
|
+
(0, import_errors.logDebug)("LibSQLIndex: libsql not installed \u2014 libSQL engine unavailable");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
this.db = new Database(this.dbPath);
|
|
67
|
+
this.db.exec(`
|
|
68
|
+
PRAGMA journal_mode=WAL;
|
|
69
|
+
PRAGMA synchronous=NORMAL;
|
|
70
|
+
PRAGMA cache_size=-32000;
|
|
71
|
+
PRAGMA temp_store=MEMORY;
|
|
72
|
+
`);
|
|
73
|
+
this.db.exec(`
|
|
74
|
+
CREATE TABLE IF NOT EXISTS kg_nodes (
|
|
75
|
+
node_id TEXT PRIMARY KEY,
|
|
76
|
+
name TEXT NOT NULL DEFAULT '',
|
|
77
|
+
kind TEXT NOT NULL DEFAULT '',
|
|
78
|
+
file_path TEXT NOT NULL DEFAULT '',
|
|
79
|
+
signature TEXT NOT NULL DEFAULT '',
|
|
80
|
+
embedding F32_BLOB(${this.dim})
|
|
81
|
+
);
|
|
82
|
+
`);
|
|
83
|
+
this.db.exec(`
|
|
84
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS kg_nodes_fts USING fts5(
|
|
85
|
+
node_id UNINDEXED,
|
|
86
|
+
name,
|
|
87
|
+
signature,
|
|
88
|
+
content='kg_nodes',
|
|
89
|
+
content_rowid='rowid'
|
|
90
|
+
);
|
|
91
|
+
`);
|
|
92
|
+
this.db.exec(`
|
|
93
|
+
CREATE TRIGGER IF NOT EXISTS kg_nodes_ai AFTER INSERT ON kg_nodes BEGIN
|
|
94
|
+
INSERT INTO kg_nodes_fts(rowid, node_id, name, signature)
|
|
95
|
+
VALUES (NEW.rowid, NEW.node_id, NEW.name, NEW.signature);
|
|
96
|
+
END;
|
|
97
|
+
|
|
98
|
+
CREATE TRIGGER IF NOT EXISTS kg_nodes_ad AFTER DELETE ON kg_nodes BEGIN
|
|
99
|
+
INSERT INTO kg_nodes_fts(kg_nodes_fts, rowid, node_id, name, signature)
|
|
100
|
+
VALUES ('delete', OLD.rowid, OLD.node_id, OLD.name, OLD.signature);
|
|
101
|
+
END;
|
|
102
|
+
|
|
103
|
+
CREATE TRIGGER IF NOT EXISTS kg_nodes_au AFTER UPDATE ON kg_nodes BEGIN
|
|
104
|
+
INSERT INTO kg_nodes_fts(kg_nodes_fts, rowid, node_id, name, signature)
|
|
105
|
+
VALUES ('delete', OLD.rowid, OLD.node_id, OLD.name, OLD.signature);
|
|
106
|
+
INSERT INTO kg_nodes_fts(rowid, node_id, name, signature)
|
|
107
|
+
VALUES (NEW.rowid, NEW.node_id, NEW.name, NEW.signature);
|
|
108
|
+
END;
|
|
109
|
+
`);
|
|
110
|
+
this._available = true;
|
|
111
|
+
(0, import_errors.logDebug)("LibSQLIndex: ready", { path: this.dbPath, dim: this.dim });
|
|
112
|
+
} catch (err) {
|
|
113
|
+
(0, import_errors.logError)("LibSQLIndex: initialization failed", { error: String(err) });
|
|
114
|
+
this._available = false;
|
|
115
|
+
if (this.db) {
|
|
116
|
+
try {
|
|
117
|
+
this.db.close();
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
this.db = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Insert or update a node's record using SQLite's native ON CONFLICT upsert.
|
|
126
|
+
*/
|
|
127
|
+
upsert(node, embedding) {
|
|
128
|
+
if (!this._available || !this.db) return;
|
|
129
|
+
try {
|
|
130
|
+
this.db.prepare(`
|
|
131
|
+
INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)
|
|
132
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
133
|
+
ON CONFLICT (node_id) DO UPDATE SET
|
|
134
|
+
name = excluded.name,
|
|
135
|
+
kind = excluded.kind,
|
|
136
|
+
file_path = excluded.file_path,
|
|
137
|
+
signature = excluded.signature,
|
|
138
|
+
embedding = excluded.embedding
|
|
139
|
+
`).run(
|
|
140
|
+
node.id,
|
|
141
|
+
node.name,
|
|
142
|
+
node.kind,
|
|
143
|
+
node.filePath,
|
|
144
|
+
node.signature ?? "",
|
|
145
|
+
embedding
|
|
146
|
+
);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
(0, import_errors.logWarn)("LibSQLIndex: upsert failed", { nodeId: node.id, error: String(err) });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Bulk upsert for batch embedding. Uses a transaction for atomicity and speed.
|
|
153
|
+
*/
|
|
154
|
+
bulkUpsert(nodes, embeddings) {
|
|
155
|
+
if (!this._available || !this.db) return;
|
|
156
|
+
if (nodes.length === 0) return;
|
|
157
|
+
const stmt = this.db.prepare(`
|
|
158
|
+
INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)
|
|
159
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
160
|
+
ON CONFLICT (node_id) DO UPDATE SET
|
|
161
|
+
name = excluded.name,
|
|
162
|
+
kind = excluded.kind,
|
|
163
|
+
file_path = excluded.file_path,
|
|
164
|
+
signature = excluded.signature,
|
|
165
|
+
embedding = excluded.embedding
|
|
166
|
+
`);
|
|
167
|
+
const tx = this.db.transaction(() => {
|
|
168
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
169
|
+
const node = nodes[i];
|
|
170
|
+
stmt.run(
|
|
171
|
+
node.id,
|
|
172
|
+
node.name,
|
|
173
|
+
node.kind,
|
|
174
|
+
node.filePath,
|
|
175
|
+
node.signature ?? "",
|
|
176
|
+
embeddings[i]
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
try {
|
|
181
|
+
tx();
|
|
182
|
+
} catch (err) {
|
|
183
|
+
(0, import_errors.logWarn)("LibSQLIndex: bulkUpsert failed", { count: nodes.length, error: String(err) });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Remove a node's record from the index.
|
|
188
|
+
*/
|
|
189
|
+
delete(nodeId) {
|
|
190
|
+
if (!this._available || !this.db) return;
|
|
191
|
+
try {
|
|
192
|
+
this.db.prepare("DELETE FROM kg_nodes WHERE node_id = ?").run(nodeId);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
(0, import_errors.logWarn)("LibSQLIndex: delete failed", { nodeId, error: String(err) });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Hybrid search: combines libSQL native vector_distance_cos with FTS5
|
|
199
|
+
* bm25 ranking. Returns node IDs ordered by combined score.
|
|
200
|
+
*
|
|
201
|
+
* Score = VECTOR_WEIGHT * (1 - cosine_distance) + FTS_WEIGHT * fts_rank
|
|
202
|
+
*
|
|
203
|
+
* Falls back to pure vector search when the query text produces no FTS matches.
|
|
204
|
+
*/
|
|
205
|
+
search(queryText, queryVec, topN = 10) {
|
|
206
|
+
if (!this._available || !this.db) return [];
|
|
207
|
+
try {
|
|
208
|
+
const hybridRows = this.db.prepare(`
|
|
209
|
+
SELECT
|
|
210
|
+
n.node_id,
|
|
211
|
+
(${VECTOR_WEIGHT} * (1.0 - vector_distance_cos(n.embedding, ?))
|
|
212
|
+
+ ${FTS_WEIGHT} * COALESCE(f.rank_score, 0.0)
|
|
213
|
+
) AS score
|
|
214
|
+
FROM kg_nodes n
|
|
215
|
+
LEFT JOIN (
|
|
216
|
+
SELECT node_id, -rank AS rank_score
|
|
217
|
+
FROM kg_nodes_fts
|
|
218
|
+
WHERE kg_nodes_fts MATCH ?
|
|
219
|
+
) f ON f.node_id = n.node_id
|
|
220
|
+
WHERE n.embedding IS NOT NULL
|
|
221
|
+
ORDER BY score DESC
|
|
222
|
+
LIMIT ?
|
|
223
|
+
`).all(queryVec, queryText, topN);
|
|
224
|
+
if (hybridRows.length > 0) {
|
|
225
|
+
return hybridRows.map((r) => r.node_id);
|
|
226
|
+
}
|
|
227
|
+
return this.vectorSearch(queryVec, topN);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
(0, import_errors.logDebug)("LibSQLIndex: hybrid search failed, trying pure vector", { error: String(err) });
|
|
230
|
+
try {
|
|
231
|
+
return this.vectorSearch(queryVec, topN);
|
|
232
|
+
} catch (vecErr) {
|
|
233
|
+
(0, import_errors.logWarn)("LibSQLIndex: search failed", { error: String(vecErr) });
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Pure vector similarity search using libSQL's native vector_distance_cos.
|
|
240
|
+
*/
|
|
241
|
+
vectorSearch(queryVec, topN) {
|
|
242
|
+
const rows = this.db.prepare(`
|
|
243
|
+
SELECT node_id, vector_distance_cos(embedding, ?) AS distance
|
|
244
|
+
FROM kg_nodes
|
|
245
|
+
WHERE embedding IS NOT NULL
|
|
246
|
+
ORDER BY distance ASC
|
|
247
|
+
LIMIT ?
|
|
248
|
+
`).all(queryVec, topN);
|
|
249
|
+
return rows.map((r) => r.node_id);
|
|
250
|
+
}
|
|
251
|
+
/** Return all node IDs currently stored in the index. */
|
|
252
|
+
getEmbeddedNodeIds() {
|
|
253
|
+
if (!this._available || !this.db) return [];
|
|
254
|
+
try {
|
|
255
|
+
const rows = this.db.prepare("SELECT node_id FROM kg_nodes").all();
|
|
256
|
+
return rows.map((r) => r.node_id);
|
|
257
|
+
} catch {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Number of documents currently in the index. */
|
|
262
|
+
count() {
|
|
263
|
+
if (!this._available || !this.db) return 0;
|
|
264
|
+
try {
|
|
265
|
+
const row = this.db.prepare("SELECT COUNT(*) AS c FROM kg_nodes").get();
|
|
266
|
+
return row ? row.c : 0;
|
|
267
|
+
} catch {
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Close the database connection and release resources. */
|
|
272
|
+
close() {
|
|
273
|
+
if (this.db) {
|
|
274
|
+
try {
|
|
275
|
+
this.db.close();
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
this.db = null;
|
|
279
|
+
this._available = false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
284
|
+
0 && (module.exports = {
|
|
285
|
+
LibSQLIndex
|
|
286
|
+
});
|
|
287
|
+
//# sourceMappingURL=libsql-index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/vectors/libsql-index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph libSQL Index\n *\n * Hybrid search (native vector similarity + FTS5 full-text) backed by libSQL,\n * the open-source fork of SQLite with built-in vector search support.\n * The database is persisted to .kirograph/libsql.db.\n *\n * Opt-in: set config.semanticEngine = 'libsql'\n * Required optional dependency (not installed by default):\n * npm install libsql\n *\n * Key advantages:\n * - Native vector_distance_cos() \u2014 no extensions to load, no WASM overhead\n * - better-sqlite3\u2013compatible synchronous API (fast, no async tax)\n * - FTS5 full-text search for hybrid ranking (vector + keyword blending)\n * - Single-file persistence, portable across platforms with prebuilds\n * - Superset of SQLite \u2014 all standard pragmas and features work\n */\n\nimport * as path from 'path';\nimport { logDebug, logWarn, logError } from '../errors';\nimport type { Node } from '../types';\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst DEFAULT_DIM = 768;\nconst DB_FILE = 'libsql.db';\n\n// Weight for vector score vs full-text score in hybrid ranking (must sum to 1.0)\nconst VECTOR_WEIGHT = 0.7;\nconst FTS_WEIGHT = 0.3;\n\n// \u2500\u2500 LibSQLIndex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class LibSQLIndex {\n private db: any = null;\n private _available = false;\n private dbPath: string;\n\n constructor(\n private readonly kirographDir: string,\n private readonly dim = DEFAULT_DIM,\n ) {\n this.dbPath = path.join(kirographDir, DB_FILE);\n }\n\n isAvailable(): boolean {\n return this._available;\n }\n\n /**\n * Load the libsql package, open the database, and apply the schema.\n * Silent no-op when the optional dep is missing.\n */\n async initialize(): Promise<void> {\n if (this._available) return;\n\n let Database: any;\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mod = require('libsql');\n Database = mod.Database ?? mod.default ?? mod;\n } catch {\n logDebug('LibSQLIndex: libsql not installed \u2014 libSQL engine unavailable');\n return;\n }\n\n try {\n this.db = new Database(this.dbPath);\n\n // Standard SQLite pragmas for performance\n this.db.exec(`\n PRAGMA journal_mode=WAL;\n PRAGMA synchronous=NORMAL;\n PRAGMA cache_size=-32000;\n PRAGMA temp_store=MEMORY;\n `);\n\n // Main table with native libSQL vector column\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS kg_nodes (\n node_id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n kind TEXT NOT NULL DEFAULT '',\n file_path TEXT NOT NULL DEFAULT '',\n signature TEXT NOT NULL DEFAULT '',\n embedding F32_BLOB(${this.dim})\n );\n `);\n\n // FTS5 virtual table for full-text search (hybrid ranking)\n this.db.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS kg_nodes_fts USING fts5(\n node_id UNINDEXED,\n name,\n signature,\n content='kg_nodes',\n content_rowid='rowid'\n );\n `);\n\n // Triggers to keep FTS5 in sync with the main table\n this.db.exec(`\n CREATE TRIGGER IF NOT EXISTS kg_nodes_ai AFTER INSERT ON kg_nodes BEGIN\n INSERT INTO kg_nodes_fts(rowid, node_id, name, signature)\n VALUES (NEW.rowid, NEW.node_id, NEW.name, NEW.signature);\n END;\n\n CREATE TRIGGER IF NOT EXISTS kg_nodes_ad AFTER DELETE ON kg_nodes BEGIN\n INSERT INTO kg_nodes_fts(kg_nodes_fts, rowid, node_id, name, signature)\n VALUES ('delete', OLD.rowid, OLD.node_id, OLD.name, OLD.signature);\n END;\n\n CREATE TRIGGER IF NOT EXISTS kg_nodes_au AFTER UPDATE ON kg_nodes BEGIN\n INSERT INTO kg_nodes_fts(kg_nodes_fts, rowid, node_id, name, signature)\n VALUES ('delete', OLD.rowid, OLD.node_id, OLD.name, OLD.signature);\n INSERT INTO kg_nodes_fts(rowid, node_id, name, signature)\n VALUES (NEW.rowid, NEW.node_id, NEW.name, NEW.signature);\n END;\n `);\n\n this._available = true;\n logDebug('LibSQLIndex: ready', { path: this.dbPath, dim: this.dim });\n } catch (err) {\n logError('LibSQLIndex: initialization failed', { error: String(err) });\n this._available = false;\n if (this.db) {\n try { this.db.close(); } catch { /* ignore */ }\n this.db = null;\n }\n }\n }\n\n /**\n * Insert or update a node's record using SQLite's native ON CONFLICT upsert.\n */\n upsert(node: Node, embedding: Float32Array): void {\n if (!this._available || !this.db) return;\n\n try {\n this.db.prepare(`\n INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (node_id) DO UPDATE SET\n name = excluded.name,\n kind = excluded.kind,\n file_path = excluded.file_path,\n signature = excluded.signature,\n embedding = excluded.embedding\n `).run(\n node.id,\n node.name,\n node.kind,\n node.filePath,\n node.signature ?? '',\n embedding,\n );\n } catch (err) {\n logWarn('LibSQLIndex: upsert failed', { nodeId: node.id, error: String(err) });\n }\n }\n\n /**\n * Bulk upsert for batch embedding. Uses a transaction for atomicity and speed.\n */\n bulkUpsert(nodes: Node[], embeddings: Float32Array[]): void {\n if (!this._available || !this.db) return;\n if (nodes.length === 0) return;\n\n const stmt = this.db.prepare(`\n INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (node_id) DO UPDATE SET\n name = excluded.name,\n kind = excluded.kind,\n file_path = excluded.file_path,\n signature = excluded.signature,\n embedding = excluded.embedding\n `);\n\n const tx = this.db.transaction(() => {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]!;\n stmt.run(\n node.id,\n node.name,\n node.kind,\n node.filePath,\n node.signature ?? '',\n embeddings[i]!,\n );\n }\n });\n\n try {\n tx();\n } catch (err) {\n logWarn('LibSQLIndex: bulkUpsert failed', { count: nodes.length, error: String(err) });\n }\n }\n\n /**\n * Remove a node's record from the index.\n */\n delete(nodeId: string): void {\n if (!this._available || !this.db) return;\n\n try {\n this.db.prepare('DELETE FROM kg_nodes WHERE node_id = ?').run(nodeId);\n } catch (err) {\n logWarn('LibSQLIndex: delete failed', { nodeId, error: String(err) });\n }\n }\n\n /**\n * Hybrid search: combines libSQL native vector_distance_cos with FTS5\n * bm25 ranking. Returns node IDs ordered by combined score.\n *\n * Score = VECTOR_WEIGHT * (1 - cosine_distance) + FTS_WEIGHT * fts_rank\n *\n * Falls back to pure vector search when the query text produces no FTS matches.\n */\n search(queryText: string, queryVec: Float32Array, topN = 10): string[] {\n if (!this._available || !this.db) return [];\n\n try {\n // Try hybrid search first\n const hybridRows = this.db.prepare(`\n SELECT\n n.node_id,\n (${VECTOR_WEIGHT} * (1.0 - vector_distance_cos(n.embedding, ?))\n + ${FTS_WEIGHT} * COALESCE(f.rank_score, 0.0)\n ) AS score\n FROM kg_nodes n\n LEFT JOIN (\n SELECT node_id, -rank AS rank_score\n FROM kg_nodes_fts\n WHERE kg_nodes_fts MATCH ?\n ) f ON f.node_id = n.node_id\n WHERE n.embedding IS NOT NULL\n ORDER BY score DESC\n LIMIT ?\n `).all(queryVec, queryText, topN);\n\n if (hybridRows.length > 0) {\n return hybridRows.map((r: any) => r.node_id as string);\n }\n\n // Fallback: pure vector search (query text may not produce FTS matches)\n return this.vectorSearch(queryVec, topN);\n } catch (err) {\n // If hybrid fails (e.g. FTS parse error), fall back to pure vector\n logDebug('LibSQLIndex: hybrid search failed, trying pure vector', { error: String(err) });\n try {\n return this.vectorSearch(queryVec, topN);\n } catch (vecErr) {\n logWarn('LibSQLIndex: search failed', { error: String(vecErr) });\n return [];\n }\n }\n }\n\n /**\n * Pure vector similarity search using libSQL's native vector_distance_cos.\n */\n private vectorSearch(queryVec: Float32Array, topN: number): string[] {\n const rows = this.db.prepare(`\n SELECT node_id, vector_distance_cos(embedding, ?) AS distance\n FROM kg_nodes\n WHERE embedding IS NOT NULL\n ORDER BY distance ASC\n LIMIT ?\n `).all(queryVec, topN);\n\n return rows.map((r: any) => r.node_id as string);\n }\n\n /** Return all node IDs currently stored in the index. */\n getEmbeddedNodeIds(): string[] {\n if (!this._available || !this.db) return [];\n try {\n const rows = this.db.prepare('SELECT node_id FROM kg_nodes').all();\n return rows.map((r: any) => r.node_id as string);\n } catch {\n return [];\n }\n }\n\n /** Number of documents currently in the index. */\n count(): number {\n if (!this._available || !this.db) return 0;\n try {\n const row = this.db.prepare('SELECT COUNT(*) AS c FROM kg_nodes').get();\n return row ? (row as any).c : 0;\n } catch {\n return 0;\n }\n }\n\n /** Close the database connection and release resources. */\n close(): void {\n if (this.db) {\n try { this.db.close(); } catch { /* ignore */ }\n this.db = null;\n this._available = false;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|