nexus-agents 7.0.2 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{child-mcp-config-ORXLS5HP.js → child-mcp-config-V2ESGPZ4.js} +7 -7
- package/dist/{chunk-RBF64DXJ.js → chunk-2S7Q7GHX.js} +5 -5
- package/dist/{chunk-AGFXJUTS.js → chunk-3GMYW66U.js} +14 -14
- package/dist/{chunk-4OW67KT2.js → chunk-4HXPNLZS.js} +6 -6
- package/dist/{chunk-W4Q5NZYZ.js → chunk-4QHAMZLU.js} +3 -3
- package/dist/{chunk-KSQSWEFH.js → chunk-54Z2II7G.js} +21 -11
- package/dist/chunk-54Z2II7G.js.map +1 -0
- package/dist/{chunk-WLIXCNZ6.js → chunk-5FBPGOKA.js} +24 -24
- package/dist/chunk-5FBPGOKA.js.map +1 -0
- package/dist/{chunk-RSMG2DPB.js → chunk-64GMIUM5.js} +5 -5
- package/dist/{chunk-RLBJTZHA.js → chunk-AHTOZ2UR.js} +2 -2
- package/dist/{chunk-JNZ3D5B5.js → chunk-AL5E5T3O.js} +4 -4
- package/dist/{chunk-3CGUPSBW.js → chunk-BKAUGBSF.js} +2 -2
- package/dist/{chunk-XREG2HTQ.js → chunk-BSWLRSOG.js} +4 -4
- package/dist/{chunk-JJT7NVNY.js → chunk-CHU7NTUR.js} +4 -4
- package/dist/{chunk-UVO673OY.js → chunk-DBDXS7PE.js} +3 -3
- package/dist/{chunk-VYAXMVI5.js → chunk-DEYXS6IZ.js} +5 -5
- package/dist/{chunk-VYAXMVI5.js.map → chunk-DEYXS6IZ.js.map} +1 -1
- package/dist/{chunk-HFOQKCD2.js → chunk-DGWT7DXX.js} +3 -3
- package/dist/{chunk-HFOQKCD2.js.map → chunk-DGWT7DXX.js.map} +1 -1
- package/dist/{chunk-LVYRGNOC.js → chunk-DWUF5YWG.js} +2 -2
- package/dist/{chunk-VVXHIUFQ.js → chunk-EGWPONTU.js} +6 -6
- package/dist/{chunk-OHULIRP6.js → chunk-EXAULYXZ.js} +19 -19
- package/dist/{chunk-K2AWJGQO.js → chunk-FSY2TYFX.js} +8 -8
- package/dist/{chunk-6GYTTWNM.js → chunk-FVHUWXM2.js} +2 -2
- package/dist/{chunk-ZGEDIKAM.js → chunk-FW2FBZBD.js} +4 -4
- package/dist/{chunk-ZD4VEHEG.js → chunk-H6BUSSTF.js} +2 -2
- package/dist/{chunk-D6TL5GZV.js → chunk-HKKLVWRO.js} +5 -5
- package/dist/{chunk-GBHJ37V3.js → chunk-ITCKXMOH.js} +4 -4
- package/dist/{chunk-Y742U2YM.js → chunk-J7SPTP2E.js} +119 -104
- package/dist/chunk-J7SPTP2E.js.map +1 -0
- package/dist/{chunk-KJWD2V4K.js → chunk-JFE3JT5Q.js} +7 -7
- package/dist/chunk-KRG23W3T.js +7 -0
- package/dist/{chunk-33FI7W3G.js → chunk-L2PDPA23.js} +3 -3
- package/dist/{chunk-XZEYSIGG.js → chunk-LOKTE5WE.js} +4 -4
- package/dist/{chunk-KUYKSKPV.js → chunk-LOYIV3HG.js} +214 -219
- package/dist/chunk-LOYIV3HG.js.map +1 -0
- package/dist/{chunk-Q6IODDEA.js → chunk-LVSKLVYC.js} +12 -12
- package/dist/{chunk-Q6IODDEA.js.map → chunk-LVSKLVYC.js.map} +1 -1
- package/dist/{chunk-JEAKVUWM.js → chunk-M6E5E7QV.js} +3 -3
- package/dist/{chunk-OGHEKPLD.js → chunk-MM223CVS.js} +10 -10
- package/dist/{chunk-6Q3KSZVI.js → chunk-MWS56UFF.js} +2 -2
- package/dist/{chunk-GAYSJGKD.js → chunk-OYWFWMLH.js} +13 -13
- package/dist/{chunk-S2Z7CME3.js → chunk-PXJ6CB5V.js} +2 -2
- package/dist/{chunk-Y5GYBP52.js → chunk-QTDUIXOJ.js} +4 -4
- package/dist/{chunk-7U7HAYPB.js → chunk-R4B4UJ6F.js} +25 -59
- package/dist/chunk-R4B4UJ6F.js.map +1 -0
- package/dist/{chunk-QAHGHWC7.js → chunk-WIJ5J7AH.js} +23 -23
- package/dist/{chunk-BTWKDT4F.js → chunk-YJ7Q2NHE.js} +20 -20
- package/dist/{chunk-IWTK6FDT.js → chunk-ZTPBXWA7.js} +4 -4
- package/dist/{cli-circuit-breaker-5FYJDS5O.js → cli-circuit-breaker-YFHX4FN2.js} +5 -5
- package/dist/cli.js +378 -379
- package/dist/cli.js.map +1 -1
- package/dist/{codebase-search-RHBGEWXK.js → codebase-search-3D4TW3KR.js} +2 -2
- package/dist/{composite-router-GE24MYPP.js → composite-router-224FZMI2.js} +4 -4
- package/dist/{consensus-vote-62FAN4ZN.js → consensus-vote-57SKG3EN.js} +22 -22
- package/dist/{context-retriever-3OO6N4V6.js → context-retriever-CCVDD7TO.js} +12 -12
- package/dist/{dist-IKZ53APZ.js → dist-JK32DFHT.js} +2 -2
- package/dist/doctor-deep-37II5V7H.js +13 -0
- package/dist/{doctor-live-J7JIZH3T.js → doctor-live-VXQOZW37.js} +13 -13
- package/dist/{expert-bridge-PWRVRFDG.js → expert-bridge-7LGHU7IL.js} +6 -6
- package/dist/factory-5URERSHK.js +25 -0
- package/dist/factory-SF6ZPN6D.js +15 -0
- package/dist/{improvement-review-QUU5HR7Z.js → improvement-review-2AFBFUQX.js} +8 -8
- package/dist/index.d.ts +58 -56
- package/dist/index.js +65 -76
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-GDPE4U65.js → init-opencode-ZFCKUI2N.js} +10 -10
- package/dist/issue-triage-IVYGGNB4.js +17 -0
- package/dist/{learning-persistence-7O5QYDM7.js → learning-persistence-7N5SZHNX.js} +3 -3
- package/dist/{pr-reviewer-helpers-KBOP72XG.js → pr-reviewer-helpers-TS2HJMZR.js} +6 -6
- package/dist/{registry-command-ZK4NQ2MX.js → registry-command-M6LXKNEY.js} +7 -7
- package/dist/{repo-analyze-UJOWFGNM.js → repo-analyze-DI65XOTQ.js} +2 -2
- package/dist/{repo-security-plan-SZU4O2TN.js → repo-security-plan-LS5GCASE.js} +6 -6
- package/dist/research-helpers-synthesize-NQMSFDMD.js +14 -0
- package/dist/{routing-memory-2SMMUPTB.js → routing-memory-BBHAEJUL.js} +4 -4
- package/dist/{session-memory-YQQVPLKM.js → session-memory-GYIEP7MM.js} +5 -5
- package/dist/setup-config-YIQ7ZZT3.js +11 -0
- package/dist/{setup-custom-api-ZSBUIJID.js → setup-custom-api-CHISHJZV.js} +7 -7
- package/dist/setup-data-dir-KQKFLBKQ.js +29 -0
- package/dist/tool-memory-TGNVGKUT.js +22 -0
- package/dist/tool-memory-registry-adapters-35IPCJLV.js +12 -0
- package/dist/unified-registry-D4QCQ36Z.js +26 -0
- package/dist/{weather-report-6AVPMHHM.js → weather-report-ZLQGZUDL.js} +4 -4
- package/package.json +4 -8
- package/dist/chunk-37JIYDVH.js +0 -7
- package/dist/chunk-7U7HAYPB.js.map +0 -1
- package/dist/chunk-KSQSWEFH.js.map +0 -1
- package/dist/chunk-KUYKSKPV.js.map +0 -1
- package/dist/chunk-WLIXCNZ6.js.map +0 -1
- package/dist/chunk-Y742U2YM.js.map +0 -1
- package/dist/doctor-deep-6WHVRRVQ.js +0 -13
- package/dist/factory-3BZDQQYM.js +0 -25
- package/dist/factory-UFKRSADE.js +0 -15
- package/dist/issue-triage-2LNYGQRJ.js +0 -17
- package/dist/research-helpers-synthesize-QY3DDHTD.js +0 -14
- package/dist/setup-config-LMZE4IRV.js +0 -11
- package/dist/setup-data-dir-ZEH7TFA6.js +0 -29
- package/dist/tool-memory-GUPG2M25.js +0 -22
- package/dist/tool-memory-registry-adapters-IMRSOAAV.js +0 -12
- package/dist/unified-registry-RPT4O3XB.js +0 -26
- package/scripts/postinstall.js +0 -12
- /package/dist/{child-mcp-config-ORXLS5HP.js.map → child-mcp-config-V2ESGPZ4.js.map} +0 -0
- /package/dist/{chunk-RBF64DXJ.js.map → chunk-2S7Q7GHX.js.map} +0 -0
- /package/dist/{chunk-AGFXJUTS.js.map → chunk-3GMYW66U.js.map} +0 -0
- /package/dist/{chunk-4OW67KT2.js.map → chunk-4HXPNLZS.js.map} +0 -0
- /package/dist/{chunk-W4Q5NZYZ.js.map → chunk-4QHAMZLU.js.map} +0 -0
- /package/dist/{chunk-RSMG2DPB.js.map → chunk-64GMIUM5.js.map} +0 -0
- /package/dist/{chunk-RLBJTZHA.js.map → chunk-AHTOZ2UR.js.map} +0 -0
- /package/dist/{chunk-JNZ3D5B5.js.map → chunk-AL5E5T3O.js.map} +0 -0
- /package/dist/{chunk-3CGUPSBW.js.map → chunk-BKAUGBSF.js.map} +0 -0
- /package/dist/{chunk-XREG2HTQ.js.map → chunk-BSWLRSOG.js.map} +0 -0
- /package/dist/{chunk-JJT7NVNY.js.map → chunk-CHU7NTUR.js.map} +0 -0
- /package/dist/{chunk-UVO673OY.js.map → chunk-DBDXS7PE.js.map} +0 -0
- /package/dist/{chunk-LVYRGNOC.js.map → chunk-DWUF5YWG.js.map} +0 -0
- /package/dist/{chunk-VVXHIUFQ.js.map → chunk-EGWPONTU.js.map} +0 -0
- /package/dist/{chunk-OHULIRP6.js.map → chunk-EXAULYXZ.js.map} +0 -0
- /package/dist/{chunk-K2AWJGQO.js.map → chunk-FSY2TYFX.js.map} +0 -0
- /package/dist/{chunk-6GYTTWNM.js.map → chunk-FVHUWXM2.js.map} +0 -0
- /package/dist/{chunk-ZGEDIKAM.js.map → chunk-FW2FBZBD.js.map} +0 -0
- /package/dist/{chunk-ZD4VEHEG.js.map → chunk-H6BUSSTF.js.map} +0 -0
- /package/dist/{chunk-D6TL5GZV.js.map → chunk-HKKLVWRO.js.map} +0 -0
- /package/dist/{chunk-GBHJ37V3.js.map → chunk-ITCKXMOH.js.map} +0 -0
- /package/dist/{chunk-KJWD2V4K.js.map → chunk-JFE3JT5Q.js.map} +0 -0
- /package/dist/{chunk-37JIYDVH.js.map → chunk-KRG23W3T.js.map} +0 -0
- /package/dist/{chunk-33FI7W3G.js.map → chunk-L2PDPA23.js.map} +0 -0
- /package/dist/{chunk-XZEYSIGG.js.map → chunk-LOKTE5WE.js.map} +0 -0
- /package/dist/{chunk-JEAKVUWM.js.map → chunk-M6E5E7QV.js.map} +0 -0
- /package/dist/{chunk-OGHEKPLD.js.map → chunk-MM223CVS.js.map} +0 -0
- /package/dist/{chunk-6Q3KSZVI.js.map → chunk-MWS56UFF.js.map} +0 -0
- /package/dist/{chunk-GAYSJGKD.js.map → chunk-OYWFWMLH.js.map} +0 -0
- /package/dist/{chunk-S2Z7CME3.js.map → chunk-PXJ6CB5V.js.map} +0 -0
- /package/dist/{chunk-Y5GYBP52.js.map → chunk-QTDUIXOJ.js.map} +0 -0
- /package/dist/{chunk-QAHGHWC7.js.map → chunk-WIJ5J7AH.js.map} +0 -0
- /package/dist/{chunk-BTWKDT4F.js.map → chunk-YJ7Q2NHE.js.map} +0 -0
- /package/dist/{chunk-IWTK6FDT.js.map → chunk-ZTPBXWA7.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-5FYJDS5O.js.map → cli-circuit-breaker-YFHX4FN2.js.map} +0 -0
- /package/dist/{codebase-search-RHBGEWXK.js.map → codebase-search-3D4TW3KR.js.map} +0 -0
- /package/dist/{composite-router-GE24MYPP.js.map → composite-router-224FZMI2.js.map} +0 -0
- /package/dist/{consensus-vote-62FAN4ZN.js.map → consensus-vote-57SKG3EN.js.map} +0 -0
- /package/dist/{context-retriever-3OO6N4V6.js.map → context-retriever-CCVDD7TO.js.map} +0 -0
- /package/dist/{dist-IKZ53APZ.js.map → dist-JK32DFHT.js.map} +0 -0
- /package/dist/{doctor-deep-6WHVRRVQ.js.map → doctor-deep-37II5V7H.js.map} +0 -0
- /package/dist/{doctor-live-J7JIZH3T.js.map → doctor-live-VXQOZW37.js.map} +0 -0
- /package/dist/{expert-bridge-PWRVRFDG.js.map → expert-bridge-7LGHU7IL.js.map} +0 -0
- /package/dist/{factory-3BZDQQYM.js.map → factory-5URERSHK.js.map} +0 -0
- /package/dist/{factory-UFKRSADE.js.map → factory-SF6ZPN6D.js.map} +0 -0
- /package/dist/{improvement-review-QUU5HR7Z.js.map → improvement-review-2AFBFUQX.js.map} +0 -0
- /package/dist/{init-opencode-GDPE4U65.js.map → init-opencode-ZFCKUI2N.js.map} +0 -0
- /package/dist/{issue-triage-2LNYGQRJ.js.map → issue-triage-IVYGGNB4.js.map} +0 -0
- /package/dist/{learning-persistence-7O5QYDM7.js.map → learning-persistence-7N5SZHNX.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-KBOP72XG.js.map → pr-reviewer-helpers-TS2HJMZR.js.map} +0 -0
- /package/dist/{registry-command-ZK4NQ2MX.js.map → registry-command-M6LXKNEY.js.map} +0 -0
- /package/dist/{repo-analyze-UJOWFGNM.js.map → repo-analyze-DI65XOTQ.js.map} +0 -0
- /package/dist/{repo-security-plan-SZU4O2TN.js.map → repo-security-plan-LS5GCASE.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-QY3DDHTD.js.map → research-helpers-synthesize-NQMSFDMD.js.map} +0 -0
- /package/dist/{routing-memory-2SMMUPTB.js.map → routing-memory-BBHAEJUL.js.map} +0 -0
- /package/dist/{session-memory-YQQVPLKM.js.map → session-memory-GYIEP7MM.js.map} +0 -0
- /package/dist/{setup-config-LMZE4IRV.js.map → setup-config-YIQ7ZZT3.js.map} +0 -0
- /package/dist/{setup-custom-api-ZSBUIJID.js.map → setup-custom-api-CHISHJZV.js.map} +0 -0
- /package/dist/{setup-data-dir-ZEH7TFA6.js.map → setup-data-dir-KQKFLBKQ.js.map} +0 -0
- /package/dist/{tool-memory-GUPG2M25.js.map → tool-memory-TGNVGKUT.js.map} +0 -0
- /package/dist/{tool-memory-registry-adapters-IMRSOAAV.js.map → tool-memory-registry-adapters-35IPCJLV.js.map} +0 -0
- /package/dist/{unified-registry-RPT4O3XB.js.map → unified-registry-D4QCQ36Z.js.map} +0 -0
- /package/dist/{weather-report-6AVPMHHM.js.map → weather-report-ZLQGZUDL.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp/tools/scanner-registry-fetcher.ts","../src/mcp/tools/repo-security-plan-fallback.ts","../src/mcp/tools/repo-security-plan.ts"],"sourcesContent":["/**\n * nexus-agents/mcp - Scanner Registry Fetcher\n *\n * Fetches the scanner-registry.json manifest from the\n * vulnerability-scanner-registry GitHub Releases at runtime.\n * Uses a TTL cache and falls back to embedded data on failure.\n *\n * @module mcp/tools/scanner-registry-fetcher\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport { z } from 'zod';\nimport { createLogger, getTimeProvider } from '../../core/index.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** A scanner entry from the registry manifest. */\nexport interface RegistryScanner {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly relationships?: readonly RegistryRelationship[] | undefined;\n}\n\n/** A relationship edge between scanners. */\nexport interface RegistryRelationship {\n readonly target: string;\n readonly type: 'uses' | 'supersedes' | 'bundles' | 'competes-with';\n}\n\n/** Language matrix: category → scanner names. */\nexport interface LanguageMatrixEntry {\n readonly sast?: readonly string[] | undefined;\n readonly sca?: readonly string[] | undefined;\n readonly secrets?: readonly string[] | undefined;\n readonly container?: readonly string[] | undefined;\n readonly iac?: readonly string[] | undefined;\n readonly dast?: readonly string[] | undefined;\n}\n\n/** The full registry manifest shape. */\nexport interface ScannerRegistryManifest {\n readonly version: string;\n readonly generatedAt: string;\n readonly scanners: readonly RegistryScanner[];\n readonly languageMatrix: Readonly<Record<string, LanguageMatrixEntry>>;\n}\n\n// ============================================================================\n// Zod Schema for Validation\n// ============================================================================\n\nconst RelationshipSchema = z.object({\n target: z.string().min(1),\n type: z.enum(['uses', 'supersedes', 'bundles', 'competes-with']),\n});\n\nconst ScannerSchema = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n categories: z.array(z.string().min(1)),\n license: z.string().min(1),\n pricingModel: z.string().min(1),\n relationships: z.array(RelationshipSchema).optional(),\n});\n\nconst LanguageMatrixEntrySchema = z\n .object({\n sast: z.array(z.string()).optional(),\n sca: z.array(z.string()).optional(),\n secrets: z.array(z.string()).optional(),\n container: z.array(z.string()).optional(),\n iac: z.array(z.string()).optional(),\n dast: z.array(z.string()).optional(),\n })\n .loose();\n\nconst ManifestSchema = z.object({\n version: z.string().min(1),\n generatedAt: z.string().min(1),\n scanners: z.array(ScannerSchema),\n languageMatrix: z.record(z.string().max(50), LanguageMatrixEntrySchema),\n});\n\n// ============================================================================\n// Cache\n// ============================================================================\n\ninterface CacheEntry {\n manifest: ScannerRegistryManifest;\n fetchedAt: number;\n releaseTag: string;\n}\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\nlet cachedEntry: CacheEntry | null = null;\n\n/** Inflight fetch promise for probe coalescing (#1448). */\nlet inflightFetch: Promise<ScannerRegistryManifest | null> | undefined;\n\n/** Clear the cache and inflight state (for testing). */\nexport function clearRegistryCache(): void {\n cachedEntry = null;\n inflightFetch = undefined;\n}\n\n// ============================================================================\n// Fetcher\n// ============================================================================\n\nconst REGISTRY_REPO = 'williamzujkowski/vulnerability-scanner-registry';\nconst FETCH_TIMEOUT_MS = 10_000;\n\n/** Promisified execFile signature used by fetcher helpers. */\ntype ExecFileAsync = (\n file: string,\n args: readonly string[],\n options: { timeout?: number; maxBuffer?: number }\n) => Promise<{ stdout: string; stderr: string }>;\n\nconst logger = createLogger({ component: 'scanner-registry-fetcher' });\n\n/** Get the latest release tag name (lightweight check, no download). */\nasync function getLatestReleaseTag(execFileAsync: ExecFileAsync): Promise<string | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n ['release', 'view', '--repo', REGISTRY_REPO, '--json', 'tagName', '--jq', '.tagName'],\n { timeout: FETCH_TIMEOUT_MS }\n );\n return stdout.trim() || null;\n}\n\n/** Download and parse the full manifest. */\nasync function downloadManifest(\n execFileAsync: ExecFileAsync\n): Promise<ScannerRegistryManifest | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n [\n 'release',\n 'download',\n '--repo',\n REGISTRY_REPO,\n '--pattern',\n 'scanner-registry.json',\n '--output',\n '-',\n ],\n { timeout: FETCH_TIMEOUT_MS, maxBuffer: 1024 * 1024 }\n );\n\n let jsonData: unknown;\n try {\n jsonData = JSON.parse(stdout);\n } catch {\n logger.warn('Registry manifest is not valid JSON', {\n stdoutLength: stdout.length,\n preview: stdout.slice(0, 100),\n });\n return null;\n }\n\n const parsed = ManifestSchema.safeParse(jsonData);\n if (!parsed.success) {\n logger.warn('Registry manifest failed schema validation', {\n errors: parsed.error.issues.slice(0, 3),\n });\n return null;\n }\n\n logger.info('Fetched scanner registry manifest', {\n version: parsed.data.version,\n scanners: parsed.data.scanners.length,\n languages: Object.keys(parsed.data.languageMatrix).length,\n });\n return parsed.data;\n}\n\n/**\n * Fetch the scanner registry manifest from GitHub Releases.\n * If we have a cached version and the release tag hasn't changed,\n * just refreshes the cache timer (no download).\n */\nasync function fetchManifestFromGitHub(): Promise<ScannerRegistryManifest | null> {\n try {\n const { execFile } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execFileAsync = promisify(execFile);\n\n const tag = await getLatestReleaseTag(execFileAsync);\n if (tag === null) {\n logger.warn('No releases found in scanner registry');\n return null;\n }\n\n // If cached version matches the latest tag, refresh timer only\n if (cachedEntry !== null && cachedEntry.releaseTag === tag) {\n logger.debug('Scanner registry unchanged, refreshing cache timer', { tag });\n cachedEntry = { ...cachedEntry, fetchedAt: getTimeProvider().now() };\n return cachedEntry.manifest;\n }\n\n // New release — download full manifest\n const manifest = await downloadManifest(execFileAsync);\n if (manifest !== null) {\n cachedEntry = { manifest, fetchedAt: getTimeProvider().now(), releaseTag: tag };\n }\n return manifest;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug('Failed to fetch scanner registry', { error: msg });\n return null;\n }\n}\n\n/**\n * Get the scanner registry, fetching from GitHub if cache is stale.\n * Returns null if no cached data and fetch fails.\n */\nexport async function getRegistryManifest(): Promise<ScannerRegistryManifest | null> {\n // Check cache\n if (cachedEntry !== null) {\n const age = getTimeProvider().now() - cachedEntry.fetchedAt;\n if (age < CACHE_TTL_MS) {\n return cachedEntry.manifest;\n }\n }\n\n // Coalesce concurrent fetches — only one inflight request at a time (#1448)\n inflightFetch ??= fetchManifestFromGitHub().finally(() => {\n inflightFetch = undefined;\n });\n const manifest = await inflightFetch;\n if (manifest !== null) {\n return manifest;\n }\n\n // Return stale cache if available\n if (cachedEntry !== null) {\n logger.warn('Using stale cached registry manifest');\n return cachedEntry.manifest;\n }\n\n return null;\n}\n\n/**\n * Extract scanners from manifest into the format expected by plan builder.\n */\nexport function extractScannerEntries(\n manifest: ScannerRegistryManifest\n): readonly RegistryScanner[] {\n return manifest.scanners;\n}\n\n/**\n * Extract language matrix, normalizing to consistent category keys.\n */\nexport function extractLanguageMatrix(\n manifest: ScannerRegistryManifest\n): Readonly<Record<string, LanguageMatrixEntry>> {\n return manifest.languageMatrix;\n}\n","/**\n * nexus-agents/mcp - Fallback Scanner Data\n *\n * Embedded snapshot of the vulnerability-scanner-registry manifest.\n * Used when the live registry fetch fails (network issues, gh CLI\n * unavailable, etc.). Updated periodically from the canonical\n * registry at github.com/williamzujkowski/vulnerability-scanner-registry.\n *\n * @module mcp/tools/repo-security-plan-fallback\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport type { ScannerData } from './repo-security-plan.js';\n\n// ============================================================================\n// Fallback Scanner Entries (27 scanners)\n// ============================================================================\n\nconst FALLBACK_SCANNERS: ScannerData['scanners'] = [\n {\n name: 'semgrep',\n displayName: 'Semgrep',\n categories: ['sast', 'secrets'],\n license: 'LGPL-2.1',\n pricingModel: 'freemium',\n },\n {\n name: 'codeql',\n displayName: 'CodeQL',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'bandit',\n displayName: 'Bandit',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gosec',\n displayName: 'Gosec',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'brakeman',\n displayName: 'Brakeman',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'phpstan',\n displayName: 'PHPStan',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'shellcheck',\n displayName: 'ShellCheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'cppcheck',\n displayName: 'Cppcheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'detekt',\n displayName: 'detekt',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'spotbugs',\n displayName: 'SpotBugs',\n categories: ['sast'],\n license: 'LGPL-2.1',\n pricingModel: 'free',\n },\n {\n name: 'eslint-security',\n displayName: 'eslint-plugin-security',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'sonarqube',\n displayName: 'SonarQube',\n categories: ['sast', 'sca'],\n license: 'LGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'osv-scanner',\n displayName: 'OSV-Scanner',\n categories: ['sca', 'container', 'iac', 'sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype',\n displayName: 'Grype',\n categories: ['sca', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'snyk',\n displayName: 'Snyk',\n categories: ['sca', 'sast', 'container'],\n license: 'Proprietary',\n pricingModel: 'freemium',\n },\n {\n name: 'npm-audit',\n displayName: 'npm audit',\n categories: ['sca'],\n license: 'Artistic-2.0',\n pricingModel: 'free',\n },\n {\n name: 'pip-audit',\n displayName: 'pip-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'cargo-audit',\n displayName: 'cargo-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'bundler-audit',\n displayName: 'bundler-audit',\n categories: ['sca'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'govulncheck',\n displayName: 'govulncheck',\n categories: ['sca'],\n license: 'BSD-3-Clause',\n pricingModel: 'free',\n },\n {\n name: 'owasp-dependency-check',\n displayName: 'OWASP Dependency-Check',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gitleaks',\n displayName: 'Gitleaks',\n categories: ['secrets'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'trufflehog',\n displayName: 'TruffleHog',\n categories: ['secrets'],\n license: 'AGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'checkov',\n displayName: 'Checkov',\n categories: ['iac', 'sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'tfsec',\n displayName: 'tfsec',\n categories: ['iac'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'owasp-zap',\n displayName: 'OWASP ZAP',\n categories: ['dast', 'api'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'syft',\n displayName: 'Syft',\n categories: ['sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype-image',\n displayName: 'Grype (image scan)',\n categories: ['image-currency', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n];\n\n// ============================================================================\n// Fallback Language Map (16 languages)\n// ============================================================================\n\nconst FALLBACK_LANGUAGE_MAP: ScannerData['languageMap'] = {\n TypeScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n JavaScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Python: {\n sast: ['bandit', 'semgrep', 'codeql'],\n sca: ['pip-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Java: {\n sast: ['codeql', 'semgrep', 'spotbugs'],\n sca: ['owasp-dependency-check', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Go: {\n sast: ['gosec', 'semgrep', 'codeql'],\n sca: ['govulncheck', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Ruby: {\n sast: ['brakeman', 'semgrep', 'codeql'],\n sca: ['bundler-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n PHP: {\n sast: ['phpstan', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C#': {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n C: {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C++': {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Rust: {\n sast: ['semgrep'],\n sca: ['cargo-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Kotlin: {\n sast: ['detekt', 'semgrep', 'codeql'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Swift: {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Scala: {\n sast: ['semgrep', 'spotbugs'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Shell: {\n sast: ['shellcheck', 'semgrep'],\n sca: [],\n secrets: ['gitleaks'],\n },\n HCL: {\n sast: ['checkov', 'tfsec'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n};\n\n// ============================================================================\n// Exported Fallback\n// ============================================================================\n\n/** Embedded scanner data snapshot used when live registry is unavailable. */\nexport const FALLBACK_SCANNER_DATA: ScannerData = {\n scanners: FALLBACK_SCANNERS,\n languageMap: FALLBACK_LANGUAGE_MAP,\n source: 'fallback',\n};\n","/**\n * nexus-agents/mcp - Repository Security Plan Logic\n *\n * Generates a language-aware security scanning pipeline recommendation\n * by composing repo_analyze output with scanner registry data.\n * Fetches fresh data from vulnerability-scanner-registry GitHub Releases;\n * falls back to embedded snapshot if fetch fails.\n *\n * @module mcp/tools/repo-security-plan\n * (Source: Issue #1079, externalization vote 6-0 unanimous)\n */\n\nimport type { RepoAnalysis } from './repo-analyze-types.js';\nimport type {\n RepoSecurityPlanInput,\n RepoSecurityPlan,\n ScannerRecommendation,\n ConflictWarning,\n CoverageAnalysis,\n} from './repo-security-plan-types.js';\nimport { analyzeGitHubRepo } from './repo-analyze.js';\nimport { getRegistryManifest } from './scanner-registry-fetcher.js';\nimport type { RegistryScanner, LanguageMatrixEntry } from './scanner-registry-fetcher.js';\nimport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\nimport { createLogger } from '../../core/index.js';\n\nconst logger = createLogger({ component: 'repo-security-plan' });\n\n// ============================================================================\n// Scanner Data Interface (common shape for fetched + fallback)\n// ============================================================================\n\n/** Internal scanner entry used by plan builder. */\nexport interface ScannerEntry {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly supersedes?: readonly string[];\n}\n\n/** Language mapping: category → scanner names. */\ninterface LanguageMapping {\n readonly sast: readonly string[];\n readonly sca: readonly string[];\n readonly secrets: readonly string[];\n}\n\n/** Resolved scanner data for plan building. */\nexport interface ScannerData {\n readonly scanners: readonly ScannerEntry[];\n readonly languageMap: Readonly<Record<string, LanguageMapping>>;\n readonly source: 'registry' | 'fallback';\n}\n\n// Re-export for consumers\nexport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\n\n// ============================================================================\n// Registry → ScannerData Conversion\n// ============================================================================\n\nfunction convertRegistryScanner(s: RegistryScanner): ScannerEntry {\n const supersedes = s.relationships?.filter((r) => r.type === 'supersedes').map((r) => r.target);\n return {\n name: s.name,\n displayName: s.displayName,\n categories: s.categories,\n license: s.license,\n pricingModel: s.pricingModel,\n ...(supersedes !== undefined && supersedes.length > 0 ? { supersedes } : {}),\n };\n}\n\n/** Known PascalCase language names from GitHub API. Handles registry keys like \"typescript\" → \"TypeScript\". */\nconst LANGUAGE_PASCAL_MAP: Readonly<Record<string, string>> = {\n typescript: 'TypeScript',\n javascript: 'JavaScript',\n python: 'Python',\n java: 'Java',\n csharp: 'C#',\n 'c#': 'C#',\n cpp: 'C++',\n 'c++': 'C++',\n go: 'Go',\n rust: 'Rust',\n ruby: 'Ruby',\n php: 'PHP',\n swift: 'Swift',\n kotlin: 'Kotlin',\n scala: 'Scala',\n hcl: 'HCL',\n shell: 'Shell',\n dockerfile: 'Dockerfile',\n};\n\nfunction normalizeLangName(lang: string): string {\n const lower = lang.toLowerCase();\n return LANGUAGE_PASCAL_MAP[lower] ?? lang.charAt(0).toUpperCase() + lang.slice(1);\n}\n\nfunction convertLanguageMatrix(\n matrix: Readonly<Record<string, LanguageMatrixEntry>>\n): Record<string, LanguageMapping> {\n const result: Record<string, LanguageMapping> = {};\n for (const [lang, entry] of Object.entries(matrix)) {\n // Normalize language name to PascalCase (GitHub API returns PascalCase like \"TypeScript\")\n const normalized = normalizeLangName(lang);\n result[normalized] = {\n sast: entry.sast ?? [],\n sca: entry.sca ?? [],\n secrets: entry.secrets ?? [],\n };\n }\n return result;\n}\n\n/** Resolve scanner data: fetch from registry, fall back to embedded. */\nexport async function resolveScannerData(): Promise<ScannerData> {\n const manifest = await getRegistryManifest();\n if (manifest !== null) {\n logger.info('Using live scanner registry', {\n version: manifest.version,\n scanners: manifest.scanners.length,\n });\n return {\n scanners: manifest.scanners.map(convertRegistryScanner),\n languageMap: convertLanguageMatrix(manifest.languageMatrix),\n source: 'registry',\n };\n }\n\n logger.info('Using fallback scanner data');\n return FALLBACK_SCANNER_DATA;\n}\n\n// ============================================================================\n// CI Snippet Generation (GitHub Actions only for v1)\n// ============================================================================\n\nconst CI_SNIPPETS: Readonly<Record<string, string>> = {\n semgrep: '- uses: semgrep/semgrep-action@v1\\n with:\\n config: auto',\n codeql: '- uses: github/codeql-action/analyze@v3',\n grype: '- uses: anchore/scan-action@v4\\n with:\\n path: .',\n 'grype-image':\n '- uses: anchore/scan-action@v4\\n with:\\n image: ${{ env.IMAGE_TAG }}\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n gitleaks: '- uses: gitleaks/gitleaks-action@v2',\n bandit: '- run: pip install bandit && bandit -r . -f json',\n gosec: '- uses: securego/gosec@master\\n with:\\n args: ./...',\n checkov: '- uses: bridgecrewio/checkov-action@master',\n 'osv-scanner': '- uses: google/osv-scanner-action@v1',\n snyk: '- uses: snyk/actions/node@master # adjust for language',\n shellcheck: '- uses: ludeeus/action-shellcheck@master',\n // #2732: scanners recommended for TypeScript repos that were missing\n // snippets, leaving most rows with ciSnippet: null.\n 'eslint-security':\n '- run: npm install --save-dev eslint-plugin-security && npx eslint --plugin security .',\n sonarqube:\n '- uses: sonarsource/sonarqube-scan-action@v3\\n env:\\n SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}',\n 'npm-audit': '- run: npm audit --audit-level=high',\n trivy:\n '- uses: aquasecurity/trivy-action@master\\n with:\\n scan-type: fs\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n trufflehog: '- uses: trufflesecurity/trufflehog@main\\n with:\\n extra_args: --only-verified',\n cppcheck: '- run: cppcheck --enable=all --error-exitcode=1 .',\n spotbugs:\n '- uses: jwgmeligmeyling/spotbugs-github-action@master\\n with:\\n path: target/spotbugsXml.xml',\n 'pip-audit': '- run: pip install pip-audit && pip-audit',\n 'cargo-audit': '- run: cargo install cargo-audit --locked && cargo audit',\n 'bundler-audit': '- run: gem install bundler-audit && bundle-audit check --update',\n 'composer-audit': '- run: composer audit',\n govulncheck: '- run: go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./...',\n detekt: '- uses: natiginfo/action-detekt-all@1.23.6',\n brakeman: '- run: gem install brakeman && brakeman -f json',\n phpstan: '- run: composer install && vendor/bin/phpstan analyse',\n tfsec: '- uses: aquasecurity/tfsec-action@v1.0.3',\n 'owasp-dependency-check':\n '- uses: dependency-check/Dependency-Check_Action@main\\n with:\\n project: ${{ github.event.repository.name }}\\n path: .\\n format: HTML',\n 'owasp-zap':\n '- uses: zaproxy/action-baseline@v0.12.0\\n with:\\n target: ${{ env.TARGET_URL }}',\n syft: '- uses: anchore/sbom-action@v0\\n with:\\n path: .\\n format: cyclonedx-json',\n};\n\nfunction generateCiSnippet(name: string, ci: string | null): string | null {\n if (ci !== 'github-actions') return null;\n return CI_SNIPPETS[name] ?? null;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction findScanner(name: string, scanners: readonly ScannerEntry[]): ScannerEntry | undefined {\n return scanners.find((s) => s.name === name);\n}\n\nfunction isAlreadyUsed(name: string, existing: readonly string[]): boolean {\n return existing.some((t) => t.toLowerCase().includes(name.toLowerCase()));\n}\n\n/** Context passed to recommendation collectors. */\ninterface RecContext {\n readonly existing: readonly string[];\n readonly ciProvider: string | null;\n readonly language: string | null;\n readonly categoryFilter: ReadonlySet<string> | null;\n readonly maxScanners: number;\n readonly scanners: readonly ScannerEntry[];\n}\n\n/** Options for collecting recommendations in a single category. */\ninterface CategoryRecOptions {\n readonly names: readonly string[];\n readonly category: string;\n readonly rationale: (entry: ScannerEntry) => string;\n readonly priority: 'critical' | 'recommended';\n readonly ctx: RecContext;\n}\n\n/** Collect recommendations for a single category. */\nfunction collectCategoryRecs(recs: ScannerRecommendation[], opts: CategoryRecOptions): void {\n // #2732: at most one `critical` slot per category — the first scanner\n // picked is the \"must run\" one, subsequent scanners in the same\n // category demote to `recommended`. Pre-fix SAST already enforced this\n // (an inline `isFirst` flag for category === 'sast'); SCA and secrets\n // skipped the demotion and marked every entry critical, so a TypeScript\n // plan came back with 3+ critical scanners. `opts.priority` is now the\n // priority applied to the *primary* slot only; remaining slots are\n // always `recommended` regardless of what the caller passed.\n let primarySlotFilled = recs.some((r) => r.category === opts.category);\n for (const name of opts.names) {\n if (recs.length >= opts.ctx.maxScanners) break;\n if (isAlreadyUsed(name, opts.ctx.existing)) continue;\n const entry = findScanner(name, opts.ctx.scanners);\n if (!entry) continue;\n if (opts.ctx.categoryFilter && !opts.ctx.categoryFilter.has(opts.category)) continue;\n const priority = primarySlotFilled ? 'recommended' : opts.priority;\n primarySlotFilled = true;\n recs.push({\n name,\n displayName: entry.displayName,\n category: opts.category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale: opts.rationale(entry),\n priority,\n ciSnippet: generateCiSnippet(name, opts.ctx.ciProvider),\n });\n }\n}\n\n/** Collect language-specific recommendations (SAST + SCA + secrets). */\nfunction collectLanguageRecs(\n langMap: LanguageMapping,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n const lang = ctx.language ?? 'unknown';\n collectCategoryRecs(recs, {\n names: langMap.sast,\n category: 'sast',\n rationale: (e) => `${e.displayName} provides SAST for ${lang}`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.sca,\n category: 'sca',\n rationale: (e) => `${e.displayName} provides SCA for ${lang} dependencies`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.secrets,\n category: 'secrets',\n rationale: () => 'Detects leaked credentials and API keys in source code',\n priority: 'critical',\n ctx,\n });\n}\n\n/** Try to add a single scanner if not already present. */\nfunction tryAddScanner(\n scannerName: string,\n category: string,\n rationale: string,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (recs.length >= ctx.maxScanners) return;\n if (ctx.categoryFilter && !ctx.categoryFilter.has(category)) return;\n if (isAlreadyUsed(scannerName, ctx.existing)) return;\n if (recs.some((r) => r.name === scannerName)) return;\n const entry = findScanner(scannerName, ctx.scanners);\n if (!entry) return;\n recs.push({\n name: scannerName,\n displayName: entry.displayName,\n category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale,\n priority: 'recommended',\n ciSnippet: generateCiSnippet(scannerName, ctx.ciProvider),\n });\n}\n\n// ============================================================================\n// Conflict Detection\n// ============================================================================\n\nfunction detectConflicts(\n recs: readonly ScannerRecommendation[],\n scanners: readonly ScannerEntry[]\n): readonly ConflictWarning[] {\n const warnings: ConflictWarning[] = [];\n const names = new Set(recs.map((r) => r.name));\n detectSuperseded(names, scanners, warnings);\n detectRedundant(recs, warnings);\n return warnings;\n}\n\nfunction detectSuperseded(\n names: ReadonlySet<string>,\n scanners: readonly ScannerEntry[],\n warnings: ConflictWarning[]\n): void {\n for (const scanner of scanners) {\n if (!names.has(scanner.name)) continue;\n if (!scanner.supersedes) continue;\n for (const old of scanner.supersedes) {\n if (names.has(old)) {\n warnings.push({\n scanners: [old, scanner.name],\n type: 'superseded',\n recommendation: `${scanner.displayName} supersedes ${old}. Remove ${old}.`,\n });\n }\n }\n }\n}\n\nfunction detectRedundant(\n recs: readonly ScannerRecommendation[],\n warnings: ConflictWarning[]\n): void {\n const catMap = new Map<string, string[]>();\n for (const rec of recs) {\n const arr = catMap.get(rec.category) ?? [];\n arr.push(rec.name);\n catMap.set(rec.category, arr);\n }\n for (const [cat, scanners] of catMap) {\n if (scanners.length > 2) {\n const count = String(scanners.length);\n warnings.push({\n scanners,\n type: 'redundant',\n recommendation: `${count} scanners for ${cat}. Consider keeping top 2.`,\n });\n }\n }\n}\n\n// ============================================================================\n// Coverage Analysis\n// ============================================================================\n\nconst ALL_CATEGORIES = ['sast', 'dast', 'sca', 'secrets', 'container', 'iac', 'image-currency'];\n\nfunction buildCoverage(\n recs: readonly ScannerRecommendation[],\n existing: readonly string[],\n scanners: readonly ScannerEntry[]\n): readonly CoverageAnalysis[] {\n return ALL_CATEGORIES.map((cat) => {\n const found = recs.filter((r) => r.category === cat).map((r) => r.name);\n const existingMatch = existing.some((t) =>\n scanners.some((s) => s.categories.includes(cat) && t.toLowerCase().includes(s.name))\n );\n return { category: cat, covered: found.length > 0 || existingMatch, scanners: found };\n });\n}\n\n// ============================================================================\n// Plan Assembly\n// ============================================================================\n\n/** Options for buildPlanFromAnalysis (allows optional fields for testability). */\ninterface BuildPlanOptions {\n readonly repo: string;\n readonly categories?: readonly string[] | undefined;\n readonly maxScanners?: number | undefined;\n}\n\n/** Generate a security scanning plan for a repository (fetches live data). */\nexport async function generateSecurityPlan(\n input: RepoSecurityPlanInput\n): Promise<RepoSecurityPlan> {\n const [analysis, data] = await Promise.all([\n analyzeGitHubRepo({ repo: input.repo, depth: 'deep' }),\n resolveScannerData(),\n ]);\n return buildPlanFromAnalysis(analysis, input, data);\n}\n\n/** Collect infrastructure-specific scanner recommendations. */\nfunction collectInfraRecs(\n analysis: RepoAnalysis,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (analysis.hasDockerfile) {\n tryAddScanner(\n 'grype',\n 'container',\n 'Dockerfile detected — scan container images for vulnerabilities',\n recs,\n ctx\n );\n tryAddScanner('grype-image', 'image-currency', buildImageCurrencyRationale(), recs, ctx);\n }\n if (analysis.hasHelmCharts) {\n tryAddScanner(\n 'checkov',\n 'iac',\n 'Helm charts detected — scan IaC for misconfigurations',\n recs,\n ctx\n );\n }\n}\n\n/**\n * Build the rationale string for periodic container base image CVE scanning.\n * Extracted to keep collectInfraRecs within the 50-line function limit.\n */\nfunction buildImageCurrencyRationale(): string {\n return (\n 'Dockerfile detected — periodically scan built images with ' +\n '`grype image --severity CRITICAL,HIGH` to detect CVEs introduced by stale base images. ' +\n 'Pin base images to specific version tags (e.g., node:22.4.0-alpine3.20) rather than ' +\n ':latest to get reproducible scans and predictable CVE surface area. ' +\n 'Alpine-based images typically have a smaller CVE surface than Debian/Ubuntu equivalents ' +\n 'due to musl libc and a minimal package set, but verify with grype before assuming.'\n );\n}\n\n/** Pure function: build plan from analysis + scanner data (testable). */\nexport function buildPlanFromAnalysis(\n analysis: RepoAnalysis,\n input: BuildPlanOptions,\n data?: ScannerData\n): RepoSecurityPlan {\n const resolved = data ?? FALLBACK_SCANNER_DATA;\n const ctx: RecContext = {\n existing: analysis.securityTooling,\n ciProvider: analysis.ciProvider,\n language: analysis.language,\n categoryFilter: input.categories ? new Set(input.categories) : null,\n maxScanners: input.maxScanners ?? 10,\n scanners: resolved.scanners,\n };\n\n const recs: ScannerRecommendation[] = [];\n const normalizedLang = analysis.language !== null ? normalizeLangName(analysis.language) : null;\n const langMap = normalizedLang !== null ? resolved.languageMap[normalizedLang] : undefined;\n if (langMap) collectLanguageRecs(langMap, recs, ctx);\n collectInfraRecs(analysis, recs, ctx);\n\n const conflicts = detectConflicts(recs, resolved.scanners);\n const coverage = buildCoverage(recs, analysis.securityTooling, resolved.scanners);\n const uncovered = coverage.filter((c) => !c.covered).map((c) => c.category);\n\n return {\n repo: analysis.name,\n language: analysis.language,\n framework: analysis.framework,\n ciProvider: analysis.ciProvider,\n existingTooling: analysis.securityTooling,\n recommendations: recs,\n conflicts,\n coverage,\n gapsSummary: [\n ...analysis.gaps,\n ...(uncovered.length > 0 ? [`Uncovered categories: ${uncovered.join(', ')}`] : []),\n ],\n };\n}\n"],"mappings":";;;;;;;;;AAWA,SAAS,SAAS;AA6ClB,IAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,MAAM,EAAE,KAAK,CAAC,QAAQ,cAAc,WAAW,eAAe,CAAC;AACjE,CAAC;AAED,IAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAAA,EACrC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACzB,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC9B,eAAe,EAAE,MAAM,kBAAkB,EAAE,SAAS;AACtD,CAAC;AAED,IAAM,4BAA4B,EAC/B,OAAO;AAAA,EACN,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACnC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAClC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACtC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACxC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAClC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AACrC,CAAC,EACA,MAAM;AAET,IAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACzB,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,UAAU,EAAE,MAAM,aAAa;AAAA,EAC/B,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,yBAAyB;AACxE,CAAC;AAYD,IAAM,eAAe,KAAK,KAAK;AAC/B,IAAI,cAAiC;AAGrC,IAAI;AAGG,SAAS,qBAA2B;AACzC,gBAAc;AACd,kBAAgB;AAClB;AAMA,IAAM,gBAAgB;AACtB,IAAM,mBAAmB;AASzB,IAAM,SAAS,aAAa,EAAE,WAAW,2BAA2B,CAAC;AAGrE,eAAe,oBAAoB,eAAsD;AACvF,QAAM,EAAE,OAAO,IAAI,MAAM;AAAA,IACvB;AAAA,IACA,CAAC,WAAW,QAAQ,UAAU,eAAe,UAAU,WAAW,QAAQ,UAAU;AAAA,IACpF,EAAE,SAAS,iBAAiB;AAAA,EAC9B;AACA,SAAO,OAAO,KAAK,KAAK;AAC1B;AAGA,eAAe,iBACb,eACyC;AACzC,QAAM,EAAE,OAAO,IAAI,MAAM;AAAA,IACvB;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,EAAE,SAAS,kBAAkB,WAAW,OAAO,KAAK;AAAA,EACtD;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,MAAM;AAAA,EAC9B,QAAQ;AACN,WAAO,KAAK,uCAAuC;AAAA,MACjD,cAAc,OAAO;AAAA,MACrB,SAAS,OAAO,MAAM,GAAG,GAAG;AAAA,IAC9B,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,eAAe,UAAU,QAAQ;AAChD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,KAAK,8CAA8C;AAAA,MACxD,QAAQ,OAAO,MAAM,OAAO,MAAM,GAAG,CAAC;AAAA,IACxC,CAAC;AACD,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,qCAAqC;AAAA,IAC/C,SAAS,OAAO,KAAK;AAAA,IACrB,UAAU,OAAO,KAAK,SAAS;AAAA,IAC/B,WAAW,OAAO,KAAK,OAAO,KAAK,cAAc,EAAE;AAAA,EACrD,CAAC;AACD,SAAO,OAAO;AAChB;AAOA,eAAe,0BAAmE;AAChF,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,OAAO,eAAoB;AACtD,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,MAAW;AAC9C,UAAM,gBAAgB,UAAU,QAAQ;AAExC,UAAM,MAAM,MAAM,oBAAoB,aAAa;AACnD,QAAI,QAAQ,MAAM;AAChB,aAAO,KAAK,uCAAuC;AACnD,aAAO;AAAA,IACT;AAGA,QAAI,gBAAgB,QAAQ,YAAY,eAAe,KAAK;AAC1D,aAAO,MAAM,sDAAsD,EAAE,IAAI,CAAC;AAC1E,oBAAc,EAAE,GAAG,aAAa,WAAW,gBAAgB,EAAE,IAAI,EAAE;AACnE,aAAO,YAAY;AAAA,IACrB;AAGA,UAAM,WAAW,MAAM,iBAAiB,aAAa;AACrD,QAAI,aAAa,MAAM;AACrB,oBAAc,EAAE,UAAU,WAAW,gBAAgB,EAAE,IAAI,GAAG,YAAY,IAAI;AAAA,IAChF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,WAAO,MAAM,oCAAoC,EAAE,OAAO,IAAI,CAAC;AAC/D,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,sBAA+D;AAEnF,MAAI,gBAAgB,MAAM;AACxB,UAAM,MAAM,gBAAgB,EAAE,IAAI,IAAI,YAAY;AAClD,QAAI,MAAM,cAAc;AACtB,aAAO,YAAY;AAAA,IACrB;AAAA,EACF;AAGA,oBAAkB,wBAAwB,EAAE,QAAQ,MAAM;AACxD,oBAAgB;AAAA,EAClB,CAAC;AACD,QAAM,WAAW,MAAM;AACvB,MAAI,aAAa,MAAM;AACrB,WAAO;AAAA,EACT;AAGA,MAAI,gBAAgB,MAAM;AACxB,WAAO,KAAK,sCAAsC;AAClD,WAAO,YAAY;AAAA,EACrB;AAEA,SAAO;AACT;;;ACtOA,IAAM,oBAA6C;AAAA,EACjD;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,QAAQ,SAAS;AAAA,IAC9B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,QAAQ,KAAK;AAAA,IAC1B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,OAAO,aAAa,OAAO,MAAM;AAAA,IAC9C,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,OAAO,WAAW;AAAA,IAC/B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,OAAO,QAAQ,WAAW;AAAA,IACvC,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,SAAS;AAAA,IACtB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,SAAS;AAAA,IACtB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,OAAO,KAAK;AAAA,IACzB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,KAAK;AAAA,IAClB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,QAAQ,KAAK;AAAA,IAC1B,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,MAAM;AAAA,IACnB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC,kBAAkB,WAAW;AAAA,IAC1C,SAAS;AAAA,IACT,cAAc;AAAA,EAChB;AACF;AAMA,IAAM,wBAAoD;AAAA,EACxD,YAAY;AAAA,IACV,MAAM,CAAC,WAAW,mBAAmB,QAAQ;AAAA,IAC7C,KAAK,CAAC,aAAa,aAAa;AAAA,IAChC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,YAAY;AAAA,IACV,MAAM,CAAC,WAAW,mBAAmB,QAAQ;AAAA,IAC7C,KAAK,CAAC,aAAa,aAAa;AAAA,IAChC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,QAAQ;AAAA,IACN,MAAM,CAAC,UAAU,WAAW,QAAQ;AAAA,IACpC,KAAK,CAAC,aAAa,aAAa;AAAA,IAChC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,UAAU,WAAW,UAAU;AAAA,IACtC,KAAK,CAAC,0BAA0B,aAAa;AAAA,IAC7C,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,IAAI;AAAA,IACF,MAAM,CAAC,SAAS,WAAW,QAAQ;AAAA,IACnC,KAAK,CAAC,eAAe,aAAa;AAAA,IAClC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,YAAY,WAAW,QAAQ;AAAA,IACtC,KAAK,CAAC,iBAAiB,aAAa;AAAA,IACpC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,KAAK;AAAA,IACH,MAAM,CAAC,WAAW,SAAS;AAAA,IAC3B,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,UAAU,SAAS;AAAA,IAC1B,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,GAAG;AAAA,IACD,MAAM,CAAC,YAAY,UAAU,SAAS;AAAA,IACtC,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,YAAY,UAAU,SAAS;AAAA,IACtC,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,SAAS;AAAA,IAChB,KAAK,CAAC,eAAe,aAAa;AAAA,IAClC,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,QAAQ;AAAA,IACN,MAAM,CAAC,UAAU,WAAW,QAAQ;AAAA,IACpC,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,UAAU,SAAS;AAAA,IAC1B,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,WAAW,UAAU;AAAA,IAC5B,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,cAAc,SAAS;AAAA,IAC9B,KAAK,CAAC;AAAA,IACN,SAAS,CAAC,UAAU;AAAA,EACtB;AAAA,EACA,KAAK;AAAA,IACH,MAAM,CAAC,WAAW,OAAO;AAAA,IACzB,KAAK,CAAC,aAAa;AAAA,IACnB,SAAS,CAAC,UAAU;AAAA,EACtB;AACF;AAOO,IAAM,wBAAqC;AAAA,EAChD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,QAAQ;AACV;;;AC/RA,IAAMA,UAAS,aAAa,EAAE,WAAW,qBAAqB,CAAC;AAqC/D,SAAS,uBAAuB,GAAkC;AAChE,QAAM,aAAa,EAAE,eAAe,OAAO,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM;AAC9F,SAAO;AAAA,IACL,MAAM,EAAE;AAAA,IACR,aAAa,EAAE;AAAA,IACf,YAAY,EAAE;AAAA,IACd,SAAS,EAAE;AAAA,IACX,cAAc,EAAE;AAAA,IAChB,GAAI,eAAe,UAAa,WAAW,SAAS,IAAI,EAAE,WAAW,IAAI,CAAC;AAAA,EAC5E;AACF;AAGA,IAAM,sBAAwD;AAAA,EAC5D,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,KAAK;AAAA,EACL,OAAO;AAAA,EACP,YAAY;AACd;AAEA,SAAS,kBAAkB,MAAsB;AAC/C,QAAM,QAAQ,KAAK,YAAY;AAC/B,SAAO,oBAAoB,KAAK,KAAK,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC;AAClF;AAEA,SAAS,sBACP,QACiC;AACjC,QAAM,SAA0C,CAAC;AACjD,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAElD,UAAM,aAAa,kBAAkB,IAAI;AACzC,WAAO,UAAU,IAAI;AAAA,MACnB,MAAM,MAAM,QAAQ,CAAC;AAAA,MACrB,KAAK,MAAM,OAAO,CAAC;AAAA,MACnB,SAAS,MAAM,WAAW,CAAC;AAAA,IAC7B;AAAA,EACF;AACA,SAAO;AACT;AAGA,eAAsB,qBAA2C;AAC/D,QAAM,WAAW,MAAM,oBAAoB;AAC3C,MAAI,aAAa,MAAM;AACrB,IAAAA,QAAO,KAAK,+BAA+B;AAAA,MACzC,SAAS,SAAS;AAAA,MAClB,UAAU,SAAS,SAAS;AAAA,IAC9B,CAAC;AACD,WAAO;AAAA,MACL,UAAU,SAAS,SAAS,IAAI,sBAAsB;AAAA,MACtD,aAAa,sBAAsB,SAAS,cAAc;AAAA,MAC1D,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,EAAAA,QAAO,KAAK,6BAA6B;AACzC,SAAO;AACT;AAMA,IAAM,cAAgD;AAAA,EACpD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,eACE;AAAA,EACF,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,SAAS;AAAA,EACT,eAAe;AAAA,EACf,MAAM;AAAA,EACN,YAAY;AAAA;AAAA;AAAA,EAGZ,mBACE;AAAA,EACF,WACE;AAAA,EACF,aAAa;AAAA,EACb,OACE;AAAA,EACF,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,UACE;AAAA,EACF,aAAa;AAAA,EACb,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,OAAO;AAAA,EACP,0BACE;AAAA,EACF,aACE;AAAA,EACF,MAAM;AACR;AAEA,SAAS,kBAAkB,MAAc,IAAkC;AACzE,MAAI,OAAO,iBAAkB,QAAO;AACpC,SAAO,YAAY,IAAI,KAAK;AAC9B;AAMA,SAAS,YAAY,MAAc,UAA6D;AAC9F,SAAO,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC7C;AAEA,SAAS,cAAc,MAAc,UAAsC;AACzE,SAAO,SAAS,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,KAAK,YAAY,CAAC,CAAC;AAC1E;AAsBA,SAAS,oBAAoB,MAA+B,MAAgC;AAS1F,MAAI,oBAAoB,KAAK,KAAK,CAAC,MAAM,EAAE,aAAa,KAAK,QAAQ;AACrE,aAAW,QAAQ,KAAK,OAAO;AAC7B,QAAI,KAAK,UAAU,KAAK,IAAI,YAAa;AACzC,QAAI,cAAc,MAAM,KAAK,IAAI,QAAQ,EAAG;AAC5C,UAAM,QAAQ,YAAY,MAAM,KAAK,IAAI,QAAQ;AACjD,QAAI,CAAC,MAAO;AACZ,QAAI,KAAK,IAAI,kBAAkB,CAAC,KAAK,IAAI,eAAe,IAAI,KAAK,QAAQ,EAAG;AAC5E,UAAM,WAAW,oBAAoB,gBAAgB,KAAK;AAC1D,wBAAoB;AACpB,SAAK,KAAK;AAAA,MACR;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,SAAS,MAAM;AAAA,MACf,cAAc,MAAM;AAAA,MACpB,WAAW,KAAK,UAAU,KAAK;AAAA,MAC/B;AAAA,MACA,WAAW,kBAAkB,MAAM,KAAK,IAAI,UAAU;AAAA,IACxD,CAAC;AAAA,EACH;AACF;AAGA,SAAS,oBACP,SACA,MACA,KACM;AACN,QAAM,OAAO,IAAI,YAAY;AAC7B,sBAAoB,MAAM;AAAA,IACxB,OAAO,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,WAAW,CAAC,MAAM,GAAG,EAAE,WAAW,sBAAsB,IAAI;AAAA,IAC5D,UAAU;AAAA,IACV;AAAA,EACF,CAAC;AACD,sBAAoB,MAAM;AAAA,IACxB,OAAO,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,WAAW,CAAC,MAAM,GAAG,EAAE,WAAW,qBAAqB,IAAI;AAAA,IAC3D,UAAU;AAAA,IACV;AAAA,EACF,CAAC;AACD,sBAAoB,MAAM;AAAA,IACxB,OAAO,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,WAAW,MAAM;AAAA,IACjB,UAAU;AAAA,IACV;AAAA,EACF,CAAC;AACH;AAGA,SAAS,cACP,aACA,UACA,WACA,MACA,KACM;AACN,MAAI,KAAK,UAAU,IAAI,YAAa;AACpC,MAAI,IAAI,kBAAkB,CAAC,IAAI,eAAe,IAAI,QAAQ,EAAG;AAC7D,MAAI,cAAc,aAAa,IAAI,QAAQ,EAAG;AAC9C,MAAI,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,WAAW,EAAG;AAC9C,QAAM,QAAQ,YAAY,aAAa,IAAI,QAAQ;AACnD,MAAI,CAAC,MAAO;AACZ,OAAK,KAAK;AAAA,IACR,MAAM;AAAA,IACN,aAAa,MAAM;AAAA,IACnB;AAAA,IACA,SAAS,MAAM;AAAA,IACf,cAAc,MAAM;AAAA,IACpB;AAAA,IACA,UAAU;AAAA,IACV,WAAW,kBAAkB,aAAa,IAAI,UAAU;AAAA,EAC1D,CAAC;AACH;AAMA,SAAS,gBACP,MACA,UAC4B;AAC5B,QAAM,WAA8B,CAAC;AACrC,QAAM,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC7C,mBAAiB,OAAO,UAAU,QAAQ;AAC1C,kBAAgB,MAAM,QAAQ;AAC9B,SAAO;AACT;AAEA,SAAS,iBACP,OACA,UACA,UACM;AACN,aAAW,WAAW,UAAU;AAC9B,QAAI,CAAC,MAAM,IAAI,QAAQ,IAAI,EAAG;AAC9B,QAAI,CAAC,QAAQ,WAAY;AACzB,eAAW,OAAO,QAAQ,YAAY;AACpC,UAAI,MAAM,IAAI,GAAG,GAAG;AAClB,iBAAS,KAAK;AAAA,UACZ,UAAU,CAAC,KAAK,QAAQ,IAAI;AAAA,UAC5B,MAAM;AAAA,UACN,gBAAgB,GAAG,QAAQ,WAAW,eAAe,GAAG,YAAY,GAAG;AAAA,QACzE,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,gBACP,MACA,UACM;AACN,QAAM,SAAS,oBAAI,IAAsB;AACzC,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,OAAO,IAAI,IAAI,QAAQ,KAAK,CAAC;AACzC,QAAI,KAAK,IAAI,IAAI;AACjB,WAAO,IAAI,IAAI,UAAU,GAAG;AAAA,EAC9B;AACA,aAAW,CAAC,KAAK,QAAQ,KAAK,QAAQ;AACpC,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,QAAQ,OAAO,SAAS,MAAM;AACpC,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,MAAM;AAAA,QACN,gBAAgB,GAAG,KAAK,iBAAiB,GAAG;AAAA,MAC9C,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAMA,IAAM,iBAAiB,CAAC,QAAQ,QAAQ,OAAO,WAAW,aAAa,OAAO,gBAAgB;AAE9F,SAAS,cACP,MACA,UACA,UAC6B;AAC7B,SAAO,eAAe,IAAI,CAAC,QAAQ;AACjC,UAAM,QAAQ,KAAK,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AACtE,UAAM,gBAAgB,SAAS;AAAA,MAAK,CAAC,MACnC,SAAS,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,GAAG,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC;AAAA,IACrF;AACA,WAAO,EAAE,UAAU,KAAK,SAAS,MAAM,SAAS,KAAK,eAAe,UAAU,MAAM;AAAA,EACtF,CAAC;AACH;AAcA,eAAsB,qBACpB,OAC2B;AAC3B,QAAM,CAAC,UAAU,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,IACzC,kBAAkB,EAAE,MAAM,MAAM,MAAM,OAAO,OAAO,CAAC;AAAA,IACrD,mBAAmB;AAAA,EACrB,CAAC;AACD,SAAO,sBAAsB,UAAU,OAAO,IAAI;AACpD;AAGA,SAAS,iBACP,UACA,MACA,KACM;AACN,MAAI,SAAS,eAAe;AAC1B;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,kBAAc,eAAe,kBAAkB,4BAA4B,GAAG,MAAM,GAAG;AAAA,EACzF;AACA,MAAI,SAAS,eAAe;AAC1B;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAMA,SAAS,8BAAsC;AAC7C,SACE;AAOJ;AAGO,SAAS,sBACd,UACA,OACA,MACkB;AAClB,QAAM,WAAW,QAAQ;AACzB,QAAM,MAAkB;AAAA,IACtB,UAAU,SAAS;AAAA,IACnB,YAAY,SAAS;AAAA,IACrB,UAAU,SAAS;AAAA,IACnB,gBAAgB,MAAM,aAAa,IAAI,IAAI,MAAM,UAAU,IAAI;AAAA,IAC/D,aAAa,MAAM,eAAe;AAAA,IAClC,UAAU,SAAS;AAAA,EACrB;AAEA,QAAM,OAAgC,CAAC;AACvC,QAAM,iBAAiB,SAAS,aAAa,OAAO,kBAAkB,SAAS,QAAQ,IAAI;AAC3F,QAAM,UAAU,mBAAmB,OAAO,SAAS,YAAY,cAAc,IAAI;AACjF,MAAI,QAAS,qBAAoB,SAAS,MAAM,GAAG;AACnD,mBAAiB,UAAU,MAAM,GAAG;AAEpC,QAAM,YAAY,gBAAgB,MAAM,SAAS,QAAQ;AACzD,QAAM,WAAW,cAAc,MAAM,SAAS,iBAAiB,SAAS,QAAQ;AAChF,QAAM,YAAY,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ;AAE1E,SAAO;AAAA,IACL,MAAM,SAAS;AAAA,IACf,UAAU,SAAS;AAAA,IACnB,WAAW,SAAS;AAAA,IACpB,YAAY,SAAS;AAAA,IACrB,iBAAiB,SAAS;AAAA,IAC1B,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,aAAa;AAAA,MACX,GAAG,SAAS;AAAA,MACZ,GAAI,UAAU,SAAS,IAAI,CAAC,yBAAyB,UAAU,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,IAClF;AAAA,EACF;AACF;","names":["logger"]}
|
|
1
|
+
{"version":3,"sources":["../src/mcp/tools/scanner-registry-fetcher.ts","../src/mcp/tools/repo-security-plan-fallback.ts","../src/mcp/tools/repo-security-plan.ts"],"sourcesContent":["/**\n * nexus-agents/mcp - Scanner Registry Fetcher\n *\n * Fetches the scanner-registry.json manifest from the\n * vulnerability-scanner-registry GitHub Releases at runtime.\n * Uses a TTL cache and falls back to embedded data on failure.\n *\n * @module mcp/tools/scanner-registry-fetcher\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport { z } from 'zod';\nimport { createLogger, getTimeProvider } from '../../core/index.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** A scanner entry from the registry manifest. */\nexport interface RegistryScanner {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly relationships?: readonly RegistryRelationship[] | undefined;\n}\n\n/** A relationship edge between scanners. */\nexport interface RegistryRelationship {\n readonly target: string;\n readonly type: 'uses' | 'supersedes' | 'bundles' | 'competes-with';\n}\n\n/** Language matrix: category → scanner names. */\nexport interface LanguageMatrixEntry {\n readonly sast?: readonly string[] | undefined;\n readonly sca?: readonly string[] | undefined;\n readonly secrets?: readonly string[] | undefined;\n readonly container?: readonly string[] | undefined;\n readonly iac?: readonly string[] | undefined;\n readonly dast?: readonly string[] | undefined;\n}\n\n/** The full registry manifest shape. */\nexport interface ScannerRegistryManifest {\n readonly version: string;\n readonly generatedAt: string;\n readonly scanners: readonly RegistryScanner[];\n readonly languageMatrix: Readonly<Record<string, LanguageMatrixEntry>>;\n}\n\n// ============================================================================\n// Zod Schema for Validation\n// ============================================================================\n\nconst RelationshipSchema = z.object({\n target: z.string().min(1),\n type: z.enum(['uses', 'supersedes', 'bundles', 'competes-with']),\n});\n\nconst ScannerSchema = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n categories: z.array(z.string().min(1)),\n license: z.string().min(1),\n pricingModel: z.string().min(1),\n relationships: z.array(RelationshipSchema).optional(),\n});\n\nconst LanguageMatrixEntrySchema = z\n .object({\n sast: z.array(z.string()).optional(),\n sca: z.array(z.string()).optional(),\n secrets: z.array(z.string()).optional(),\n container: z.array(z.string()).optional(),\n iac: z.array(z.string()).optional(),\n dast: z.array(z.string()).optional(),\n })\n .loose();\n\nconst ManifestSchema = z.object({\n version: z.string().min(1),\n generatedAt: z.string().min(1),\n scanners: z.array(ScannerSchema),\n languageMatrix: z.record(z.string().max(50), LanguageMatrixEntrySchema),\n});\n\n// ============================================================================\n// Cache\n// ============================================================================\n\ninterface CacheEntry {\n manifest: ScannerRegistryManifest;\n fetchedAt: number;\n releaseTag: string;\n}\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\nlet cachedEntry: CacheEntry | null = null;\n\n/** Inflight fetch promise for probe coalescing (#1448). */\nlet inflightFetch: Promise<ScannerRegistryManifest | null> | undefined;\n\n/** Clear the cache and inflight state (for testing). */\nexport function clearRegistryCache(): void {\n cachedEntry = null;\n inflightFetch = undefined;\n}\n\n// ============================================================================\n// Fetcher\n// ============================================================================\n\nconst REGISTRY_REPO = 'williamzujkowski/vulnerability-scanner-registry';\nconst FETCH_TIMEOUT_MS = 10_000;\n\n/** Promisified execFile signature used by fetcher helpers. */\ntype ExecFileAsync = (\n file: string,\n args: readonly string[],\n options: { timeout?: number; maxBuffer?: number }\n) => Promise<{ stdout: string; stderr: string }>;\n\nconst logger = createLogger({ component: 'scanner-registry-fetcher' });\n\n/** Get the latest release tag name (lightweight check, no download). */\nasync function getLatestReleaseTag(execFileAsync: ExecFileAsync): Promise<string | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n ['release', 'view', '--repo', REGISTRY_REPO, '--json', 'tagName', '--jq', '.tagName'],\n { timeout: FETCH_TIMEOUT_MS }\n );\n return stdout.trim() || null;\n}\n\n/** Download and parse the full manifest. */\nasync function downloadManifest(\n execFileAsync: ExecFileAsync\n): Promise<ScannerRegistryManifest | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n [\n 'release',\n 'download',\n '--repo',\n REGISTRY_REPO,\n '--pattern',\n 'scanner-registry.json',\n '--output',\n '-',\n ],\n { timeout: FETCH_TIMEOUT_MS, maxBuffer: 1024 * 1024 }\n );\n\n let jsonData: unknown;\n try {\n jsonData = JSON.parse(stdout);\n } catch {\n logger.warn('Registry manifest is not valid JSON', {\n stdoutLength: stdout.length,\n preview: stdout.slice(0, 100),\n });\n return null;\n }\n\n const parsed = ManifestSchema.safeParse(jsonData);\n if (!parsed.success) {\n logger.warn('Registry manifest failed schema validation', {\n errors: parsed.error.issues.slice(0, 3),\n });\n return null;\n }\n\n logger.info('Fetched scanner registry manifest', {\n version: parsed.data.version,\n scanners: parsed.data.scanners.length,\n languages: Object.keys(parsed.data.languageMatrix).length,\n });\n return parsed.data;\n}\n\n/**\n * Fetch the scanner registry manifest from GitHub Releases.\n * If we have a cached version and the release tag hasn't changed,\n * just refreshes the cache timer (no download).\n */\nasync function fetchManifestFromGitHub(): Promise<ScannerRegistryManifest | null> {\n try {\n const { execFile } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execFileAsync = promisify(execFile);\n\n const tag = await getLatestReleaseTag(execFileAsync);\n if (tag === null) {\n logger.warn('No releases found in scanner registry');\n return null;\n }\n\n // If cached version matches the latest tag, refresh timer only\n if (cachedEntry !== null && cachedEntry.releaseTag === tag) {\n logger.debug('Scanner registry unchanged, refreshing cache timer', { tag });\n cachedEntry = { ...cachedEntry, fetchedAt: getTimeProvider().now() };\n return cachedEntry.manifest;\n }\n\n // New release — download full manifest\n const manifest = await downloadManifest(execFileAsync);\n if (manifest !== null) {\n cachedEntry = { manifest, fetchedAt: getTimeProvider().now(), releaseTag: tag };\n }\n return manifest;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug('Failed to fetch scanner registry', { error: msg });\n return null;\n }\n}\n\n/**\n * Get the scanner registry, fetching from GitHub if cache is stale.\n * Returns null if no cached data and fetch fails.\n */\nexport async function getRegistryManifest(): Promise<ScannerRegistryManifest | null> {\n // Check cache\n if (cachedEntry !== null) {\n const age = getTimeProvider().now() - cachedEntry.fetchedAt;\n if (age < CACHE_TTL_MS) {\n return cachedEntry.manifest;\n }\n }\n\n // Coalesce concurrent fetches — only one inflight request at a time (#1448)\n inflightFetch ??= fetchManifestFromGitHub().finally(() => {\n inflightFetch = undefined;\n });\n const manifest = await inflightFetch;\n if (manifest !== null) {\n return manifest;\n }\n\n // Return stale cache if available\n if (cachedEntry !== null) {\n logger.warn('Using stale cached registry manifest');\n return cachedEntry.manifest;\n }\n\n return null;\n}\n\n/**\n * Extract scanners from manifest into the format expected by plan builder.\n */\nexport function extractScannerEntries(\n manifest: ScannerRegistryManifest\n): readonly RegistryScanner[] {\n return manifest.scanners;\n}\n\n/**\n * Extract language matrix, normalizing to consistent category keys.\n */\nexport function extractLanguageMatrix(\n manifest: ScannerRegistryManifest\n): Readonly<Record<string, LanguageMatrixEntry>> {\n return manifest.languageMatrix;\n}\n","/**\n * nexus-agents/mcp - Fallback Scanner Data\n *\n * Embedded snapshot of the vulnerability-scanner-registry manifest.\n * Used when the live registry fetch fails (network issues, gh CLI\n * unavailable, etc.). Updated periodically from the canonical\n * registry at github.com/williamzujkowski/vulnerability-scanner-registry.\n *\n * @module mcp/tools/repo-security-plan-fallback\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport type { ScannerData } from './repo-security-plan.js';\n\n// ============================================================================\n// Fallback Scanner Entries (27 scanners)\n// ============================================================================\n\nconst FALLBACK_SCANNERS: ScannerData['scanners'] = [\n {\n name: 'semgrep',\n displayName: 'Semgrep',\n categories: ['sast', 'secrets'],\n license: 'LGPL-2.1',\n pricingModel: 'freemium',\n },\n {\n name: 'codeql',\n displayName: 'CodeQL',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'bandit',\n displayName: 'Bandit',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gosec',\n displayName: 'Gosec',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'brakeman',\n displayName: 'Brakeman',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'phpstan',\n displayName: 'PHPStan',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'shellcheck',\n displayName: 'ShellCheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'cppcheck',\n displayName: 'Cppcheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'detekt',\n displayName: 'detekt',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'spotbugs',\n displayName: 'SpotBugs',\n categories: ['sast'],\n license: 'LGPL-2.1',\n pricingModel: 'free',\n },\n {\n name: 'eslint-security',\n displayName: 'eslint-plugin-security',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'sonarqube',\n displayName: 'SonarQube',\n categories: ['sast', 'sca'],\n license: 'LGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'osv-scanner',\n displayName: 'OSV-Scanner',\n categories: ['sca', 'container', 'iac', 'sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype',\n displayName: 'Grype',\n categories: ['sca', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'snyk',\n displayName: 'Snyk',\n categories: ['sca', 'sast', 'container'],\n license: 'Proprietary',\n pricingModel: 'freemium',\n },\n {\n name: 'npm-audit',\n displayName: 'npm audit',\n categories: ['sca'],\n license: 'Artistic-2.0',\n pricingModel: 'free',\n },\n {\n name: 'pip-audit',\n displayName: 'pip-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'cargo-audit',\n displayName: 'cargo-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'bundler-audit',\n displayName: 'bundler-audit',\n categories: ['sca'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'govulncheck',\n displayName: 'govulncheck',\n categories: ['sca'],\n license: 'BSD-3-Clause',\n pricingModel: 'free',\n },\n {\n name: 'owasp-dependency-check',\n displayName: 'OWASP Dependency-Check',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gitleaks',\n displayName: 'Gitleaks',\n categories: ['secrets'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'trufflehog',\n displayName: 'TruffleHog',\n categories: ['secrets'],\n license: 'AGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'checkov',\n displayName: 'Checkov',\n categories: ['iac', 'sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'tfsec',\n displayName: 'tfsec',\n categories: ['iac'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'owasp-zap',\n displayName: 'OWASP ZAP',\n categories: ['dast', 'api'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'syft',\n displayName: 'Syft',\n categories: ['sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype-image',\n displayName: 'Grype (image scan)',\n categories: ['image-currency', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n];\n\n// ============================================================================\n// Fallback Language Map (16 languages)\n// ============================================================================\n\nconst FALLBACK_LANGUAGE_MAP: ScannerData['languageMap'] = {\n TypeScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n JavaScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Python: {\n sast: ['bandit', 'semgrep', 'codeql'],\n sca: ['pip-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Java: {\n sast: ['codeql', 'semgrep', 'spotbugs'],\n sca: ['owasp-dependency-check', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Go: {\n sast: ['gosec', 'semgrep', 'codeql'],\n sca: ['govulncheck', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Ruby: {\n sast: ['brakeman', 'semgrep', 'codeql'],\n sca: ['bundler-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n PHP: {\n sast: ['phpstan', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C#': {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n C: {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C++': {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Rust: {\n sast: ['semgrep'],\n sca: ['cargo-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Kotlin: {\n sast: ['detekt', 'semgrep', 'codeql'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Swift: {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Scala: {\n sast: ['semgrep', 'spotbugs'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Shell: {\n sast: ['shellcheck', 'semgrep'],\n sca: [],\n secrets: ['gitleaks'],\n },\n HCL: {\n sast: ['checkov', 'tfsec'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n};\n\n// ============================================================================\n// Exported Fallback\n// ============================================================================\n\n/** Embedded scanner data snapshot used when live registry is unavailable. */\nexport const FALLBACK_SCANNER_DATA: ScannerData = {\n scanners: FALLBACK_SCANNERS,\n languageMap: FALLBACK_LANGUAGE_MAP,\n source: 'fallback',\n};\n","/**\n * nexus-agents/mcp - Repository Security Plan Logic\n *\n * Generates a language-aware security scanning pipeline recommendation\n * by composing repo_analyze output with scanner registry data.\n * Fetches fresh data from vulnerability-scanner-registry GitHub Releases;\n * falls back to embedded snapshot if fetch fails.\n *\n * @module mcp/tools/repo-security-plan\n * (Source: Issue #1079, externalization vote 6-0 unanimous)\n */\n\nimport type { RepoAnalysis } from './repo-analyze-types.js';\nimport type {\n RepoSecurityPlanInput,\n RepoSecurityPlan,\n ScannerRecommendation,\n ConflictWarning,\n CoverageAnalysis,\n} from './repo-security-plan-types.js';\nimport { analyzeGitHubRepo } from './repo-analyze.js';\nimport { getRegistryManifest } from './scanner-registry-fetcher.js';\nimport type { RegistryScanner, LanguageMatrixEntry } from './scanner-registry-fetcher.js';\nimport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\nimport { createLogger } from '../../core/index.js';\n\nconst logger = createLogger({ component: 'repo-security-plan' });\n\n// ============================================================================\n// Scanner Data Interface (common shape for fetched + fallback)\n// ============================================================================\n\n/** Internal scanner entry used by plan builder. */\nexport interface ScannerEntry {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly supersedes?: readonly string[];\n}\n\n/** Language mapping: category → scanner names. */\ninterface LanguageMapping {\n readonly sast: readonly string[];\n readonly sca: readonly string[];\n readonly secrets: readonly string[];\n}\n\n/** Resolved scanner data for plan building. */\nexport interface ScannerData {\n readonly scanners: readonly ScannerEntry[];\n readonly languageMap: Readonly<Record<string, LanguageMapping>>;\n readonly source: 'registry' | 'fallback';\n}\n\n// Re-export for consumers\nexport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\n\n// ============================================================================\n// Registry → ScannerData Conversion\n// ============================================================================\n\nfunction convertRegistryScanner(s: RegistryScanner): ScannerEntry {\n const supersedes = s.relationships?.filter((r) => r.type === 'supersedes').map((r) => r.target);\n return {\n name: s.name,\n displayName: s.displayName,\n categories: s.categories,\n license: s.license,\n pricingModel: s.pricingModel,\n ...(supersedes !== undefined && supersedes.length > 0 ? { supersedes } : {}),\n };\n}\n\n/** Known PascalCase language names from GitHub API. Handles registry keys like \"typescript\" → \"TypeScript\". */\nconst LANGUAGE_PASCAL_MAP: Readonly<Record<string, string>> = {\n typescript: 'TypeScript',\n javascript: 'JavaScript',\n python: 'Python',\n java: 'Java',\n csharp: 'C#',\n 'c#': 'C#',\n cpp: 'C++',\n 'c++': 'C++',\n go: 'Go',\n rust: 'Rust',\n ruby: 'Ruby',\n php: 'PHP',\n swift: 'Swift',\n kotlin: 'Kotlin',\n scala: 'Scala',\n hcl: 'HCL',\n shell: 'Shell',\n dockerfile: 'Dockerfile',\n};\n\nfunction normalizeLangName(lang: string): string {\n const lower = lang.toLowerCase();\n return LANGUAGE_PASCAL_MAP[lower] ?? lang.charAt(0).toUpperCase() + lang.slice(1);\n}\n\nfunction convertLanguageMatrix(\n matrix: Readonly<Record<string, LanguageMatrixEntry>>\n): Record<string, LanguageMapping> {\n const result: Record<string, LanguageMapping> = {};\n for (const [lang, entry] of Object.entries(matrix)) {\n // Normalize language name to PascalCase (GitHub API returns PascalCase like \"TypeScript\")\n const normalized = normalizeLangName(lang);\n result[normalized] = {\n sast: entry.sast ?? [],\n sca: entry.sca ?? [],\n secrets: entry.secrets ?? [],\n };\n }\n return result;\n}\n\n/** Resolve scanner data: fetch from registry, fall back to embedded. */\nexport async function resolveScannerData(): Promise<ScannerData> {\n const manifest = await getRegistryManifest();\n if (manifest !== null) {\n logger.info('Using live scanner registry', {\n version: manifest.version,\n scanners: manifest.scanners.length,\n });\n return {\n scanners: manifest.scanners.map(convertRegistryScanner),\n languageMap: convertLanguageMatrix(manifest.languageMatrix),\n source: 'registry',\n };\n }\n\n logger.info('Using fallback scanner data');\n return FALLBACK_SCANNER_DATA;\n}\n\n// ============================================================================\n// CI Snippet Generation (GitHub Actions only for v1)\n// ============================================================================\n\nconst CI_SNIPPETS: Readonly<Record<string, string>> = {\n semgrep: '- uses: semgrep/semgrep-action@v1\\n with:\\n config: auto',\n codeql: '- uses: github/codeql-action/analyze@v3',\n grype: '- uses: anchore/scan-action@v4\\n with:\\n path: .',\n 'grype-image':\n '- uses: anchore/scan-action@v4\\n with:\\n image: ${{ env.IMAGE_TAG }}\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n gitleaks: '- uses: gitleaks/gitleaks-action@v2',\n bandit: '- run: pip install bandit && bandit -r . -f json',\n gosec: '- uses: securego/gosec@master\\n with:\\n args: ./...',\n checkov: '- uses: bridgecrewio/checkov-action@master',\n 'osv-scanner': '- uses: google/osv-scanner-action@v1',\n snyk: '- uses: snyk/actions/node@master # adjust for language',\n shellcheck: '- uses: ludeeus/action-shellcheck@master',\n // #2732: scanners recommended for TypeScript repos that were missing\n // snippets, leaving most rows with ciSnippet: null.\n 'eslint-security':\n '- run: npm install --save-dev eslint-plugin-security && npx eslint --plugin security .',\n sonarqube:\n '- uses: sonarsource/sonarqube-scan-action@v3\\n env:\\n SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}',\n 'npm-audit': '- run: npm audit --audit-level=high',\n trivy:\n '- uses: aquasecurity/trivy-action@master\\n with:\\n scan-type: fs\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n trufflehog: '- uses: trufflesecurity/trufflehog@main\\n with:\\n extra_args: --only-verified',\n cppcheck: '- run: cppcheck --enable=all --error-exitcode=1 .',\n spotbugs:\n '- uses: jwgmeligmeyling/spotbugs-github-action@master\\n with:\\n path: target/spotbugsXml.xml',\n 'pip-audit': '- run: pip install pip-audit && pip-audit',\n 'cargo-audit': '- run: cargo install cargo-audit --locked && cargo audit',\n 'bundler-audit': '- run: gem install bundler-audit && bundle-audit check --update',\n 'composer-audit': '- run: composer audit',\n govulncheck: '- run: go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./...',\n detekt: '- uses: natiginfo/action-detekt-all@1.23.6',\n brakeman: '- run: gem install brakeman && brakeman -f json',\n phpstan: '- run: composer install && vendor/bin/phpstan analyse',\n tfsec: '- uses: aquasecurity/tfsec-action@v1.0.3',\n 'owasp-dependency-check':\n '- uses: dependency-check/Dependency-Check_Action@main\\n with:\\n project: ${{ github.event.repository.name }}\\n path: .\\n format: HTML',\n 'owasp-zap':\n '- uses: zaproxy/action-baseline@v0.12.0\\n with:\\n target: ${{ env.TARGET_URL }}',\n syft: '- uses: anchore/sbom-action@v0\\n with:\\n path: .\\n format: cyclonedx-json',\n};\n\nfunction generateCiSnippet(name: string, ci: string | null): string | null {\n if (ci !== 'github-actions') return null;\n return CI_SNIPPETS[name] ?? null;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction findScanner(name: string, scanners: readonly ScannerEntry[]): ScannerEntry | undefined {\n return scanners.find((s) => s.name === name);\n}\n\nfunction isAlreadyUsed(name: string, existing: readonly string[]): boolean {\n return existing.some((t) => t.toLowerCase().includes(name.toLowerCase()));\n}\n\n/** Context passed to recommendation collectors. */\ninterface RecContext {\n readonly existing: readonly string[];\n readonly ciProvider: string | null;\n readonly language: string | null;\n readonly categoryFilter: ReadonlySet<string> | null;\n readonly maxScanners: number;\n readonly scanners: readonly ScannerEntry[];\n}\n\n/** Options for collecting recommendations in a single category. */\ninterface CategoryRecOptions {\n readonly names: readonly string[];\n readonly category: string;\n readonly rationale: (entry: ScannerEntry) => string;\n readonly priority: 'critical' | 'recommended';\n readonly ctx: RecContext;\n}\n\n/** Collect recommendations for a single category. */\nfunction collectCategoryRecs(recs: ScannerRecommendation[], opts: CategoryRecOptions): void {\n // #2732: at most one `critical` slot per category — the first scanner\n // picked is the \"must run\" one, subsequent scanners in the same\n // category demote to `recommended`. Pre-fix SAST already enforced this\n // (an inline `isFirst` flag for category === 'sast'); SCA and secrets\n // skipped the demotion and marked every entry critical, so a TypeScript\n // plan came back with 3+ critical scanners. `opts.priority` is now the\n // priority applied to the *primary* slot only; remaining slots are\n // always `recommended` regardless of what the caller passed.\n let primarySlotFilled = recs.some((r) => r.category === opts.category);\n for (const name of opts.names) {\n if (recs.length >= opts.ctx.maxScanners) break;\n if (isAlreadyUsed(name, opts.ctx.existing)) continue;\n const entry = findScanner(name, opts.ctx.scanners);\n if (!entry) continue;\n if (opts.ctx.categoryFilter && !opts.ctx.categoryFilter.has(opts.category)) continue;\n const priority = primarySlotFilled ? 'recommended' : opts.priority;\n primarySlotFilled = true;\n recs.push({\n name,\n displayName: entry.displayName,\n category: opts.category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale: opts.rationale(entry),\n priority,\n ciSnippet: generateCiSnippet(name, opts.ctx.ciProvider),\n });\n }\n}\n\n/** Collect language-specific recommendations (SAST + SCA + secrets). */\nfunction collectLanguageRecs(\n langMap: LanguageMapping,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n const lang = ctx.language ?? 'unknown';\n collectCategoryRecs(recs, {\n names: langMap.sast,\n category: 'sast',\n rationale: (e) => `${e.displayName} provides SAST for ${lang}`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.sca,\n category: 'sca',\n rationale: (e) => `${e.displayName} provides SCA for ${lang} dependencies`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.secrets,\n category: 'secrets',\n rationale: () => 'Detects leaked credentials and API keys in source code',\n priority: 'critical',\n ctx,\n });\n}\n\n/** Try to add a single scanner if not already present. */\nfunction tryAddScanner(\n scannerName: string,\n category: string,\n rationale: string,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (recs.length >= ctx.maxScanners) return;\n if (ctx.categoryFilter && !ctx.categoryFilter.has(category)) return;\n if (isAlreadyUsed(scannerName, ctx.existing)) return;\n if (recs.some((r) => r.name === scannerName)) return;\n const entry = findScanner(scannerName, ctx.scanners);\n if (!entry) return;\n recs.push({\n name: scannerName,\n displayName: entry.displayName,\n category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale,\n priority: 'recommended',\n ciSnippet: generateCiSnippet(scannerName, ctx.ciProvider),\n });\n}\n\n// ============================================================================\n// Conflict Detection\n// ============================================================================\n\nfunction detectConflicts(\n recs: readonly ScannerRecommendation[],\n scanners: readonly ScannerEntry[]\n): readonly ConflictWarning[] {\n const warnings: ConflictWarning[] = [];\n const names = new Set(recs.map((r) => r.name));\n detectSuperseded(names, scanners, warnings);\n detectRedundant(recs, warnings);\n return warnings;\n}\n\nfunction detectSuperseded(\n names: ReadonlySet<string>,\n scanners: readonly ScannerEntry[],\n warnings: ConflictWarning[]\n): void {\n for (const scanner of scanners) {\n if (!names.has(scanner.name)) continue;\n if (!scanner.supersedes) continue;\n for (const old of scanner.supersedes) {\n if (names.has(old)) {\n warnings.push({\n scanners: [old, scanner.name],\n type: 'superseded',\n recommendation: `${scanner.displayName} supersedes ${old}. Remove ${old}.`,\n });\n }\n }\n }\n}\n\nfunction detectRedundant(\n recs: readonly ScannerRecommendation[],\n warnings: ConflictWarning[]\n): void {\n const catMap = new Map<string, string[]>();\n for (const rec of recs) {\n const arr = catMap.get(rec.category) ?? [];\n arr.push(rec.name);\n catMap.set(rec.category, arr);\n }\n for (const [cat, scanners] of catMap) {\n if (scanners.length > 2) {\n const count = String(scanners.length);\n warnings.push({\n scanners,\n type: 'redundant',\n recommendation: `${count} scanners for ${cat}. Consider keeping top 2.`,\n });\n }\n }\n}\n\n// ============================================================================\n// Coverage Analysis\n// ============================================================================\n\nconst ALL_CATEGORIES = ['sast', 'dast', 'sca', 'secrets', 'container', 'iac', 'image-currency'];\n\nfunction buildCoverage(\n recs: readonly ScannerRecommendation[],\n existing: readonly string[],\n scanners: readonly ScannerEntry[]\n): readonly CoverageAnalysis[] {\n return ALL_CATEGORIES.map((cat) => {\n const found = recs.filter((r) => r.category === cat).map((r) => r.name);\n const existingMatch = existing.some((t) =>\n scanners.some((s) => s.categories.includes(cat) && t.toLowerCase().includes(s.name))\n );\n return { category: cat, covered: found.length > 0 || existingMatch, scanners: found };\n });\n}\n\n// ============================================================================\n// Plan Assembly\n// ============================================================================\n\n/** Options for buildPlanFromAnalysis (allows optional fields for testability). */\ninterface BuildPlanOptions {\n readonly repo: string;\n readonly categories?: readonly string[] | undefined;\n readonly maxScanners?: number | undefined;\n}\n\n/** Generate a security scanning plan for a repository (fetches live data). */\nexport async function generateSecurityPlan(\n input: RepoSecurityPlanInput\n): Promise<RepoSecurityPlan> {\n const [analysis, data] = await Promise.all([\n analyzeGitHubRepo({ repo: input.repo, depth: 'deep' }),\n resolveScannerData(),\n ]);\n return buildPlanFromAnalysis(analysis, input, data);\n}\n\n/** Collect infrastructure-specific scanner recommendations. */\nfunction collectInfraRecs(\n analysis: RepoAnalysis,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (analysis.hasDockerfile) {\n tryAddScanner(\n 'grype',\n 'container',\n 'Dockerfile detected — scan container images for vulnerabilities',\n recs,\n ctx\n );\n tryAddScanner('grype-image', 'image-currency', buildImageCurrencyRationale(), recs, ctx);\n }\n if (analysis.hasHelmCharts) {\n tryAddScanner(\n 'checkov',\n 'iac',\n 'Helm charts detected — scan IaC for misconfigurations',\n recs,\n ctx\n );\n }\n}\n\n/**\n * Build the rationale string for periodic container base image CVE scanning.\n * Extracted to keep collectInfraRecs within the 50-line function limit.\n */\nfunction buildImageCurrencyRationale(): string {\n return (\n 'Dockerfile detected — periodically scan built images with ' +\n '`grype image --severity CRITICAL,HIGH` to detect CVEs introduced by stale base images. ' +\n 'Pin base images to specific version tags (e.g., node:22.4.0-alpine3.20) rather than ' +\n ':latest to get reproducible scans and predictable CVE surface area. ' +\n 'Alpine-based images typically have a smaller CVE surface than Debian/Ubuntu equivalents ' +\n 'due to musl libc and a minimal package set, but verify with grype before assuming.'\n );\n}\n\n/** Pure function: build plan from analysis + scanner data (testable). */\nexport function buildPlanFromAnalysis(\n analysis: RepoAnalysis,\n input: BuildPlanOptions,\n data?: ScannerData\n): RepoSecurityPlan {\n const resolved = data ?? FALLBACK_SCANNER_DATA;\n const ctx: RecContext = {\n existing: analysis.securityTooling,\n ciProvider: analysis.ciProvider,\n language: analysis.language,\n categoryFilter: input.categories ? new Set(input.categories) : null,\n maxScanners: input.maxScanners ?? 10,\n scanners: resolved.scanners,\n };\n\n const recs: ScannerRecommendation[] = [];\n const normalizedLang = analysis.language !== null ? normalizeLangName(analysis.language) : null;\n const langMap = normalizedLang !== null ? resolved.languageMap[normalizedLang] : undefined;\n if (langMap) collectLanguageRecs(langMap, recs, ctx);\n collectInfraRecs(analysis, recs, ctx);\n\n const conflicts = detectConflicts(recs, resolved.scanners);\n const coverage = buildCoverage(recs, analysis.securityTooling, resolved.scanners);\n const uncovered = coverage.filter((c) => !c.covered).map((c) => c.category);\n\n return {\n repo: analysis.name,\n language: analysis.language,\n framework: analysis.framework,\n ciProvider: analysis.ciProvider,\n existingTooling: analysis.securityTooling,\n recommendations: recs,\n conflicts,\n coverage,\n gapsSummary: [\n ...analysis.gaps,\n ...(uncovered.length > 0 ? [`Uncovered categories: ${uncovered.join(', ')}`] : []),\n ],\n };\n}\n"],"mappings":"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","names":["logger"]}
|
|
@@ -277,8 +277,8 @@ function inferLanguageFromEntries(entries, fallback) {
|
|
|
277
277
|
return fallback;
|
|
278
278
|
}
|
|
279
279
|
async function getExecFile() {
|
|
280
|
-
const { execFile } = await import("child_process");
|
|
281
|
-
const { promisify } = await import("util");
|
|
280
|
+
const { execFile } = await import("node:child_process");
|
|
281
|
+
const { promisify } = await import("node:util");
|
|
282
282
|
return promisify(execFile);
|
|
283
283
|
}
|
|
284
284
|
async function fetchRepoData(repoId, exec) {
|
|
@@ -387,4 +387,4 @@ export {
|
|
|
387
387
|
analyzeRepo,
|
|
388
388
|
analyzeGitHubRepo
|
|
389
389
|
};
|
|
390
|
-
//# sourceMappingURL=chunk-
|
|
390
|
+
//# sourceMappingURL=chunk-DGWT7DXX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp/tools/repo-analyze.ts"],"sourcesContent":["/* eslint-disable max-lines -- cohesive module, governance allows 400-600 */\n/**\n * nexus-agents/mcp - Repository Analyze Logic\n *\n * Inspects a GitHub repository and returns structured analysis\n * including language, tooling, CI, security, and gap identification.\n *\n * @module mcp/tools/repo-analyze\n * (Source: Issue #1074)\n */\n\nimport { z } from 'zod';\nimport type { RepoAnalyzeInput, RepoAnalysis } from './repo-analyze-types.js';\n\n/**\n * Zod schema for the `gh api repos/{repoId}` payload (#2962). Pre-fix the\n * stdout was cast directly via `JSON.parse(...) as GhRepoMetadata`, so any\n * GitHub-side schema drift went unflagged until `analyzeRepo` dereferenced\n * a missing field. Now `safeParse` + a structured error surface the drift\n * with a stdout preview.\n */\nconst GhRepoMetadataSchema = z.object({\n name: z.string(),\n full_name: z.string(),\n description: z.string().nullable(),\n language: z.string().nullable(),\n default_branch: z.string(),\n stargazers_count: z.number(),\n license: z.object({ spdx_id: z.string() }).nullable(),\n});\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Normalize \"owner/repo\" from either \"owner/repo\" or full GitHub URL. */\nexport function normalizeRepoId(input: string): string {\n const urlMatch = /github\\.com\\/([^/]+\\/[^/]+?)(?:\\.git)?(?:\\/|$)/.exec(input);\n const matched = urlMatch?.[1] ?? '';\n if (matched.length > 0) return matched;\n if (/^[^/]+\\/[^/]+$/.test(input)) return input;\n throw new Error(`Invalid repo format: \"${input}\". Use \"owner/name\" or a GitHub URL.`);\n}\n\n/** Package manager detection rules: [files, manager]. First match wins. */\nconst PACKAGE_MANAGER_RULES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['pnpm-lock.yaml'], 'pnpm'],\n [['yarn.lock'], 'yarn'],\n [['package-lock.json', 'package.json'], 'npm'],\n [['Cargo.toml'], 'cargo'],\n [['go.mod'], 'go'],\n [['requirements.txt', 'pyproject.toml'], 'pip'],\n [['Gemfile'], 'bundler'],\n [['pom.xml'], 'maven'],\n [['build.gradle', 'build.gradle.kts'], 'gradle'],\n];\n\n/** Detect package manager from top-level files. */\nexport function detectPackageManager(entries: readonly string[]): string | null {\n for (const [files, manager] of PACKAGE_MANAGER_RULES) {\n if (files.some((f) => entries.includes(f))) return manager;\n }\n return null;\n}\n\n/** Detect CI provider from directory structure. */\nexport function detectCiProvider(entries: readonly string[]): string | null {\n if (entries.includes('.github')) return 'github-actions';\n if (entries.includes('.gitlab-ci.yml')) return 'gitlab-ci';\n if (entries.includes('Jenkinsfile')) return 'jenkins';\n if (entries.includes('.circleci')) return 'circleci';\n if (entries.includes('.travis.yml')) return 'travis';\n if (entries.includes('azure-pipelines.yml')) return 'azure-devops';\n if (entries.includes('concourse')) return 'concourse';\n return null;\n}\n\n/** Tool names detectable from CI workflow filenames (#1674). */\nconst WORKFLOW_SECURITY_PATTERNS: ReadonlyArray<readonly [string, string]> = [\n ['semgrep', 'semgrep'],\n ['codeql', 'codeql'],\n ['grype', 'grype'],\n ['snyk', 'snyk'],\n];\n\n/** Detect security tools from .github/workflows/ filenames. */\nfunction detectWorkflowSecurity(\n workflowEntries: readonly string[],\n existing: readonly string[]\n): readonly string[] {\n const wfLower = workflowEntries.map((w) => w.toLowerCase());\n const found: string[] = [];\n for (const [pattern, tool] of WORKFLOW_SECURITY_PATTERNS) {\n if (!existing.includes(tool) && wfLower.some((w) => w.includes(pattern))) {\n found.push(tool);\n }\n }\n return found;\n}\n\n/** Root-level config files that imply a security tool is in use. */\nconst ROOT_SECURITY_FILES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['.semgrep.yml', '.semgrep'], 'semgrep'],\n [['.snyk'], 'snyk'],\n [['SECURITY.md'], 'security-policy'],\n [['.grype.yaml'], 'grype'],\n [['CODEOWNERS'], 'codeowners'],\n // gitleaks (#2732). Catches `.gitleaks.toml` (canonical) plus legacy\n // `gitleaks.toml` / `.gitleaksignore` variants. Without this, a repo\n // carrying `.gitleaks.toml` reported `existingTooling` without gitleaks,\n // and `repo_security_plan` then showed `secrets: covered: true,\n // scanners: []` (covered by existing-but-undetected tooling).\n [['.gitleaks.toml', 'gitleaks.toml', '.gitleaksignore'], 'gitleaks'],\n];\n\n/** Detect security tooling from root files and CI workflow filenames (#1674). */\nexport function detectSecurityTooling(\n entries: readonly string[],\n workflowEntries?: readonly string[]\n): readonly string[] {\n const tools: string[] = [];\n for (const [files, tool] of ROOT_SECURITY_FILES) {\n if (files.some((f) => entries.includes(f))) tools.push(tool);\n }\n if (workflowEntries !== undefined) {\n tools.push(...detectWorkflowSecurity(workflowEntries, tools));\n }\n return tools;\n}\n\n/** Detect framework from package manager config. */\nexport function detectFramework(entries: readonly string[]): string | null {\n if (entries.includes('helmfile.yaml') || entries.includes('helmfile.yaml.gotmpl'))\n return 'helmfile';\n if (entries.includes('next.config.js') || entries.includes('next.config.ts')) return 'nextjs';\n if (entries.includes('angular.json')) return 'angular';\n if (entries.includes('vite.config.ts') || entries.includes('vite.config.js')) return 'vite';\n if (entries.includes('tsconfig.json') && entries.includes('package.json')) return 'typescript';\n return null;\n}\n\n/** Gap detection rules: [files-any-present, gap message]. */\nconst GAP_RULES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['SECURITY.md'], 'No SECURITY.md policy'],\n [['CODEOWNERS'], 'No CODEOWNERS file'],\n [['LICENSE', 'LICENSE.md'], 'No LICENSE file'],\n [\n ['.semgrep.yml', '.semgrep', '.grype.yaml', '.snyk'],\n 'No SAST/SCA security scanning configured',\n ],\n // Test detection handled separately via detectTestInfra (supports monorepo + co-located patterns)\n [['.gitignore'], 'No .gitignore file'],\n];\n\n/** Scanner recommendation per language. Canonical source: secure-language-stacks. */\ninterface LanguageScanners {\n readonly sast: readonly string[];\n readonly sca: readonly string[];\n}\n\nconst LANGUAGE_SCANNER_MATRIX: Readonly<Record<string, LanguageScanners>> = {\n TypeScript: {\n sast: ['semgrep (p/typescript, p/nodejs)', 'eslint-plugin-security'],\n sca: ['osv-scanner', 'npm audit'],\n },\n JavaScript: {\n sast: ['semgrep (p/javascript, p/nodejs)', 'eslint-plugin-security'],\n sca: ['osv-scanner', 'npm audit'],\n },\n Python: {\n sast: ['semgrep (p/python)', 'bandit'],\n sca: ['osv-scanner', 'pip-audit'],\n },\n Java: {\n sast: ['semgrep (p/java)', 'spotbugs + find-sec-bugs'],\n sca: ['osv-scanner', 'OWASP dependency-check'],\n },\n Go: {\n sast: ['semgrep (p/golang)', 'gosec'],\n sca: ['osv-scanner', 'govulncheck'],\n },\n Rust: {\n sast: ['semgrep (p/rust)'],\n sca: ['osv-scanner', 'cargo-audit'],\n },\n 'C++': {\n sast: ['semgrep (p/c)', 'cppcheck'],\n sca: ['osv-scanner'],\n },\n C: {\n sast: ['semgrep (p/c)', 'cppcheck'],\n sca: ['osv-scanner'],\n },\n Kotlin: {\n sast: ['semgrep (p/kotlin)', 'detekt'],\n sca: ['osv-scanner', 'OWASP dependency-check'],\n },\n Swift: {\n sast: ['semgrep (p/swift)'],\n sca: ['osv-scanner'],\n },\n Ruby: {\n sast: ['semgrep (p/ruby)', 'brakeman'],\n sca: ['osv-scanner', 'bundler-audit'],\n },\n PHP: {\n sast: ['semgrep (p/php)', 'phpstan'],\n sca: ['osv-scanner', 'composer audit'],\n },\n Shell: {\n sast: ['semgrep (p/bash)', 'shellcheck'],\n sca: [],\n },\n HCL: {\n sast: ['semgrep (p/terraform)', 'tfsec'],\n sca: ['osv-scanner'],\n },\n};\n\n/** Generate language-specific scanner recommendations when SAST/SCA is missing. */\nexport function getLanguageRecommendations(\n language: string | null,\n securityTooling: readonly string[]\n): readonly string[] {\n if (language === null) return [];\n const scanners = LANGUAGE_SCANNER_MATRIX[language];\n if (scanners === undefined) return [];\n\n const hasSast = securityTooling.includes('semgrep') || securityTooling.includes('snyk');\n const hasSca =\n securityTooling.includes('osv-scanner') ||\n securityTooling.includes('grype') ||\n securityTooling.includes('snyk');\n\n const recs: string[] = [];\n if (!hasSast && scanners.sast.length > 0) {\n const tools = scanners.sast.join(', ');\n recs.push(`${language} project missing SAST: ${tools}`);\n }\n if (!hasSca && scanners.sca.length > 0) {\n const tools = scanners.sca.join(', ');\n recs.push(`${language} project missing SCA: ${tools}`);\n }\n return recs;\n}\n\n/** SAST tool names that suppress the generic gap message. */\nconst SAST_TOOLS = new Set(['semgrep', 'codeql', 'snyk']);\nconst SAST_GAP_MSG = 'No SAST/SCA security scanning configured';\n\n/** Remove the SAST/SCA gap if any SAST tool was detected (#1674). */\nfunction removeSastGapIfToolDetected(gaps: string[], secTools: readonly string[]): void {\n if (secTools.some((t) => SAST_TOOLS.has(t))) {\n const idx = gaps.indexOf(SAST_GAP_MSG);\n if (idx !== -1) gaps.splice(idx, 1);\n }\n}\n\n/** Identify gaps in repository best practices. */\nexport function identifyGaps(\n entries: readonly string[],\n ciProvider: string | null,\n language?: string | null,\n securityTooling?: readonly string[]\n): readonly string[] {\n const gaps: string[] = [];\n if (ciProvider === null) gaps.push('No CI/CD configuration detected');\n for (const [files, message] of GAP_RULES) {\n if (!files.some((f) => entries.includes(f))) gaps.push(message);\n }\n // Test detection: uses detectTestInfra for monorepo + co-located pattern support (#1130)\n if (!detectTestInfra(entries)) gaps.push('No test directory detected');\n\n // Remove SAST/SCA gap if workflow-level security was detected (#1674)\n removeSastGapIfToolDetected(gaps, securityTooling ?? []);\n\n // Language-specific recommendations when generic SAST/SCA gap detected\n const hasGenericSecGap = gaps.includes('No SAST/SCA security scanning configured');\n if (\n hasGenericSecGap &&\n language !== null &&\n language !== undefined &&\n securityTooling !== undefined\n ) {\n const langRecs = getLanguageRecommendations(language, securityTooling);\n gaps.push(...langRecs);\n }\n\n return gaps;\n}\n\n// ============================================================================\n// Core\n// ============================================================================\n\n/** GitHub repo metadata from the API. */\nexport interface GhRepoMetadata {\n readonly name: string;\n readonly full_name: string;\n readonly description: string | null;\n readonly language: string | null;\n readonly default_branch: string;\n readonly stargazers_count: number;\n readonly license: { readonly spdx_id: string } | null;\n}\n\n/** Analyze a GitHub repository given its metadata and file tree. */\nexport function analyzeRepo(\n metadata: GhRepoMetadata,\n topLevelEntries: readonly string[],\n workflowEntries?: readonly string[]\n): RepoAnalysis {\n const ciProvider = detectCiProvider(topLevelEntries);\n const secTooling = detectSecurityTooling(topLevelEntries, workflowEntries);\n const hasTests = detectTestInfra(topLevelEntries);\n\n return {\n name: metadata.full_name,\n language: metadata.language,\n framework: detectFramework(topLevelEntries),\n packageManager: detectPackageManager(topLevelEntries),\n ciProvider,\n securityTooling: secTooling,\n // Match `Dockerfile`, `Dockerfile.<purpose>` (e.g. `Dockerfile.sandbox`),\n // and docker-compose variants. Pre-#2730 the check was exact-match only,\n // so a repo with three legitimate `Dockerfile.*` files reported false.\n hasDockerfile:\n topLevelEntries.some((e) => e === 'Dockerfile' || e.startsWith('Dockerfile.')) ||\n topLevelEntries.includes('docker-compose.yml') ||\n topLevelEntries.includes('docker-compose.yaml'),\n hasHelmCharts:\n topLevelEntries.includes('Chart.yaml') ||\n topLevelEntries.includes('charts') ||\n topLevelEntries.includes('helm'),\n hasMakefile: topLevelEntries.includes('Makefile'),\n hasTests,\n license: metadata.license?.spdx_id ?? null,\n description: metadata.description,\n defaultBranch: metadata.default_branch,\n stars: metadata.stargazers_count,\n topLevelEntries: [...topLevelEntries],\n gaps: identifyGaps(topLevelEntries, ciProvider, metadata.language, secTooling),\n };\n}\n\n/** Non-code languages to exclude when detecting primary language. */\nconst MARKUP_LANGUAGES = new Set([\n 'HTML',\n 'CSS',\n 'SCSS',\n 'Less',\n 'Markdown',\n 'Roff',\n 'SVG',\n 'XML',\n 'XSLT',\n 'Mustache',\n 'Handlebars',\n 'EJS',\n]);\n\n/** Detect primary language from GitHub languages API (byte counts). */\nfunction detectPrimaryLanguage(\n languages: Record<string, number>,\n fallback: string | null\n): string | null {\n const sorted = Object.entries(languages)\n .filter(([lang]) => !MARKUP_LANGUAGES.has(lang))\n .sort((a, b) => b[1] - a[1]);\n const top = sorted[0];\n return top !== undefined ? top[0] : fallback;\n}\n\n/** Check for test infrastructure beyond top-level directories. */\nfunction detectTestInfra(entries: readonly string[]): boolean {\n const testDirs = ['tests', 'test', '__tests__', 'spec'];\n if (testDirs.some((d) => entries.includes(d))) return true;\n // Check for test config files (co-located test pattern, monorepos)\n const testConfigs = [\n 'vitest.config.ts',\n 'vitest.config.js',\n 'vitest.config.mts',\n 'vitest.workspace.ts',\n 'vitest.workspace.js',\n 'jest.config.ts',\n 'jest.config.js',\n 'jest.config.mjs',\n 'cypress.config.ts',\n 'cypress.config.js',\n 'playwright.config.ts',\n '.mocharc.yml',\n '.mocharc.json',\n ];\n if (testConfigs.some((c) => entries.includes(c))) return true;\n // Monorepo: packages/ dir + package.json implies co-located tests\n return entries.includes('packages') && entries.includes('package.json');\n}\n\n/** Infer code language from project files when GitHub reports markup. */\nfunction inferLanguageFromEntries(\n entries: readonly string[],\n fallback: string | null\n): string | null {\n if (entries.includes('tsconfig.json')) return 'TypeScript';\n if (entries.includes('Cargo.toml')) return 'Rust';\n if (entries.includes('go.mod')) return 'Go';\n if (entries.includes('pyproject.toml') || entries.includes('setup.py')) return 'Python';\n if (entries.includes('pom.xml') || entries.includes('build.gradle')) return 'Java';\n if (entries.includes('Gemfile')) return 'Ruby';\n if (entries.includes('package.json')) return 'JavaScript';\n return fallback;\n}\n\ntype ExecFileFn = (\n cmd: string,\n args: string[],\n options?: { timeout?: number }\n) => Promise<{ stdout: string }>;\n\n/** Lazy-load promisified execFile. */\nasync function getExecFile(): Promise<ExecFileFn> {\n const { execFile } = await import('node:child_process');\n const { promisify } = await import('node:util');\n return promisify(execFile);\n}\n\n/** Fetch repo metadata and languages via GitHub API. */\nasync function fetchRepoData(\n repoId: string,\n exec: ExecFileFn\n): Promise<{ metadata: GhRepoMetadata; entries: string[] }> {\n const { stdout: metaJson } = await exec(\n 'gh',\n [\n 'api',\n `repos/${repoId}`,\n '--jq',\n '{name: .name, full_name: .full_name, description: .description, language: .language, default_branch: .default_branch, stargazers_count: .stargazers_count, license: .license}',\n ],\n { timeout: 30_000 }\n );\n let parsed: unknown;\n try {\n parsed = JSON.parse(metaJson.trim());\n } catch {\n throw new Error(`Failed to parse repo metadata for ${repoId}: ${metaJson.slice(0, 200)}`);\n }\n // Closes #2962 (P1): Zod-validate the parsed JSON instead of casting\n // blindly. Pre-fix, a GitHub-side schema drift produced a typed-but-\n // mismatched object that crashed deep in `analyzeRepo` or silently\n // surfaced a wrong field (e.g., enhanced.license.spdx_id null →\n // \"no license\" — same shape as #2943).\n const validated = GhRepoMetadataSchema.safeParse(parsed);\n if (!validated.success) {\n throw new Error(\n `Repo metadata schema mismatch for ${repoId}: ${validated.error.message} ` +\n `(payload preview: ${metaJson.slice(0, 200)})`\n );\n }\n const metadata: GhRepoMetadata = validated.data;\n const { stdout: contentsJson } = await exec(\n 'gh',\n ['api', `repos/${repoId}/contents`, '--jq', '[.[].name]'],\n { timeout: 30_000 }\n );\n let entries: string[];\n try {\n const parsed: unknown = JSON.parse(contentsJson.trim());\n entries = Array.isArray(parsed) ? parsed.filter((e): e is string => typeof e === 'string') : [];\n } catch {\n throw new Error(`Failed to parse repo contents for ${repoId}: ${contentsJson.slice(0, 200)}`);\n }\n return { metadata, entries };\n}\n\n/** Resolve NOASSERTION license via the GitHub license API. */\nasync function resolveLicense(repoId: string, exec: ExecFileFn): Promise<string | null> {\n try {\n const { stdout } = await exec(\n 'gh',\n ['api', `repos/${repoId}/license`, '--jq', '.license.spdx_id'],\n { timeout: 15_000 }\n );\n const spdxId = stdout.trim();\n if (spdxId !== '' && spdxId !== 'null' && spdxId !== 'NOASSERTION') {\n return spdxId;\n }\n } catch {\n // Keep NOASSERTION if license API also fails\n }\n return null;\n}\n\n/** Resolve primary language, falling back to entry inference for markup repos. */\nasync function resolveLanguage(\n repoId: string,\n entries: readonly string[],\n metadata: GhRepoMetadata,\n exec: ExecFileFn\n): Promise<string | null> {\n let languages: Record<string, number> = {};\n try {\n const { stdout } = await exec('gh', ['api', `repos/${repoId}/languages`], { timeout: 15_000 });\n languages = JSON.parse(stdout.trim()) as Record<string, number>;\n } catch {\n /* fall back to metadata.language */\n }\n const primary = detectPrimaryLanguage(languages, metadata.language);\n if (primary === null || MARKUP_LANGUAGES.has(primary)) {\n return inferLanguageFromEntries(entries, primary);\n }\n return primary;\n}\n\n/** Fetch workflow filenames from .github/workflows/ (#1674). Best-effort. */\nasync function fetchWorkflowEntries(repoId: string, exec: ExecFileFn): Promise<readonly string[]> {\n try {\n const { stdout } = await exec(\n 'gh',\n ['api', `repos/${repoId}/contents/.github/workflows`, '--jq', '[.[].name]'],\n { timeout: 15_000 }\n );\n const parsed: unknown = JSON.parse(stdout.trim());\n return Array.isArray(parsed) ? parsed.filter((e): e is string => typeof e === 'string') : [];\n } catch {\n return []; // No workflows directory or API error — graceful fallback\n }\n}\n\n/** Fetch repo data from GitHub and produce analysis. */\nexport async function analyzeGitHubRepo(input: RepoAnalyzeInput): Promise<RepoAnalysis> {\n const repoId = normalizeRepoId(input.repo);\n const exec = await getExecFile();\n const { metadata, entries } = await fetchRepoData(repoId, exec);\n\n const primaryLang = await resolveLanguage(repoId, entries, metadata, exec);\n const enhanced = { ...metadata, language: primaryLang };\n\n // Resolve null or NOASSERTION license when LICENSE file exists\n const hasLicenseFile = entries.includes('LICENSE') || entries.includes('LICENSE.md');\n const licenseUnresolved = enhanced.license === null || enhanced.license.spdx_id === 'NOASSERTION';\n if (licenseUnresolved && hasLicenseFile) {\n const resolved = await resolveLicense(repoId, exec);\n if (resolved !== null) enhanced.license = { spdx_id: resolved };\n }\n\n // Fetch workflow filenames for CI-level security detection (#1674)\n const workflowEntries = entries.includes('.github')\n ? await fetchWorkflowEntries(repoId, exec)\n : [];\n\n return analyzeRepo(enhanced, entries, workflowEntries);\n}\n"],"mappings":"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","names":["parsed"]}
|
|
1
|
+
{"version":3,"sources":["../src/mcp/tools/repo-analyze.ts"],"sourcesContent":["/* eslint-disable max-lines -- cohesive module, governance allows 400-600 */\n/**\n * nexus-agents/mcp - Repository Analyze Logic\n *\n * Inspects a GitHub repository and returns structured analysis\n * including language, tooling, CI, security, and gap identification.\n *\n * @module mcp/tools/repo-analyze\n * (Source: Issue #1074)\n */\n\nimport { z } from 'zod';\nimport type { RepoAnalyzeInput, RepoAnalysis } from './repo-analyze-types.js';\n\n/**\n * Zod schema for the `gh api repos/{repoId}` payload (#2962). Pre-fix the\n * stdout was cast directly via `JSON.parse(...) as GhRepoMetadata`, so any\n * GitHub-side schema drift went unflagged until `analyzeRepo` dereferenced\n * a missing field. Now `safeParse` + a structured error surface the drift\n * with a stdout preview.\n */\nconst GhRepoMetadataSchema = z.object({\n name: z.string(),\n full_name: z.string(),\n description: z.string().nullable(),\n language: z.string().nullable(),\n default_branch: z.string(),\n stargazers_count: z.number(),\n license: z.object({ spdx_id: z.string() }).nullable(),\n});\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Normalize \"owner/repo\" from either \"owner/repo\" or full GitHub URL. */\nexport function normalizeRepoId(input: string): string {\n const urlMatch = /github\\.com\\/([^/]+\\/[^/]+?)(?:\\.git)?(?:\\/|$)/.exec(input);\n const matched = urlMatch?.[1] ?? '';\n if (matched.length > 0) return matched;\n if (/^[^/]+\\/[^/]+$/.test(input)) return input;\n throw new Error(`Invalid repo format: \"${input}\". Use \"owner/name\" or a GitHub URL.`);\n}\n\n/** Package manager detection rules: [files, manager]. First match wins. */\nconst PACKAGE_MANAGER_RULES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['pnpm-lock.yaml'], 'pnpm'],\n [['yarn.lock'], 'yarn'],\n [['package-lock.json', 'package.json'], 'npm'],\n [['Cargo.toml'], 'cargo'],\n [['go.mod'], 'go'],\n [['requirements.txt', 'pyproject.toml'], 'pip'],\n [['Gemfile'], 'bundler'],\n [['pom.xml'], 'maven'],\n [['build.gradle', 'build.gradle.kts'], 'gradle'],\n];\n\n/** Detect package manager from top-level files. */\nexport function detectPackageManager(entries: readonly string[]): string | null {\n for (const [files, manager] of PACKAGE_MANAGER_RULES) {\n if (files.some((f) => entries.includes(f))) return manager;\n }\n return null;\n}\n\n/** Detect CI provider from directory structure. */\nexport function detectCiProvider(entries: readonly string[]): string | null {\n if (entries.includes('.github')) return 'github-actions';\n if (entries.includes('.gitlab-ci.yml')) return 'gitlab-ci';\n if (entries.includes('Jenkinsfile')) return 'jenkins';\n if (entries.includes('.circleci')) return 'circleci';\n if (entries.includes('.travis.yml')) return 'travis';\n if (entries.includes('azure-pipelines.yml')) return 'azure-devops';\n if (entries.includes('concourse')) return 'concourse';\n return null;\n}\n\n/** Tool names detectable from CI workflow filenames (#1674). */\nconst WORKFLOW_SECURITY_PATTERNS: ReadonlyArray<readonly [string, string]> = [\n ['semgrep', 'semgrep'],\n ['codeql', 'codeql'],\n ['grype', 'grype'],\n ['snyk', 'snyk'],\n];\n\n/** Detect security tools from .github/workflows/ filenames. */\nfunction detectWorkflowSecurity(\n workflowEntries: readonly string[],\n existing: readonly string[]\n): readonly string[] {\n const wfLower = workflowEntries.map((w) => w.toLowerCase());\n const found: string[] = [];\n for (const [pattern, tool] of WORKFLOW_SECURITY_PATTERNS) {\n if (!existing.includes(tool) && wfLower.some((w) => w.includes(pattern))) {\n found.push(tool);\n }\n }\n return found;\n}\n\n/** Root-level config files that imply a security tool is in use. */\nconst ROOT_SECURITY_FILES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['.semgrep.yml', '.semgrep'], 'semgrep'],\n [['.snyk'], 'snyk'],\n [['SECURITY.md'], 'security-policy'],\n [['.grype.yaml'], 'grype'],\n [['CODEOWNERS'], 'codeowners'],\n // gitleaks (#2732). Catches `.gitleaks.toml` (canonical) plus legacy\n // `gitleaks.toml` / `.gitleaksignore` variants. Without this, a repo\n // carrying `.gitleaks.toml` reported `existingTooling` without gitleaks,\n // and `repo_security_plan` then showed `secrets: covered: true,\n // scanners: []` (covered by existing-but-undetected tooling).\n [['.gitleaks.toml', 'gitleaks.toml', '.gitleaksignore'], 'gitleaks'],\n];\n\n/** Detect security tooling from root files and CI workflow filenames (#1674). */\nexport function detectSecurityTooling(\n entries: readonly string[],\n workflowEntries?: readonly string[]\n): readonly string[] {\n const tools: string[] = [];\n for (const [files, tool] of ROOT_SECURITY_FILES) {\n if (files.some((f) => entries.includes(f))) tools.push(tool);\n }\n if (workflowEntries !== undefined) {\n tools.push(...detectWorkflowSecurity(workflowEntries, tools));\n }\n return tools;\n}\n\n/** Detect framework from package manager config. */\nexport function detectFramework(entries: readonly string[]): string | null {\n if (entries.includes('helmfile.yaml') || entries.includes('helmfile.yaml.gotmpl'))\n return 'helmfile';\n if (entries.includes('next.config.js') || entries.includes('next.config.ts')) return 'nextjs';\n if (entries.includes('angular.json')) return 'angular';\n if (entries.includes('vite.config.ts') || entries.includes('vite.config.js')) return 'vite';\n if (entries.includes('tsconfig.json') && entries.includes('package.json')) return 'typescript';\n return null;\n}\n\n/** Gap detection rules: [files-any-present, gap message]. */\nconst GAP_RULES: ReadonlyArray<readonly [readonly string[], string]> = [\n [['SECURITY.md'], 'No SECURITY.md policy'],\n [['CODEOWNERS'], 'No CODEOWNERS file'],\n [['LICENSE', 'LICENSE.md'], 'No LICENSE file'],\n [\n ['.semgrep.yml', '.semgrep', '.grype.yaml', '.snyk'],\n 'No SAST/SCA security scanning configured',\n ],\n // Test detection handled separately via detectTestInfra (supports monorepo + co-located patterns)\n [['.gitignore'], 'No .gitignore file'],\n];\n\n/** Scanner recommendation per language. Canonical source: secure-language-stacks. */\ninterface LanguageScanners {\n readonly sast: readonly string[];\n readonly sca: readonly string[];\n}\n\nconst LANGUAGE_SCANNER_MATRIX: Readonly<Record<string, LanguageScanners>> = {\n TypeScript: {\n sast: ['semgrep (p/typescript, p/nodejs)', 'eslint-plugin-security'],\n sca: ['osv-scanner', 'npm audit'],\n },\n JavaScript: {\n sast: ['semgrep (p/javascript, p/nodejs)', 'eslint-plugin-security'],\n sca: ['osv-scanner', 'npm audit'],\n },\n Python: {\n sast: ['semgrep (p/python)', 'bandit'],\n sca: ['osv-scanner', 'pip-audit'],\n },\n Java: {\n sast: ['semgrep (p/java)', 'spotbugs + find-sec-bugs'],\n sca: ['osv-scanner', 'OWASP dependency-check'],\n },\n Go: {\n sast: ['semgrep (p/golang)', 'gosec'],\n sca: ['osv-scanner', 'govulncheck'],\n },\n Rust: {\n sast: ['semgrep (p/rust)'],\n sca: ['osv-scanner', 'cargo-audit'],\n },\n 'C++': {\n sast: ['semgrep (p/c)', 'cppcheck'],\n sca: ['osv-scanner'],\n },\n C: {\n sast: ['semgrep (p/c)', 'cppcheck'],\n sca: ['osv-scanner'],\n },\n Kotlin: {\n sast: ['semgrep (p/kotlin)', 'detekt'],\n sca: ['osv-scanner', 'OWASP dependency-check'],\n },\n Swift: {\n sast: ['semgrep (p/swift)'],\n sca: ['osv-scanner'],\n },\n Ruby: {\n sast: ['semgrep (p/ruby)', 'brakeman'],\n sca: ['osv-scanner', 'bundler-audit'],\n },\n PHP: {\n sast: ['semgrep (p/php)', 'phpstan'],\n sca: ['osv-scanner', 'composer audit'],\n },\n Shell: {\n sast: ['semgrep (p/bash)', 'shellcheck'],\n sca: [],\n },\n HCL: {\n sast: ['semgrep (p/terraform)', 'tfsec'],\n sca: ['osv-scanner'],\n },\n};\n\n/** Generate language-specific scanner recommendations when SAST/SCA is missing. */\nexport function getLanguageRecommendations(\n language: string | null,\n securityTooling: readonly string[]\n): readonly string[] {\n if (language === null) return [];\n const scanners = LANGUAGE_SCANNER_MATRIX[language];\n if (scanners === undefined) return [];\n\n const hasSast = securityTooling.includes('semgrep') || securityTooling.includes('snyk');\n const hasSca =\n securityTooling.includes('osv-scanner') ||\n securityTooling.includes('grype') ||\n securityTooling.includes('snyk');\n\n const recs: string[] = [];\n if (!hasSast && scanners.sast.length > 0) {\n const tools = scanners.sast.join(', ');\n recs.push(`${language} project missing SAST: ${tools}`);\n }\n if (!hasSca && scanners.sca.length > 0) {\n const tools = scanners.sca.join(', ');\n recs.push(`${language} project missing SCA: ${tools}`);\n }\n return recs;\n}\n\n/** SAST tool names that suppress the generic gap message. */\nconst SAST_TOOLS = new Set(['semgrep', 'codeql', 'snyk']);\nconst SAST_GAP_MSG = 'No SAST/SCA security scanning configured';\n\n/** Remove the SAST/SCA gap if any SAST tool was detected (#1674). */\nfunction removeSastGapIfToolDetected(gaps: string[], secTools: readonly string[]): void {\n if (secTools.some((t) => SAST_TOOLS.has(t))) {\n const idx = gaps.indexOf(SAST_GAP_MSG);\n if (idx !== -1) gaps.splice(idx, 1);\n }\n}\n\n/** Identify gaps in repository best practices. */\nexport function identifyGaps(\n entries: readonly string[],\n ciProvider: string | null,\n language?: string | null,\n securityTooling?: readonly string[]\n): readonly string[] {\n const gaps: string[] = [];\n if (ciProvider === null) gaps.push('No CI/CD configuration detected');\n for (const [files, message] of GAP_RULES) {\n if (!files.some((f) => entries.includes(f))) gaps.push(message);\n }\n // Test detection: uses detectTestInfra for monorepo + co-located pattern support (#1130)\n if (!detectTestInfra(entries)) gaps.push('No test directory detected');\n\n // Remove SAST/SCA gap if workflow-level security was detected (#1674)\n removeSastGapIfToolDetected(gaps, securityTooling ?? []);\n\n // Language-specific recommendations when generic SAST/SCA gap detected\n const hasGenericSecGap = gaps.includes('No SAST/SCA security scanning configured');\n if (\n hasGenericSecGap &&\n language !== null &&\n language !== undefined &&\n securityTooling !== undefined\n ) {\n const langRecs = getLanguageRecommendations(language, securityTooling);\n gaps.push(...langRecs);\n }\n\n return gaps;\n}\n\n// ============================================================================\n// Core\n// ============================================================================\n\n/** GitHub repo metadata from the API. */\nexport interface GhRepoMetadata {\n readonly name: string;\n readonly full_name: string;\n readonly description: string | null;\n readonly language: string | null;\n readonly default_branch: string;\n readonly stargazers_count: number;\n readonly license: { readonly spdx_id: string } | null;\n}\n\n/** Analyze a GitHub repository given its metadata and file tree. */\nexport function analyzeRepo(\n metadata: GhRepoMetadata,\n topLevelEntries: readonly string[],\n workflowEntries?: readonly string[]\n): RepoAnalysis {\n const ciProvider = detectCiProvider(topLevelEntries);\n const secTooling = detectSecurityTooling(topLevelEntries, workflowEntries);\n const hasTests = detectTestInfra(topLevelEntries);\n\n return {\n name: metadata.full_name,\n language: metadata.language,\n framework: detectFramework(topLevelEntries),\n packageManager: detectPackageManager(topLevelEntries),\n ciProvider,\n securityTooling: secTooling,\n // Match `Dockerfile`, `Dockerfile.<purpose>` (e.g. `Dockerfile.sandbox`),\n // and docker-compose variants. Pre-#2730 the check was exact-match only,\n // so a repo with three legitimate `Dockerfile.*` files reported false.\n hasDockerfile:\n topLevelEntries.some((e) => e === 'Dockerfile' || e.startsWith('Dockerfile.')) ||\n topLevelEntries.includes('docker-compose.yml') ||\n topLevelEntries.includes('docker-compose.yaml'),\n hasHelmCharts:\n topLevelEntries.includes('Chart.yaml') ||\n topLevelEntries.includes('charts') ||\n topLevelEntries.includes('helm'),\n hasMakefile: topLevelEntries.includes('Makefile'),\n hasTests,\n license: metadata.license?.spdx_id ?? null,\n description: metadata.description,\n defaultBranch: metadata.default_branch,\n stars: metadata.stargazers_count,\n topLevelEntries: [...topLevelEntries],\n gaps: identifyGaps(topLevelEntries, ciProvider, metadata.language, secTooling),\n };\n}\n\n/** Non-code languages to exclude when detecting primary language. */\nconst MARKUP_LANGUAGES = new Set([\n 'HTML',\n 'CSS',\n 'SCSS',\n 'Less',\n 'Markdown',\n 'Roff',\n 'SVG',\n 'XML',\n 'XSLT',\n 'Mustache',\n 'Handlebars',\n 'EJS',\n]);\n\n/** Detect primary language from GitHub languages API (byte counts). */\nfunction detectPrimaryLanguage(\n languages: Record<string, number>,\n fallback: string | null\n): string | null {\n const sorted = Object.entries(languages)\n .filter(([lang]) => !MARKUP_LANGUAGES.has(lang))\n .sort((a, b) => b[1] - a[1]);\n const top = sorted[0];\n return top !== undefined ? top[0] : fallback;\n}\n\n/** Check for test infrastructure beyond top-level directories. */\nfunction detectTestInfra(entries: readonly string[]): boolean {\n const testDirs = ['tests', 'test', '__tests__', 'spec'];\n if (testDirs.some((d) => entries.includes(d))) return true;\n // Check for test config files (co-located test pattern, monorepos)\n const testConfigs = [\n 'vitest.config.ts',\n 'vitest.config.js',\n 'vitest.config.mts',\n 'vitest.workspace.ts',\n 'vitest.workspace.js',\n 'jest.config.ts',\n 'jest.config.js',\n 'jest.config.mjs',\n 'cypress.config.ts',\n 'cypress.config.js',\n 'playwright.config.ts',\n '.mocharc.yml',\n '.mocharc.json',\n ];\n if (testConfigs.some((c) => entries.includes(c))) return true;\n // Monorepo: packages/ dir + package.json implies co-located tests\n return entries.includes('packages') && entries.includes('package.json');\n}\n\n/** Infer code language from project files when GitHub reports markup. */\nfunction inferLanguageFromEntries(\n entries: readonly string[],\n fallback: string | null\n): string | null {\n if (entries.includes('tsconfig.json')) return 'TypeScript';\n if (entries.includes('Cargo.toml')) return 'Rust';\n if (entries.includes('go.mod')) return 'Go';\n if (entries.includes('pyproject.toml') || entries.includes('setup.py')) return 'Python';\n if (entries.includes('pom.xml') || entries.includes('build.gradle')) return 'Java';\n if (entries.includes('Gemfile')) return 'Ruby';\n if (entries.includes('package.json')) return 'JavaScript';\n return fallback;\n}\n\ntype ExecFileFn = (\n cmd: string,\n args: string[],\n options?: { timeout?: number }\n) => Promise<{ stdout: string }>;\n\n/** Lazy-load promisified execFile. */\nasync function getExecFile(): Promise<ExecFileFn> {\n const { execFile } = await import('node:child_process');\n const { promisify } = await import('node:util');\n return promisify(execFile);\n}\n\n/** Fetch repo metadata and languages via GitHub API. */\nasync function fetchRepoData(\n repoId: string,\n exec: ExecFileFn\n): Promise<{ metadata: GhRepoMetadata; entries: string[] }> {\n const { stdout: metaJson } = await exec(\n 'gh',\n [\n 'api',\n `repos/${repoId}`,\n '--jq',\n '{name: .name, full_name: .full_name, description: .description, language: .language, default_branch: .default_branch, stargazers_count: .stargazers_count, license: .license}',\n ],\n { timeout: 30_000 }\n );\n let parsed: unknown;\n try {\n parsed = JSON.parse(metaJson.trim());\n } catch {\n throw new Error(`Failed to parse repo metadata for ${repoId}: ${metaJson.slice(0, 200)}`);\n }\n // Closes #2962 (P1): Zod-validate the parsed JSON instead of casting\n // blindly. Pre-fix, a GitHub-side schema drift produced a typed-but-\n // mismatched object that crashed deep in `analyzeRepo` or silently\n // surfaced a wrong field (e.g., enhanced.license.spdx_id null →\n // \"no license\" — same shape as #2943).\n const validated = GhRepoMetadataSchema.safeParse(parsed);\n if (!validated.success) {\n throw new Error(\n `Repo metadata schema mismatch for ${repoId}: ${validated.error.message} ` +\n `(payload preview: ${metaJson.slice(0, 200)})`\n );\n }\n const metadata: GhRepoMetadata = validated.data;\n const { stdout: contentsJson } = await exec(\n 'gh',\n ['api', `repos/${repoId}/contents`, '--jq', '[.[].name]'],\n { timeout: 30_000 }\n );\n let entries: string[];\n try {\n const parsed: unknown = JSON.parse(contentsJson.trim());\n entries = Array.isArray(parsed) ? parsed.filter((e): e is string => typeof e === 'string') : [];\n } catch {\n throw new Error(`Failed to parse repo contents for ${repoId}: ${contentsJson.slice(0, 200)}`);\n }\n return { metadata, entries };\n}\n\n/** Resolve NOASSERTION license via the GitHub license API. */\nasync function resolveLicense(repoId: string, exec: ExecFileFn): Promise<string | null> {\n try {\n const { stdout } = await exec(\n 'gh',\n ['api', `repos/${repoId}/license`, '--jq', '.license.spdx_id'],\n { timeout: 15_000 }\n );\n const spdxId = stdout.trim();\n if (spdxId !== '' && spdxId !== 'null' && spdxId !== 'NOASSERTION') {\n return spdxId;\n }\n } catch {\n // Keep NOASSERTION if license API also fails\n }\n return null;\n}\n\n/** Resolve primary language, falling back to entry inference for markup repos. */\nasync function resolveLanguage(\n repoId: string,\n entries: readonly string[],\n metadata: GhRepoMetadata,\n exec: ExecFileFn\n): Promise<string | null> {\n let languages: Record<string, number> = {};\n try {\n const { stdout } = await exec('gh', ['api', `repos/${repoId}/languages`], { timeout: 15_000 });\n languages = JSON.parse(stdout.trim()) as Record<string, number>;\n } catch {\n /* fall back to metadata.language */\n }\n const primary = detectPrimaryLanguage(languages, metadata.language);\n if (primary === null || MARKUP_LANGUAGES.has(primary)) {\n return inferLanguageFromEntries(entries, primary);\n }\n return primary;\n}\n\n/** Fetch workflow filenames from .github/workflows/ (#1674). Best-effort. */\nasync function fetchWorkflowEntries(repoId: string, exec: ExecFileFn): Promise<readonly string[]> {\n try {\n const { stdout } = await exec(\n 'gh',\n ['api', `repos/${repoId}/contents/.github/workflows`, '--jq', '[.[].name]'],\n { timeout: 15_000 }\n );\n const parsed: unknown = JSON.parse(stdout.trim());\n return Array.isArray(parsed) ? parsed.filter((e): e is string => typeof e === 'string') : [];\n } catch {\n return []; // No workflows directory or API error — graceful fallback\n }\n}\n\n/** Fetch repo data from GitHub and produce analysis. */\nexport async function analyzeGitHubRepo(input: RepoAnalyzeInput): Promise<RepoAnalysis> {\n const repoId = normalizeRepoId(input.repo);\n const exec = await getExecFile();\n const { metadata, entries } = await fetchRepoData(repoId, exec);\n\n const primaryLang = await resolveLanguage(repoId, entries, metadata, exec);\n const enhanced = { ...metadata, language: primaryLang };\n\n // Resolve null or NOASSERTION license when LICENSE file exists\n const hasLicenseFile = entries.includes('LICENSE') || entries.includes('LICENSE.md');\n const licenseUnresolved = enhanced.license === null || enhanced.license.spdx_id === 'NOASSERTION';\n if (licenseUnresolved && hasLicenseFile) {\n const resolved = await resolveLicense(repoId, exec);\n if (resolved !== null) enhanced.license = { spdx_id: resolved };\n }\n\n // Fetch workflow filenames for CI-level security detection (#1674)\n const workflowEntries = entries.includes('.github')\n ? await fetchWorkflowEntries(repoId, exec)\n : [];\n\n return analyzeRepo(enhanced, entries, workflowEntries);\n}\n"],"mappings":"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","names":["parsed"]}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
lookupInTreeCapability,
|
|
14
14
|
ok,
|
|
15
15
|
resolveRetryAfterMs
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-J7SPTP2E.js";
|
|
17
17
|
|
|
18
18
|
// src/config/model-parameter-support.ts
|
|
19
19
|
function parseClaudeMajorMinor(bareId) {
|
|
@@ -834,4 +834,4 @@ export {
|
|
|
834
834
|
DEFAULT_COLLECT_STREAM_MAX_CHUNKS,
|
|
835
835
|
collectStream
|
|
836
836
|
};
|
|
837
|
-
//# sourceMappingURL=chunk-
|
|
837
|
+
//# sourceMappingURL=chunk-DWUF5YWG.js.map
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
computeCostDetail,
|
|
3
3
|
recordUsageEvent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2S7Q7GHX.js";
|
|
5
5
|
import {
|
|
6
6
|
BaseAdapter,
|
|
7
7
|
createStream,
|
|
8
8
|
planOptionalParams,
|
|
9
9
|
requireApiKey,
|
|
10
10
|
validateApiKeyPresence
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DWUF5YWG.js";
|
|
12
12
|
import {
|
|
13
13
|
assertCustomApiHostResolvesPublic
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-FW2FBZBD.js";
|
|
15
15
|
import {
|
|
16
16
|
ConfigError,
|
|
17
17
|
ModelCapability,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
getTimeProvider,
|
|
23
23
|
getTokenEstimator,
|
|
24
24
|
ok
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-J7SPTP2E.js";
|
|
26
26
|
|
|
27
27
|
// src/adapters/openai-types.ts
|
|
28
28
|
var OPENAI_MODELS = {
|
|
@@ -622,7 +622,7 @@ function createOpenAIAdapter(config) {
|
|
|
622
622
|
}
|
|
623
623
|
|
|
624
624
|
// src/config/opencode-bridge.ts
|
|
625
|
-
import { readFileSync } from "fs";
|
|
625
|
+
import { readFileSync } from "node:fs";
|
|
626
626
|
var logger = createLogger({ component: "opencode-bridge" });
|
|
627
627
|
function readOpencodeGateway(path) {
|
|
628
628
|
const raw = readFileOrNull(path);
|
|
@@ -833,4 +833,4 @@ export {
|
|
|
833
833
|
discoverModels,
|
|
834
834
|
buildOpenAICompatAdapters
|
|
835
835
|
};
|
|
836
|
-
//# sourceMappingURL=chunk-
|
|
836
|
+
//# sourceMappingURL=chunk-EGWPONTU.js.map
|