nexus-agents 2.28.0 → 2.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -10
- package/dist/adaptive-memory-RST6DZYR.js +15 -0
- package/dist/chunk-2UR7YN6T.js +700 -0
- package/dist/chunk-2UR7YN6T.js.map +1 -0
- package/dist/chunk-2UUUKVNR.js +61 -0
- package/dist/chunk-2UUUKVNR.js.map +1 -0
- package/dist/chunk-3EVVQ32X.js +838 -0
- package/dist/chunk-3EVVQ32X.js.map +1 -0
- package/dist/{chunk-UGNLR4NZ.js → chunk-3GXDN4AX.js} +2 -2
- package/dist/{chunk-LKSTILEE.js → chunk-4AGPR6XZ.js} +194 -1461
- package/dist/chunk-4AGPR6XZ.js.map +1 -0
- package/dist/chunk-4HA5PAL7.js +246 -0
- package/dist/chunk-4HA5PAL7.js.map +1 -0
- package/dist/chunk-633WH2ML.js +127 -0
- package/dist/chunk-633WH2ML.js.map +1 -0
- package/dist/chunk-7SKAKA4I.js +90 -0
- package/dist/chunk-7SKAKA4I.js.map +1 -0
- package/dist/chunk-AP2FD37C.js +127 -0
- package/dist/chunk-AP2FD37C.js.map +1 -0
- package/dist/chunk-BC3M4VLP.js +359 -0
- package/dist/chunk-BC3M4VLP.js.map +1 -0
- package/dist/chunk-BQ4YXGGQ.js +127 -0
- package/dist/chunk-BQ4YXGGQ.js.map +1 -0
- package/dist/chunk-C2C5ONFR.js +195 -0
- package/dist/chunk-C2C5ONFR.js.map +1 -0
- package/dist/chunk-CGWRJ4EM.js +12661 -0
- package/dist/chunk-CGWRJ4EM.js.map +1 -0
- package/dist/chunk-CLYZ7FWP.js +30 -0
- package/dist/chunk-CLYZ7FWP.js.map +1 -0
- package/dist/chunk-ED6VQWNG.js +63 -0
- package/dist/chunk-ED6VQWNG.js.map +1 -0
- package/dist/{chunk-L2SHSW4T.js → chunk-ELIFTCYM.js} +2705 -2473
- package/dist/chunk-ELIFTCYM.js.map +1 -0
- package/dist/chunk-FYJVXQHX.js +944 -0
- package/dist/chunk-FYJVXQHX.js.map +1 -0
- package/dist/{chunk-QZEAD6AG.js → chunk-FZFZ77UJ.js} +19434 -38182
- package/dist/chunk-FZFZ77UJ.js.map +1 -0
- package/dist/chunk-IECE6DBS.js +1585 -0
- package/dist/chunk-IECE6DBS.js.map +1 -0
- package/dist/chunk-LLGUX44Z.js +356 -0
- package/dist/chunk-LLGUX44Z.js.map +1 -0
- package/dist/chunk-MRU6L7YJ.js +931 -0
- package/dist/chunk-MRU6L7YJ.js.map +1 -0
- package/dist/chunk-N5SY7V45.js +44 -0
- package/dist/chunk-N5SY7V45.js.map +1 -0
- package/dist/chunk-OOIPRRPX.js +3790 -0
- package/dist/chunk-OOIPRRPX.js.map +1 -0
- package/dist/chunk-SRECH7OQ.js +354 -0
- package/dist/chunk-SRECH7OQ.js.map +1 -0
- package/dist/{chunk-YSDUVCCZ.js → chunk-VKRMXD62.js} +6 -6
- package/dist/{chunk-E7EX2KQJ.js → chunk-XU3NADFE.js} +2 -2
- package/dist/chunk-YSTJEMQX.js +122 -0
- package/dist/chunk-YSTJEMQX.js.map +1 -0
- package/dist/cli-circuit-breaker-5FAODXVY.js +13 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +1485 -683
- package/dist/cli.js.map +1 -1
- package/dist/codebase-search-CZUA37RU.js +9 -0
- package/dist/composite-router-FNW7ZWL7.js +17 -0
- package/dist/composite-router-FNW7ZWL7.js.map +1 -0
- package/dist/consensus-vote-757YULIP.js +25 -0
- package/dist/consensus-vote-757YULIP.js.map +1 -0
- package/dist/{dist-H5XNXVAV.js → dist-CV74KUT7.js} +1521 -972
- package/dist/dist-CV74KUT7.js.map +1 -0
- package/dist/{doctor-deep-BDE2PHVX.js → doctor-deep-LMCEAFU4.js} +4 -3
- package/dist/doctor-deep-LMCEAFU4.js.map +1 -0
- package/dist/expert-bridge-L2D4OXOR.js +10 -0
- package/dist/expert-bridge-L2D4OXOR.js.map +1 -0
- package/dist/expert-config-A5CHKUGI.js +22 -0
- package/dist/expert-config-A5CHKUGI.js.map +1 -0
- package/dist/factory-ELEDP2WD.js +17 -0
- package/dist/factory-ELEDP2WD.js.map +1 -0
- package/dist/factory-IDTIBX6B.js +14 -0
- package/dist/factory-IDTIBX6B.js.map +1 -0
- package/dist/index.d.ts +1814 -777
- package/dist/index.js +785 -355
- package/dist/index.js.map +1 -1
- package/dist/issue-triage-SJPKJLXH.js +15 -0
- package/dist/issue-triage-SJPKJLXH.js.map +1 -0
- package/dist/learning-persistence-WMWZJZ35.js +16 -0
- package/dist/learning-persistence-WMWZJZ35.js.map +1 -0
- package/dist/mcp-config-2OXIOMJ6.js +12 -0
- package/dist/mcp-config-2OXIOMJ6.js.map +1 -0
- package/dist/mobimem-5S3VLNSU.js +13 -0
- package/dist/mobimem-5S3VLNSU.js.map +1 -0
- package/dist/{model-capabilities-types-CSWO27YN.d.ts → model-capabilities-types-B57GZryc.d.ts} +1 -1
- package/dist/repo-analyze-HWMXSK5C.js +24 -0
- package/dist/repo-analyze-HWMXSK5C.js.map +1 -0
- package/dist/repo-security-plan-MUFDGWSQ.js +17 -0
- package/dist/repo-security-plan-MUFDGWSQ.js.map +1 -0
- package/dist/research-helpers-synthesize-OBQJ5BGX.js +10 -0
- package/dist/research-helpers-synthesize-OBQJ5BGX.js.map +1 -0
- package/dist/routing-memory-3QBQTS4A.js +13 -0
- package/dist/routing-memory-3QBQTS4A.js.map +1 -0
- package/dist/session-memory-VXWLOFRC.js +22 -0
- package/dist/session-memory-VXWLOFRC.js.map +1 -0
- package/dist/{setup-command-SS7LMN7Y.js → setup-command-E6MXO5RZ.js} +9 -5
- package/dist/setup-command-E6MXO5RZ.js.map +1 -0
- package/dist/setup-config-O5F3AZBL.js +10 -0
- package/dist/setup-config-O5F3AZBL.js.map +1 -0
- package/dist/shared-memory-AEO2HJLC.js +8 -0
- package/dist/shared-memory-AEO2HJLC.js.map +1 -0
- package/dist/symbol-extractor-UEBANFSN.js +10 -0
- package/dist/symbol-extractor-UEBANFSN.js.map +1 -0
- package/dist/weather-report-MUGSIOU5.js +15 -0
- package/dist/weather-report-MUGSIOU5.js.map +1 -0
- package/package.json +18 -19
- package/dist/chunk-L2SHSW4T.js.map +0 -1
- package/dist/chunk-LKSTILEE.js.map +0 -1
- package/dist/chunk-QZEAD6AG.js.map +0 -1
- package/dist/dist-H5XNXVAV.js.map +0 -1
- package/dist/setup-config-DSMOOLVW.js +0 -9
- /package/dist/{doctor-deep-BDE2PHVX.js.map → adaptive-memory-RST6DZYR.js.map} +0 -0
- /package/dist/{chunk-UGNLR4NZ.js.map → chunk-3GXDN4AX.js.map} +0 -0
- /package/dist/{chunk-YSDUVCCZ.js.map → chunk-VKRMXD62.js.map} +0 -0
- /package/dist/{chunk-E7EX2KQJ.js.map → chunk-XU3NADFE.js.map} +0 -0
- /package/dist/{setup-command-SS7LMN7Y.js.map → cli-circuit-breaker-5FAODXVY.js.map} +0 -0
- /package/dist/{setup-config-DSMOOLVW.js.map → codebase-search-CZUA37RU.js.map} +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractSymbols
|
|
3
|
+
} from "./chunk-BQ4YXGGQ.js";
|
|
4
|
+
|
|
5
|
+
// src/indexer/codebase-search.ts
|
|
6
|
+
import { readdir } from "fs/promises";
|
|
7
|
+
import { resolve, extname, relative } from "path";
|
|
8
|
+
var SCORE_EXACT = 20;
|
|
9
|
+
var SCORE_PREFIX = 10;
|
|
10
|
+
var SCORE_WORD = 5;
|
|
11
|
+
var SCORE_SUBSTRING = 2;
|
|
12
|
+
var SCORE_EXPORTED_BONUS = 3;
|
|
13
|
+
function isSourceFile(name) {
|
|
14
|
+
const ext = extname(name).toLowerCase();
|
|
15
|
+
return [".ts", ".tsx", ".js", ".jsx"].includes(ext) && !name.endsWith(".test.ts") && !name.endsWith(".test.tsx") && !name.endsWith(".d.ts");
|
|
16
|
+
}
|
|
17
|
+
async function findSourceFiles(dir, maxDepth) {
|
|
18
|
+
if (maxDepth <= 0) return [];
|
|
19
|
+
const files = [];
|
|
20
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const fullPath = resolve(dir, entry.name);
|
|
23
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist") {
|
|
24
|
+
files.push(...await findSourceFiles(fullPath, maxDepth - 1));
|
|
25
|
+
}
|
|
26
|
+
if (entry.isFile() && isSourceFile(entry.name)) {
|
|
27
|
+
files.push(fullPath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
32
|
+
function scoreMatch(symbolName, query) {
|
|
33
|
+
const nameLower = symbolName.toLowerCase();
|
|
34
|
+
const queryLower = query.toLowerCase();
|
|
35
|
+
if (nameLower === queryLower) return SCORE_EXACT;
|
|
36
|
+
if (nameLower.startsWith(queryLower)) return SCORE_PREFIX;
|
|
37
|
+
const words = symbolName.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[\s_-]+/);
|
|
38
|
+
if (words.some((w) => w === queryLower)) return SCORE_WORD;
|
|
39
|
+
if (nameLower.includes(queryLower)) return SCORE_SUBSTRING;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function getMatchType(score) {
|
|
43
|
+
if (score >= SCORE_EXACT) return "exact";
|
|
44
|
+
if (score >= SCORE_PREFIX) return "prefix";
|
|
45
|
+
if (score >= SCORE_WORD) return "word";
|
|
46
|
+
return "substring";
|
|
47
|
+
}
|
|
48
|
+
var CodebaseIndex = class {
|
|
49
|
+
symbols = [];
|
|
50
|
+
fileResults = /* @__PURE__ */ new Map();
|
|
51
|
+
rootDir;
|
|
52
|
+
constructor(rootDir) {
|
|
53
|
+
this.rootDir = rootDir;
|
|
54
|
+
}
|
|
55
|
+
/** Index all TS/JS source files in the directory. */
|
|
56
|
+
async index(maxDepth = 4) {
|
|
57
|
+
const files = await findSourceFiles(this.rootDir, maxDepth);
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
const result = await extractSymbols(file);
|
|
60
|
+
const relPath = relative(this.rootDir, file);
|
|
61
|
+
this.fileResults.set(relPath, result);
|
|
62
|
+
for (const symbol of result.symbols) {
|
|
63
|
+
this.symbols.push({ ...symbol, filePath: relPath });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
totalFiles: files.length,
|
|
68
|
+
totalSymbols: this.symbols.length,
|
|
69
|
+
indexedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Search symbols by keyword. Returns top N results sorted by relevance. */
|
|
73
|
+
search(query, limit = 20) {
|
|
74
|
+
const results = [];
|
|
75
|
+
for (const symbol of this.symbols) {
|
|
76
|
+
const baseScore = scoreMatch(symbol.name, query);
|
|
77
|
+
if (baseScore === null) continue;
|
|
78
|
+
const bonus = symbol.exported ? SCORE_EXPORTED_BONUS : 0;
|
|
79
|
+
results.push({
|
|
80
|
+
symbol,
|
|
81
|
+
score: baseScore + bonus,
|
|
82
|
+
matchType: getMatchType(baseScore)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
86
|
+
}
|
|
87
|
+
/** Get a compact summary of a file's symbols. */
|
|
88
|
+
getFileSummary(filePath) {
|
|
89
|
+
const result = this.fileResults.get(filePath);
|
|
90
|
+
if (result === void 0) return void 0;
|
|
91
|
+
const kinds = {};
|
|
92
|
+
let exported = 0;
|
|
93
|
+
let priv = 0;
|
|
94
|
+
for (const s of result.symbols) {
|
|
95
|
+
kinds[s.kind] = (kinds[s.kind] ?? 0) + 1;
|
|
96
|
+
if (s.exported) exported++;
|
|
97
|
+
else priv++;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
filePath,
|
|
101
|
+
totalLines: result.totalLines,
|
|
102
|
+
exportedSymbols: exported,
|
|
103
|
+
privateSymbols: priv,
|
|
104
|
+
kinds
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** List all indexed files with symbol counts. */
|
|
108
|
+
listFiles() {
|
|
109
|
+
return [...this.fileResults.entries()].map(([path, result]) => ({
|
|
110
|
+
path,
|
|
111
|
+
symbols: result.symbols.length,
|
|
112
|
+
lines: result.totalLines
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
/** Get index statistics. */
|
|
116
|
+
get stats() {
|
|
117
|
+
return {
|
|
118
|
+
files: this.fileResults.size,
|
|
119
|
+
symbols: this.symbols.length
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
CodebaseIndex
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=chunk-AP2FD37C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/indexer/codebase-search.ts"],"sourcesContent":["/**\n * Codebase search — keyword search across symbol indices.\n *\n * Builds an in-memory symbol index for a directory of TS/JS files,\n * then supports keyword search, file summaries, and symbol lookup.\n *\n * Inspired by Augment Code's Context Engine. Uses the existing\n * extractSymbols() function for AST parsing.\n *\n * @module indexer/codebase-search\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { resolve, extname, relative } from 'node:path';\nimport {\n extractSymbols,\n type CodeSymbol,\n type SymbolExtractionResult,\n} from './symbol-extractor.js';\n\n/** A symbol with its source file path. */\nexport interface IndexedSymbol extends CodeSymbol {\n /** Relative file path from the indexed root. */\n filePath: string;\n}\n\n/** Search result with relevance scoring. */\nexport interface SearchResult {\n symbol: IndexedSymbol;\n /** Relevance score (higher = better match). */\n score: number;\n /** How the query matched (exact, prefix, substring, word). */\n matchType: 'exact' | 'prefix' | 'substring' | 'word';\n}\n\n/** File summary — compact overview of a source file. */\nexport interface FileSummary {\n filePath: string;\n totalLines: number;\n exportedSymbols: number;\n privateSymbols: number;\n kinds: Record<string, number>;\n}\n\n/** Index statistics. */\nexport interface IndexStats {\n totalFiles: number;\n totalSymbols: number;\n indexedAt: string;\n}\n\n// Score weights for different match types\nconst SCORE_EXACT = 20;\nconst SCORE_PREFIX = 10;\nconst SCORE_WORD = 5;\nconst SCORE_SUBSTRING = 2;\nconst SCORE_EXPORTED_BONUS = 3;\n\nfunction isSourceFile(name: string): boolean {\n const ext = extname(name).toLowerCase();\n return (\n ['.ts', '.tsx', '.js', '.jsx'].includes(ext) &&\n !name.endsWith('.test.ts') &&\n !name.endsWith('.test.tsx') &&\n !name.endsWith('.d.ts')\n );\n}\n\nasync function findSourceFiles(dir: string, maxDepth: number): Promise<string[]> {\n if (maxDepth <= 0) return [];\n const files: string[] = [];\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n for (const entry of entries) {\n const fullPath = resolve(dir, entry.name);\n if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {\n files.push(...(await findSourceFiles(fullPath, maxDepth - 1)));\n }\n if (entry.isFile() && isSourceFile(entry.name)) {\n files.push(fullPath);\n }\n }\n return files;\n}\n\nfunction scoreMatch(symbolName: string, query: string): SearchResult['score'] | null {\n const nameLower = symbolName.toLowerCase();\n const queryLower = query.toLowerCase();\n\n if (nameLower === queryLower) return SCORE_EXACT;\n if (nameLower.startsWith(queryLower)) return SCORE_PREFIX;\n\n // Word boundary match (camelCase splitting)\n const words = symbolName\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .toLowerCase()\n .split(/[\\s_-]+/);\n if (words.some((w) => w === queryLower)) return SCORE_WORD;\n\n if (nameLower.includes(queryLower)) return SCORE_SUBSTRING;\n\n return null;\n}\n\nfunction getMatchType(score: number): SearchResult['matchType'] {\n if (score >= SCORE_EXACT) return 'exact';\n if (score >= SCORE_PREFIX) return 'prefix';\n if (score >= SCORE_WORD) return 'word';\n return 'substring';\n}\n\n/** In-memory codebase symbol index. */\nexport class CodebaseIndex {\n private readonly symbols: IndexedSymbol[] = [];\n private readonly fileResults = new Map<string, SymbolExtractionResult>();\n private readonly rootDir: string;\n\n constructor(rootDir: string) {\n this.rootDir = rootDir;\n }\n\n /** Index all TS/JS source files in the directory. */\n async index(maxDepth = 4): Promise<IndexStats> {\n const files = await findSourceFiles(this.rootDir, maxDepth);\n\n for (const file of files) {\n const result = await extractSymbols(file);\n const relPath = relative(this.rootDir, file);\n this.fileResults.set(relPath, result);\n\n for (const symbol of result.symbols) {\n this.symbols.push({ ...symbol, filePath: relPath });\n }\n }\n\n return {\n totalFiles: files.length,\n totalSymbols: this.symbols.length,\n indexedAt: new Date().toISOString(),\n };\n }\n\n /** Search symbols by keyword. Returns top N results sorted by relevance. */\n search(query: string, limit = 20): SearchResult[] {\n const results: SearchResult[] = [];\n\n for (const symbol of this.symbols) {\n const baseScore = scoreMatch(symbol.name, query);\n if (baseScore === null) continue;\n\n const bonus = symbol.exported ? SCORE_EXPORTED_BONUS : 0;\n results.push({\n symbol,\n score: baseScore + bonus,\n matchType: getMatchType(baseScore),\n });\n }\n\n return results.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n\n /** Get a compact summary of a file's symbols. */\n getFileSummary(filePath: string): FileSummary | undefined {\n const result = this.fileResults.get(filePath);\n if (result === undefined) return undefined;\n\n const kinds: Record<string, number> = {};\n let exported = 0;\n let priv = 0;\n\n for (const s of result.symbols) {\n kinds[s.kind] = (kinds[s.kind] ?? 0) + 1;\n if (s.exported) exported++;\n else priv++;\n }\n\n return {\n filePath,\n totalLines: result.totalLines,\n exportedSymbols: exported,\n privateSymbols: priv,\n kinds,\n };\n }\n\n /** List all indexed files with symbol counts. */\n listFiles(): Array<{ path: string; symbols: number; lines: number }> {\n return [...this.fileResults.entries()].map(([path, result]) => ({\n path,\n symbols: result.symbols.length,\n lines: result.totalLines,\n }));\n }\n\n /** Get index statistics. */\n get stats(): { files: number; symbols: number } {\n return {\n files: this.fileResults.size,\n symbols: this.symbols.length,\n };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// src/mcp/tools/repo-analyze.ts
|
|
2
|
+
function normalizeRepoId(input) {
|
|
3
|
+
const urlMatch = /github\.com\/([^/]+\/[^/]+?)(?:\.git)?(?:\/|$)/.exec(input);
|
|
4
|
+
const matched = urlMatch?.[1] ?? "";
|
|
5
|
+
if (matched.length > 0) return matched;
|
|
6
|
+
if (/^[^/]+\/[^/]+$/.test(input)) return input;
|
|
7
|
+
throw new Error(`Invalid repo format: "${input}". Use "owner/name" or a GitHub URL.`);
|
|
8
|
+
}
|
|
9
|
+
var PACKAGE_MANAGER_RULES = [
|
|
10
|
+
[["pnpm-lock.yaml"], "pnpm"],
|
|
11
|
+
[["yarn.lock"], "yarn"],
|
|
12
|
+
[["package-lock.json", "package.json"], "npm"],
|
|
13
|
+
[["Cargo.toml"], "cargo"],
|
|
14
|
+
[["go.mod"], "go"],
|
|
15
|
+
[["requirements.txt", "pyproject.toml"], "pip"],
|
|
16
|
+
[["Gemfile"], "bundler"],
|
|
17
|
+
[["pom.xml"], "maven"],
|
|
18
|
+
[["build.gradle", "build.gradle.kts"], "gradle"]
|
|
19
|
+
];
|
|
20
|
+
function detectPackageManager(entries) {
|
|
21
|
+
for (const [files, manager] of PACKAGE_MANAGER_RULES) {
|
|
22
|
+
if (files.some((f) => entries.includes(f))) return manager;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function detectCiProvider(entries) {
|
|
27
|
+
if (entries.includes(".github")) return "github-actions";
|
|
28
|
+
if (entries.includes(".gitlab-ci.yml")) return "gitlab-ci";
|
|
29
|
+
if (entries.includes("Jenkinsfile")) return "jenkins";
|
|
30
|
+
if (entries.includes(".circleci")) return "circleci";
|
|
31
|
+
if (entries.includes(".travis.yml")) return "travis";
|
|
32
|
+
if (entries.includes("azure-pipelines.yml")) return "azure-devops";
|
|
33
|
+
if (entries.includes("concourse")) return "concourse";
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
var WORKFLOW_SECURITY_PATTERNS = [
|
|
37
|
+
["semgrep", "semgrep"],
|
|
38
|
+
["codeql", "codeql"],
|
|
39
|
+
["grype", "grype"],
|
|
40
|
+
["snyk", "snyk"]
|
|
41
|
+
];
|
|
42
|
+
function detectWorkflowSecurity(workflowEntries, existing) {
|
|
43
|
+
const wfLower = workflowEntries.map((w) => w.toLowerCase());
|
|
44
|
+
const found = [];
|
|
45
|
+
for (const [pattern, tool] of WORKFLOW_SECURITY_PATTERNS) {
|
|
46
|
+
if (!existing.includes(tool) && wfLower.some((w) => w.includes(pattern))) {
|
|
47
|
+
found.push(tool);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
function detectSecurityTooling(entries, workflowEntries) {
|
|
53
|
+
const tools = [];
|
|
54
|
+
if (entries.includes(".semgrep.yml") || entries.includes(".semgrep")) tools.push("semgrep");
|
|
55
|
+
if (entries.includes(".snyk")) tools.push("snyk");
|
|
56
|
+
if (entries.includes("SECURITY.md")) tools.push("security-policy");
|
|
57
|
+
if (entries.includes(".grype.yaml")) tools.push("grype");
|
|
58
|
+
if (entries.includes("CODEOWNERS")) tools.push("codeowners");
|
|
59
|
+
if (workflowEntries !== void 0) {
|
|
60
|
+
tools.push(...detectWorkflowSecurity(workflowEntries, tools));
|
|
61
|
+
}
|
|
62
|
+
return tools;
|
|
63
|
+
}
|
|
64
|
+
function detectFramework(entries) {
|
|
65
|
+
if (entries.includes("helmfile.yaml") || entries.includes("helmfile.yaml.gotmpl"))
|
|
66
|
+
return "helmfile";
|
|
67
|
+
if (entries.includes("next.config.js") || entries.includes("next.config.ts")) return "nextjs";
|
|
68
|
+
if (entries.includes("angular.json")) return "angular";
|
|
69
|
+
if (entries.includes("vite.config.ts") || entries.includes("vite.config.js")) return "vite";
|
|
70
|
+
if (entries.includes("tsconfig.json") && entries.includes("package.json")) return "typescript";
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
var GAP_RULES = [
|
|
74
|
+
[["SECURITY.md"], "No SECURITY.md policy"],
|
|
75
|
+
[["CODEOWNERS"], "No CODEOWNERS file"],
|
|
76
|
+
[["LICENSE", "LICENSE.md"], "No LICENSE file"],
|
|
77
|
+
[
|
|
78
|
+
[".semgrep.yml", ".semgrep", ".grype.yaml", ".snyk"],
|
|
79
|
+
"No SAST/SCA security scanning configured"
|
|
80
|
+
],
|
|
81
|
+
// Test detection handled separately via detectTestInfra (supports monorepo + co-located patterns)
|
|
82
|
+
[[".gitignore"], "No .gitignore file"]
|
|
83
|
+
];
|
|
84
|
+
var LANGUAGE_SCANNER_MATRIX = {
|
|
85
|
+
TypeScript: {
|
|
86
|
+
sast: ["semgrep (p/typescript, p/nodejs)", "eslint-plugin-security"],
|
|
87
|
+
sca: ["osv-scanner", "npm audit"]
|
|
88
|
+
},
|
|
89
|
+
JavaScript: {
|
|
90
|
+
sast: ["semgrep (p/javascript, p/nodejs)", "eslint-plugin-security"],
|
|
91
|
+
sca: ["osv-scanner", "npm audit"]
|
|
92
|
+
},
|
|
93
|
+
Python: {
|
|
94
|
+
sast: ["semgrep (p/python)", "bandit"],
|
|
95
|
+
sca: ["osv-scanner", "pip-audit"]
|
|
96
|
+
},
|
|
97
|
+
Java: {
|
|
98
|
+
sast: ["semgrep (p/java)", "spotbugs + find-sec-bugs"],
|
|
99
|
+
sca: ["osv-scanner", "OWASP dependency-check"]
|
|
100
|
+
},
|
|
101
|
+
Go: {
|
|
102
|
+
sast: ["semgrep (p/golang)", "gosec"],
|
|
103
|
+
sca: ["osv-scanner", "govulncheck"]
|
|
104
|
+
},
|
|
105
|
+
Rust: {
|
|
106
|
+
sast: ["semgrep (p/rust)"],
|
|
107
|
+
sca: ["osv-scanner", "cargo-audit"]
|
|
108
|
+
},
|
|
109
|
+
"C++": {
|
|
110
|
+
sast: ["semgrep (p/c)", "cppcheck"],
|
|
111
|
+
sca: ["osv-scanner"]
|
|
112
|
+
},
|
|
113
|
+
C: {
|
|
114
|
+
sast: ["semgrep (p/c)", "cppcheck"],
|
|
115
|
+
sca: ["osv-scanner"]
|
|
116
|
+
},
|
|
117
|
+
Kotlin: {
|
|
118
|
+
sast: ["semgrep (p/kotlin)", "detekt"],
|
|
119
|
+
sca: ["osv-scanner", "OWASP dependency-check"]
|
|
120
|
+
},
|
|
121
|
+
Swift: {
|
|
122
|
+
sast: ["semgrep (p/swift)"],
|
|
123
|
+
sca: ["osv-scanner"]
|
|
124
|
+
},
|
|
125
|
+
Ruby: {
|
|
126
|
+
sast: ["semgrep (p/ruby)", "brakeman"],
|
|
127
|
+
sca: ["osv-scanner", "bundler-audit"]
|
|
128
|
+
},
|
|
129
|
+
PHP: {
|
|
130
|
+
sast: ["semgrep (p/php)", "phpstan"],
|
|
131
|
+
sca: ["osv-scanner", "composer audit"]
|
|
132
|
+
},
|
|
133
|
+
Shell: {
|
|
134
|
+
sast: ["semgrep (p/bash)", "shellcheck"],
|
|
135
|
+
sca: []
|
|
136
|
+
},
|
|
137
|
+
HCL: {
|
|
138
|
+
sast: ["semgrep (p/terraform)", "tfsec"],
|
|
139
|
+
sca: ["osv-scanner"]
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
function getLanguageRecommendations(language, securityTooling) {
|
|
143
|
+
if (language === null) return [];
|
|
144
|
+
const scanners = LANGUAGE_SCANNER_MATRIX[language];
|
|
145
|
+
if (scanners === void 0) return [];
|
|
146
|
+
const hasSast = securityTooling.includes("semgrep") || securityTooling.includes("snyk");
|
|
147
|
+
const hasSca = securityTooling.includes("osv-scanner") || securityTooling.includes("grype") || securityTooling.includes("snyk");
|
|
148
|
+
const recs = [];
|
|
149
|
+
if (!hasSast && scanners.sast.length > 0) {
|
|
150
|
+
const tools = scanners.sast.join(", ");
|
|
151
|
+
recs.push(`${language} project missing SAST: ${tools}`);
|
|
152
|
+
}
|
|
153
|
+
if (!hasSca && scanners.sca.length > 0) {
|
|
154
|
+
const tools = scanners.sca.join(", ");
|
|
155
|
+
recs.push(`${language} project missing SCA: ${tools}`);
|
|
156
|
+
}
|
|
157
|
+
return recs;
|
|
158
|
+
}
|
|
159
|
+
var SAST_TOOLS = /* @__PURE__ */ new Set(["semgrep", "codeql", "snyk"]);
|
|
160
|
+
var SAST_GAP_MSG = "No SAST/SCA security scanning configured";
|
|
161
|
+
function removeSastGapIfToolDetected(gaps, secTools) {
|
|
162
|
+
if (secTools.some((t) => SAST_TOOLS.has(t))) {
|
|
163
|
+
const idx = gaps.indexOf(SAST_GAP_MSG);
|
|
164
|
+
if (idx !== -1) gaps.splice(idx, 1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function identifyGaps(entries, ciProvider, language, securityTooling) {
|
|
168
|
+
const gaps = [];
|
|
169
|
+
if (ciProvider === null) gaps.push("No CI/CD configuration detected");
|
|
170
|
+
for (const [files, message] of GAP_RULES) {
|
|
171
|
+
if (!files.some((f) => entries.includes(f))) gaps.push(message);
|
|
172
|
+
}
|
|
173
|
+
if (!detectTestInfra(entries)) gaps.push("No test directory detected");
|
|
174
|
+
removeSastGapIfToolDetected(gaps, securityTooling ?? []);
|
|
175
|
+
const hasGenericSecGap = gaps.includes("No SAST/SCA security scanning configured");
|
|
176
|
+
if (hasGenericSecGap && language !== null && language !== void 0 && securityTooling !== void 0) {
|
|
177
|
+
const langRecs = getLanguageRecommendations(language, securityTooling);
|
|
178
|
+
gaps.push(...langRecs);
|
|
179
|
+
}
|
|
180
|
+
return gaps;
|
|
181
|
+
}
|
|
182
|
+
function analyzeRepo(metadata, topLevelEntries, workflowEntries) {
|
|
183
|
+
const ciProvider = detectCiProvider(topLevelEntries);
|
|
184
|
+
const secTooling = detectSecurityTooling(topLevelEntries, workflowEntries);
|
|
185
|
+
const hasTests = detectTestInfra(topLevelEntries);
|
|
186
|
+
return {
|
|
187
|
+
name: metadata.full_name,
|
|
188
|
+
language: metadata.language,
|
|
189
|
+
framework: detectFramework(topLevelEntries),
|
|
190
|
+
packageManager: detectPackageManager(topLevelEntries),
|
|
191
|
+
ciProvider,
|
|
192
|
+
securityTooling: secTooling,
|
|
193
|
+
hasDockerfile: topLevelEntries.includes("Dockerfile") || topLevelEntries.includes("docker-compose.yml") || topLevelEntries.includes("docker-compose.yaml"),
|
|
194
|
+
hasHelmCharts: topLevelEntries.includes("Chart.yaml") || topLevelEntries.includes("charts") || topLevelEntries.includes("helm"),
|
|
195
|
+
hasMakefile: topLevelEntries.includes("Makefile"),
|
|
196
|
+
hasTests,
|
|
197
|
+
license: metadata.license?.spdx_id ?? null,
|
|
198
|
+
description: metadata.description,
|
|
199
|
+
defaultBranch: metadata.default_branch,
|
|
200
|
+
stars: metadata.stargazers_count,
|
|
201
|
+
topLevelEntries: [...topLevelEntries],
|
|
202
|
+
gaps: identifyGaps(topLevelEntries, ciProvider, metadata.language, secTooling)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
var MARKUP_LANGUAGES = /* @__PURE__ */ new Set([
|
|
206
|
+
"HTML",
|
|
207
|
+
"CSS",
|
|
208
|
+
"SCSS",
|
|
209
|
+
"Less",
|
|
210
|
+
"Markdown",
|
|
211
|
+
"Roff",
|
|
212
|
+
"SVG",
|
|
213
|
+
"XML",
|
|
214
|
+
"XSLT",
|
|
215
|
+
"Mustache",
|
|
216
|
+
"Handlebars",
|
|
217
|
+
"EJS"
|
|
218
|
+
]);
|
|
219
|
+
function detectPrimaryLanguage(languages, fallback) {
|
|
220
|
+
const sorted = Object.entries(languages).filter(([lang]) => !MARKUP_LANGUAGES.has(lang)).sort((a, b) => b[1] - a[1]);
|
|
221
|
+
const top = sorted[0];
|
|
222
|
+
return top !== void 0 ? top[0] : fallback;
|
|
223
|
+
}
|
|
224
|
+
function detectTestInfra(entries) {
|
|
225
|
+
const testDirs = ["tests", "test", "__tests__", "spec"];
|
|
226
|
+
if (testDirs.some((d) => entries.includes(d))) return true;
|
|
227
|
+
const testConfigs = [
|
|
228
|
+
"vitest.config.ts",
|
|
229
|
+
"vitest.config.js",
|
|
230
|
+
"vitest.config.mts",
|
|
231
|
+
"vitest.workspace.ts",
|
|
232
|
+
"vitest.workspace.js",
|
|
233
|
+
"jest.config.ts",
|
|
234
|
+
"jest.config.js",
|
|
235
|
+
"jest.config.mjs",
|
|
236
|
+
"cypress.config.ts",
|
|
237
|
+
"cypress.config.js",
|
|
238
|
+
"playwright.config.ts",
|
|
239
|
+
".mocharc.yml",
|
|
240
|
+
".mocharc.json"
|
|
241
|
+
];
|
|
242
|
+
if (testConfigs.some((c) => entries.includes(c))) return true;
|
|
243
|
+
return entries.includes("packages") && entries.includes("package.json");
|
|
244
|
+
}
|
|
245
|
+
function inferLanguageFromEntries(entries, fallback) {
|
|
246
|
+
if (entries.includes("tsconfig.json")) return "TypeScript";
|
|
247
|
+
if (entries.includes("Cargo.toml")) return "Rust";
|
|
248
|
+
if (entries.includes("go.mod")) return "Go";
|
|
249
|
+
if (entries.includes("pyproject.toml") || entries.includes("setup.py")) return "Python";
|
|
250
|
+
if (entries.includes("pom.xml") || entries.includes("build.gradle")) return "Java";
|
|
251
|
+
if (entries.includes("Gemfile")) return "Ruby";
|
|
252
|
+
if (entries.includes("package.json")) return "JavaScript";
|
|
253
|
+
return fallback;
|
|
254
|
+
}
|
|
255
|
+
async function getExecFile() {
|
|
256
|
+
const { execFile } = await import("child_process");
|
|
257
|
+
const { promisify } = await import("util");
|
|
258
|
+
return promisify(execFile);
|
|
259
|
+
}
|
|
260
|
+
async function fetchRepoData(repoId, exec) {
|
|
261
|
+
const { stdout: metaJson } = await exec(
|
|
262
|
+
"gh",
|
|
263
|
+
[
|
|
264
|
+
"api",
|
|
265
|
+
`repos/${repoId}`,
|
|
266
|
+
"--jq",
|
|
267
|
+
"{name: .name, full_name: .full_name, description: .description, language: .language, default_branch: .default_branch, stargazers_count: .stargazers_count, license: .license}"
|
|
268
|
+
],
|
|
269
|
+
{ timeout: 3e4 }
|
|
270
|
+
);
|
|
271
|
+
let metadata;
|
|
272
|
+
try {
|
|
273
|
+
metadata = JSON.parse(metaJson.trim());
|
|
274
|
+
} catch {
|
|
275
|
+
throw new Error(`Failed to parse repo metadata for ${repoId}: ${metaJson.slice(0, 200)}`);
|
|
276
|
+
}
|
|
277
|
+
const { stdout: contentsJson } = await exec(
|
|
278
|
+
"gh",
|
|
279
|
+
["api", `repos/${repoId}/contents`, "--jq", "[.[].name]"],
|
|
280
|
+
{ timeout: 3e4 }
|
|
281
|
+
);
|
|
282
|
+
let entries;
|
|
283
|
+
try {
|
|
284
|
+
const parsed = JSON.parse(contentsJson.trim());
|
|
285
|
+
entries = Array.isArray(parsed) ? parsed.filter((e) => typeof e === "string") : [];
|
|
286
|
+
} catch {
|
|
287
|
+
throw new Error(`Failed to parse repo contents for ${repoId}: ${contentsJson.slice(0, 200)}`);
|
|
288
|
+
}
|
|
289
|
+
return { metadata, entries };
|
|
290
|
+
}
|
|
291
|
+
async function resolveLicense(repoId, exec) {
|
|
292
|
+
try {
|
|
293
|
+
const { stdout } = await exec(
|
|
294
|
+
"gh",
|
|
295
|
+
["api", `repos/${repoId}/license`, "--jq", ".license.spdx_id"],
|
|
296
|
+
{ timeout: 15e3 }
|
|
297
|
+
);
|
|
298
|
+
const spdxId = stdout.trim();
|
|
299
|
+
if (spdxId !== "" && spdxId !== "null" && spdxId !== "NOASSERTION") {
|
|
300
|
+
return spdxId;
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
async function resolveLanguage(repoId, entries, metadata, exec) {
|
|
307
|
+
let languages = {};
|
|
308
|
+
try {
|
|
309
|
+
const { stdout } = await exec("gh", ["api", `repos/${repoId}/languages`], { timeout: 15e3 });
|
|
310
|
+
languages = JSON.parse(stdout.trim());
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
const primary = detectPrimaryLanguage(languages, metadata.language);
|
|
314
|
+
if (primary === null || MARKUP_LANGUAGES.has(primary)) {
|
|
315
|
+
return inferLanguageFromEntries(entries, primary);
|
|
316
|
+
}
|
|
317
|
+
return primary;
|
|
318
|
+
}
|
|
319
|
+
async function fetchWorkflowEntries(repoId, exec) {
|
|
320
|
+
try {
|
|
321
|
+
const { stdout } = await exec(
|
|
322
|
+
"gh",
|
|
323
|
+
["api", `repos/${repoId}/contents/.github/workflows`, "--jq", "[.[].name]"],
|
|
324
|
+
{ timeout: 15e3 }
|
|
325
|
+
);
|
|
326
|
+
const parsed = JSON.parse(stdout.trim());
|
|
327
|
+
return Array.isArray(parsed) ? parsed.filter((e) => typeof e === "string") : [];
|
|
328
|
+
} catch {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function analyzeGitHubRepo(input) {
|
|
333
|
+
const repoId = normalizeRepoId(input.repo);
|
|
334
|
+
const exec = await getExecFile();
|
|
335
|
+
const { metadata, entries } = await fetchRepoData(repoId, exec);
|
|
336
|
+
const primaryLang = await resolveLanguage(repoId, entries, metadata, exec);
|
|
337
|
+
const enhanced = { ...metadata, language: primaryLang };
|
|
338
|
+
const hasLicenseFile = entries.includes("LICENSE") || entries.includes("LICENSE.md");
|
|
339
|
+
const licenseUnresolved = enhanced.license === null || enhanced.license.spdx_id === "NOASSERTION";
|
|
340
|
+
if (licenseUnresolved && hasLicenseFile) {
|
|
341
|
+
const resolved = await resolveLicense(repoId, exec);
|
|
342
|
+
if (resolved !== null) enhanced.license = { spdx_id: resolved };
|
|
343
|
+
}
|
|
344
|
+
const workflowEntries = entries.includes(".github") ? await fetchWorkflowEntries(repoId, exec) : [];
|
|
345
|
+
return analyzeRepo(enhanced, entries, workflowEntries);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export {
|
|
349
|
+
normalizeRepoId,
|
|
350
|
+
detectPackageManager,
|
|
351
|
+
detectCiProvider,
|
|
352
|
+
detectSecurityTooling,
|
|
353
|
+
detectFramework,
|
|
354
|
+
getLanguageRecommendations,
|
|
355
|
+
identifyGaps,
|
|
356
|
+
analyzeRepo,
|
|
357
|
+
analyzeGitHubRepo
|
|
358
|
+
};
|
|
359
|
+
//# sourceMappingURL=chunk-BC3M4VLP.js.map
|
|
@@ -0,0 +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 type { RepoAnalyzeInput, RepoAnalysis } from './repo-analyze-types.js';\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/** 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 if (entries.includes('.semgrep.yml') || entries.includes('.semgrep')) tools.push('semgrep');\n if (entries.includes('.snyk')) tools.push('snyk');\n if (entries.includes('SECURITY.md')) tools.push('security-policy');\n if (entries.includes('.grype.yaml')) tools.push('grype');\n if (entries.includes('CODEOWNERS')) tools.push('codeowners');\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 hasDockerfile:\n topLevelEntries.includes('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 metadata: GhRepoMetadata;\n try {\n metadata = JSON.parse(metaJson.trim()) as GhRepoMetadata;\n } catch {\n throw new Error(`Failed to parse repo metadata for ${repoId}: ${metaJson.slice(0, 200)}`);\n }\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":";AAkBO,SAAS,gBAAgB,OAAuB;AACrD,QAAM,WAAW,iDAAiD,KAAK,KAAK;AAC5E,QAAM,UAAU,WAAW,CAAC,KAAK;AACjC,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,MAAI,iBAAiB,KAAK,KAAK,EAAG,QAAO;AACzC,QAAM,IAAI,MAAM,yBAAyB,KAAK,sCAAsC;AACtF;AAGA,IAAM,wBAA6E;AAAA,EACjF,CAAC,CAAC,gBAAgB,GAAG,MAAM;AAAA,EAC3B,CAAC,CAAC,WAAW,GAAG,MAAM;AAAA,EACtB,CAAC,CAAC,qBAAqB,cAAc,GAAG,KAAK;AAAA,EAC7C,CAAC,CAAC,YAAY,GAAG,OAAO;AAAA,EACxB,CAAC,CAAC,QAAQ,GAAG,IAAI;AAAA,EACjB,CAAC,CAAC,oBAAoB,gBAAgB,GAAG,KAAK;AAAA,EAC9C,CAAC,CAAC,SAAS,GAAG,SAAS;AAAA,EACvB,CAAC,CAAC,SAAS,GAAG,OAAO;AAAA,EACrB,CAAC,CAAC,gBAAgB,kBAAkB,GAAG,QAAQ;AACjD;AAGO,SAAS,qBAAqB,SAA2C;AAC9E,aAAW,CAAC,OAAO,OAAO,KAAK,uBAAuB;AACpD,QAAI,MAAM,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,EAAG,QAAO;AAAA,EACrD;AACA,SAAO;AACT;AAGO,SAAS,iBAAiB,SAA2C;AAC1E,MAAI,QAAQ,SAAS,SAAS,EAAG,QAAO;AACxC,MAAI,QAAQ,SAAS,gBAAgB,EAAG,QAAO;AAC/C,MAAI,QAAQ,SAAS,aAAa,EAAG,QAAO;AAC5C,MAAI,QAAQ,SAAS,WAAW,EAAG,QAAO;AAC1C,MAAI,QAAQ,SAAS,aAAa,EAAG,QAAO;AAC5C,MAAI,QAAQ,SAAS,qBAAqB,EAAG,QAAO;AACpD,MAAI,QAAQ,SAAS,WAAW,EAAG,QAAO;AAC1C,SAAO;AACT;AAGA,IAAM,6BAAuE;AAAA,EAC3E,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,UAAU,QAAQ;AAAA,EACnB,CAAC,SAAS,OAAO;AAAA,EACjB,CAAC,QAAQ,MAAM;AACjB;AAGA,SAAS,uBACP,iBACA,UACmB;AACnB,QAAM,UAAU,gBAAgB,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AAC1D,QAAM,QAAkB,CAAC;AACzB,aAAW,CAAC,SAAS,IAAI,KAAK,4BAA4B;AACxD,QAAI,CAAC,SAAS,SAAS,IAAI,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC,GAAG;AACxE,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,sBACd,SACA,iBACmB;AACnB,QAAM,QAAkB,CAAC;AACzB,MAAI,QAAQ,SAAS,cAAc,KAAK,QAAQ,SAAS,UAAU,EAAG,OAAM,KAAK,SAAS;AAC1F,MAAI,QAAQ,SAAS,OAAO,EAAG,OAAM,KAAK,MAAM;AAChD,MAAI,QAAQ,SAAS,aAAa,EAAG,OAAM,KAAK,iBAAiB;AACjE,MAAI,QAAQ,SAAS,aAAa,EAAG,OAAM,KAAK,OAAO;AACvD,MAAI,QAAQ,SAAS,YAAY,EAAG,OAAM,KAAK,YAAY;AAC3D,MAAI,oBAAoB,QAAW;AACjC,UAAM,KAAK,GAAG,uBAAuB,iBAAiB,KAAK,CAAC;AAAA,EAC9D;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,SAA2C;AACzE,MAAI,QAAQ,SAAS,eAAe,KAAK,QAAQ,SAAS,sBAAsB;AAC9E,WAAO;AACT,MAAI,QAAQ,SAAS,gBAAgB,KAAK,QAAQ,SAAS,gBAAgB,EAAG,QAAO;AACrF,MAAI,QAAQ,SAAS,cAAc,EAAG,QAAO;AAC7C,MAAI,QAAQ,SAAS,gBAAgB,KAAK,QAAQ,SAAS,gBAAgB,EAAG,QAAO;AACrF,MAAI,QAAQ,SAAS,eAAe,KAAK,QAAQ,SAAS,cAAc,EAAG,QAAO;AAClF,SAAO;AACT;AAGA,IAAM,YAAiE;AAAA,EACrE,CAAC,CAAC,aAAa,GAAG,uBAAuB;AAAA,EACzC,CAAC,CAAC,YAAY,GAAG,oBAAoB;AAAA,EACrC,CAAC,CAAC,WAAW,YAAY,GAAG,iBAAiB;AAAA,EAC7C;AAAA,IACE,CAAC,gBAAgB,YAAY,eAAe,OAAO;AAAA,IACnD;AAAA,EACF;AAAA;AAAA,EAEA,CAAC,CAAC,YAAY,GAAG,oBAAoB;AACvC;AAQA,IAAM,0BAAsE;AAAA,EAC1E,YAAY;AAAA,IACV,MAAM,CAAC,oCAAoC,wBAAwB;AAAA,IACnE,KAAK,CAAC,eAAe,WAAW;AAAA,EAClC;AAAA,EACA,YAAY;AAAA,IACV,MAAM,CAAC,oCAAoC,wBAAwB;AAAA,IACnE,KAAK,CAAC,eAAe,WAAW;AAAA,EAClC;AAAA,EACA,QAAQ;AAAA,IACN,MAAM,CAAC,sBAAsB,QAAQ;AAAA,IACrC,KAAK,CAAC,eAAe,WAAW;AAAA,EAClC;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,oBAAoB,0BAA0B;AAAA,IACrD,KAAK,CAAC,eAAe,wBAAwB;AAAA,EAC/C;AAAA,EACA,IAAI;AAAA,IACF,MAAM,CAAC,sBAAsB,OAAO;AAAA,IACpC,KAAK,CAAC,eAAe,aAAa;AAAA,EACpC;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,kBAAkB;AAAA,IACzB,KAAK,CAAC,eAAe,aAAa;AAAA,EACpC;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,iBAAiB,UAAU;AAAA,IAClC,KAAK,CAAC,aAAa;AAAA,EACrB;AAAA,EACA,GAAG;AAAA,IACD,MAAM,CAAC,iBAAiB,UAAU;AAAA,IAClC,KAAK,CAAC,aAAa;AAAA,EACrB;AAAA,EACA,QAAQ;AAAA,IACN,MAAM,CAAC,sBAAsB,QAAQ;AAAA,IACrC,KAAK,CAAC,eAAe,wBAAwB;AAAA,EAC/C;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,mBAAmB;AAAA,IAC1B,KAAK,CAAC,aAAa;AAAA,EACrB;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,CAAC,oBAAoB,UAAU;AAAA,IACrC,KAAK,CAAC,eAAe,eAAe;AAAA,EACtC;AAAA,EACA,KAAK;AAAA,IACH,MAAM,CAAC,mBAAmB,SAAS;AAAA,IACnC,KAAK,CAAC,eAAe,gBAAgB;AAAA,EACvC;AAAA,EACA,OAAO;AAAA,IACL,MAAM,CAAC,oBAAoB,YAAY;AAAA,IACvC,KAAK,CAAC;AAAA,EACR;AAAA,EACA,KAAK;AAAA,IACH,MAAM,CAAC,yBAAyB,OAAO;AAAA,IACvC,KAAK,CAAC,aAAa;AAAA,EACrB;AACF;AAGO,SAAS,2BACd,UACA,iBACmB;AACnB,MAAI,aAAa,KAAM,QAAO,CAAC;AAC/B,QAAM,WAAW,wBAAwB,QAAQ;AACjD,MAAI,aAAa,OAAW,QAAO,CAAC;AAEpC,QAAM,UAAU,gBAAgB,SAAS,SAAS,KAAK,gBAAgB,SAAS,MAAM;AACtF,QAAM,SACJ,gBAAgB,SAAS,aAAa,KACtC,gBAAgB,SAAS,OAAO,KAChC,gBAAgB,SAAS,MAAM;AAEjC,QAAM,OAAiB,CAAC;AACxB,MAAI,CAAC,WAAW,SAAS,KAAK,SAAS,GAAG;AACxC,UAAM,QAAQ,SAAS,KAAK,KAAK,IAAI;AACrC,SAAK,KAAK,GAAG,QAAQ,0BAA0B,KAAK,EAAE;AAAA,EACxD;AACA,MAAI,CAAC,UAAU,SAAS,IAAI,SAAS,GAAG;AACtC,UAAM,QAAQ,SAAS,IAAI,KAAK,IAAI;AACpC,SAAK,KAAK,GAAG,QAAQ,yBAAyB,KAAK,EAAE;AAAA,EACvD;AACA,SAAO;AACT;AAGA,IAAM,aAAa,oBAAI,IAAI,CAAC,WAAW,UAAU,MAAM,CAAC;AACxD,IAAM,eAAe;AAGrB,SAAS,4BAA4B,MAAgB,UAAmC;AACtF,MAAI,SAAS,KAAK,CAAC,MAAM,WAAW,IAAI,CAAC,CAAC,GAAG;AAC3C,UAAM,MAAM,KAAK,QAAQ,YAAY;AACrC,QAAI,QAAQ,GAAI,MAAK,OAAO,KAAK,CAAC;AAAA,EACpC;AACF;AAGO,SAAS,aACd,SACA,YACA,UACA,iBACmB;AACnB,QAAM,OAAiB,CAAC;AACxB,MAAI,eAAe,KAAM,MAAK,KAAK,iCAAiC;AACpE,aAAW,CAAC,OAAO,OAAO,KAAK,WAAW;AACxC,QAAI,CAAC,MAAM,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,EAAG,MAAK,KAAK,OAAO;AAAA,EAChE;AAEA,MAAI,CAAC,gBAAgB,OAAO,EAAG,MAAK,KAAK,4BAA4B;AAGrE,8BAA4B,MAAM,mBAAmB,CAAC,CAAC;AAGvD,QAAM,mBAAmB,KAAK,SAAS,0CAA0C;AACjF,MACE,oBACA,aAAa,QACb,aAAa,UACb,oBAAoB,QACpB;AACA,UAAM,WAAW,2BAA2B,UAAU,eAAe;AACrE,SAAK,KAAK,GAAG,QAAQ;AAAA,EACvB;AAEA,SAAO;AACT;AAkBO,SAAS,YACd,UACA,iBACA,iBACc;AACd,QAAM,aAAa,iBAAiB,eAAe;AACnD,QAAM,aAAa,sBAAsB,iBAAiB,eAAe;AACzE,QAAM,WAAW,gBAAgB,eAAe;AAEhD,SAAO;AAAA,IACL,MAAM,SAAS;AAAA,IACf,UAAU,SAAS;AAAA,IACnB,WAAW,gBAAgB,eAAe;AAAA,IAC1C,gBAAgB,qBAAqB,eAAe;AAAA,IACpD;AAAA,IACA,iBAAiB;AAAA,IACjB,eACE,gBAAgB,SAAS,YAAY,KACrC,gBAAgB,SAAS,oBAAoB,KAC7C,gBAAgB,SAAS,qBAAqB;AAAA,IAChD,eACE,gBAAgB,SAAS,YAAY,KACrC,gBAAgB,SAAS,QAAQ,KACjC,gBAAgB,SAAS,MAAM;AAAA,IACjC,aAAa,gBAAgB,SAAS,UAAU;AAAA,IAChD;AAAA,IACA,SAAS,SAAS,SAAS,WAAW;AAAA,IACtC,aAAa,SAAS;AAAA,IACtB,eAAe,SAAS;AAAA,IACxB,OAAO,SAAS;AAAA,IAChB,iBAAiB,CAAC,GAAG,eAAe;AAAA,IACpC,MAAM,aAAa,iBAAiB,YAAY,SAAS,UAAU,UAAU;AAAA,EAC/E;AACF;AAGA,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,SAAS,sBACP,WACA,UACe;AACf,QAAM,SAAS,OAAO,QAAQ,SAAS,EACpC,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,iBAAiB,IAAI,IAAI,CAAC,EAC9C,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7B,QAAM,MAAM,OAAO,CAAC;AACpB,SAAO,QAAQ,SAAY,IAAI,CAAC,IAAI;AACtC;AAGA,SAAS,gBAAgB,SAAqC;AAC5D,QAAM,WAAW,CAAC,SAAS,QAAQ,aAAa,MAAM;AACtD,MAAI,SAAS,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,EAAG,QAAO;AAEtD,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,YAAY,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,EAAG,QAAO;AAEzD,SAAO,QAAQ,SAAS,UAAU,KAAK,QAAQ,SAAS,cAAc;AACxE;AAGA,SAAS,yBACP,SACA,UACe;AACf,MAAI,QAAQ,SAAS,eAAe,EAAG,QAAO;AAC9C,MAAI,QAAQ,SAAS,YAAY,EAAG,QAAO;AAC3C,MAAI,QAAQ,SAAS,QAAQ,EAAG,QAAO;AACvC,MAAI,QAAQ,SAAS,gBAAgB,KAAK,QAAQ,SAAS,UAAU,EAAG,QAAO;AAC/E,MAAI,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,cAAc,EAAG,QAAO;AAC5E,MAAI,QAAQ,SAAS,SAAS,EAAG,QAAO;AACxC,MAAI,QAAQ,SAAS,cAAc,EAAG,QAAO;AAC7C,SAAO;AACT;AASA,eAAe,cAAmC;AAChD,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,eAAoB;AACtD,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,MAAW;AAC9C,SAAO,UAAU,QAAQ;AAC3B;AAGA,eAAe,cACb,QACA,MAC0D;AAC1D,QAAM,EAAE,QAAQ,SAAS,IAAI,MAAM;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACA,SAAS,MAAM;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,IACA,EAAE,SAAS,IAAO;AAAA,EACpB;AACA,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,SAAS,KAAK,CAAC;AAAA,EACvC,QAAQ;AACN,UAAM,IAAI,MAAM,qCAAqC,MAAM,KAAK,SAAS,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EAC1F;AACA,QAAM,EAAE,QAAQ,aAAa,IAAI,MAAM;AAAA,IACrC;AAAA,IACA,CAAC,OAAO,SAAS,MAAM,aAAa,QAAQ,YAAY;AAAA,IACxD,EAAE,SAAS,IAAO;AAAA,EACpB;AACA,MAAI;AACJ,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,aAAa,KAAK,CAAC;AACtD,cAAU,MAAM,QAAQ,MAAM,IAAI,OAAO,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,EAChG,QAAQ;AACN,UAAM,IAAI,MAAM,qCAAqC,MAAM,KAAK,aAAa,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EAC9F;AACA,SAAO,EAAE,UAAU,QAAQ;AAC7B;AAGA,eAAe,eAAe,QAAgB,MAA0C;AACtF,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM;AAAA,MACvB;AAAA,MACA,CAAC,OAAO,SAAS,MAAM,YAAY,QAAQ,kBAAkB;AAAA,MAC7D,EAAE,SAAS,KAAO;AAAA,IACpB;AACA,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,WAAW,MAAM,WAAW,UAAU,WAAW,eAAe;AAClE,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAGA,eAAe,gBACb,QACA,SACA,UACA,MACwB;AACxB,MAAI,YAAoC,CAAC;AACzC,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,YAAY,GAAG,EAAE,SAAS,KAAO,CAAC;AAC7F,gBAAY,KAAK,MAAM,OAAO,KAAK,CAAC;AAAA,EACtC,QAAQ;AAAA,EAER;AACA,QAAM,UAAU,sBAAsB,WAAW,SAAS,QAAQ;AAClE,MAAI,YAAY,QAAQ,iBAAiB,IAAI,OAAO,GAAG;AACrD,WAAO,yBAAyB,SAAS,OAAO;AAAA,EAClD;AACA,SAAO;AACT;AAGA,eAAe,qBAAqB,QAAgB,MAA8C;AAChG,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM;AAAA,MACvB;AAAA,MACA,CAAC,OAAO,SAAS,MAAM,+BAA+B,QAAQ,YAAY;AAAA,MAC1E,EAAE,SAAS,KAAO;AAAA,IACpB;AACA,UAAM,SAAkB,KAAK,MAAM,OAAO,KAAK,CAAC;AAChD,WAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,EAC7F,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGA,eAAsB,kBAAkB,OAAgD;AACtF,QAAM,SAAS,gBAAgB,MAAM,IAAI;AACzC,QAAM,OAAO,MAAM,YAAY;AAC/B,QAAM,EAAE,UAAU,QAAQ,IAAI,MAAM,cAAc,QAAQ,IAAI;AAE9D,QAAM,cAAc,MAAM,gBAAgB,QAAQ,SAAS,UAAU,IAAI;AACzE,QAAM,WAAW,EAAE,GAAG,UAAU,UAAU,YAAY;AAGtD,QAAM,iBAAiB,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,YAAY;AACnF,QAAM,oBAAoB,SAAS,YAAY,QAAQ,SAAS,QAAQ,YAAY;AACpF,MAAI,qBAAqB,gBAAgB;AACvC,UAAM,WAAW,MAAM,eAAe,QAAQ,IAAI;AAClD,QAAI,aAAa,KAAM,UAAS,UAAU,EAAE,SAAS,SAAS;AAAA,EAChE;AAGA,QAAM,kBAAkB,QAAQ,SAAS,SAAS,IAC9C,MAAM,qBAAqB,QAAQ,IAAI,IACvC,CAAC;AAEL,SAAO,YAAY,UAAU,SAAS,eAAe;AACvD;","names":[]}
|