ghagga-core 2.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/README.md +51 -0
- package/dist/agents/consensus.d.ts +68 -0
- package/dist/agents/consensus.d.ts.map +1 -0
- package/dist/agents/consensus.js +216 -0
- package/dist/agents/consensus.js.map +1 -0
- package/dist/agents/prompts.d.ts +18 -0
- package/dist/agents/prompts.d.ts.map +1 -0
- package/dist/agents/prompts.js +194 -0
- package/dist/agents/prompts.js.map +1 -0
- package/dist/agents/simple.d.ts +49 -0
- package/dist/agents/simple.d.ts.map +1 -0
- package/dist/agents/simple.js +135 -0
- package/dist/agents/simple.js.map +1 -0
- package/dist/agents/workflow.d.ts +40 -0
- package/dist/agents/workflow.d.ts.map +1 -0
- package/dist/agents/workflow.js +127 -0
- package/dist/agents/workflow.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/context.d.ts +23 -0
- package/dist/memory/context.d.ts.map +1 -0
- package/dist/memory/context.js +36 -0
- package/dist/memory/context.js.map +1 -0
- package/dist/memory/persist.d.ts +22 -0
- package/dist/memory/persist.d.ts.map +1 -0
- package/dist/memory/persist.js +103 -0
- package/dist/memory/persist.js.map +1 -0
- package/dist/memory/privacy.d.ts +19 -0
- package/dist/memory/privacy.d.ts.map +1 -0
- package/dist/memory/privacy.js +77 -0
- package/dist/memory/privacy.js.map +1 -0
- package/dist/memory/search.d.ts +20 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +76 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/pipeline.d.ts +30 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +267 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/providers/fallback.d.ts +46 -0
- package/dist/providers/fallback.d.ts.map +1 -0
- package/dist/providers/fallback.js +84 -0
- package/dist/providers/fallback.js.map +1 -0
- package/dist/providers/index.d.ts +40 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +76 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/tools/cpd.d.ts +24 -0
- package/dist/tools/cpd.d.ts.map +1 -0
- package/dist/tools/cpd.js +130 -0
- package/dist/tools/cpd.js.map +1 -0
- package/dist/tools/runner.d.ts +19 -0
- package/dist/tools/runner.d.ts.map +1 -0
- package/dist/tools/runner.js +61 -0
- package/dist/tools/runner.js.map +1 -0
- package/dist/tools/semgrep.d.ts +12 -0
- package/dist/tools/semgrep.d.ts.map +1 -0
- package/dist/tools/semgrep.js +97 -0
- package/dist/tools/semgrep.js.map +1 -0
- package/dist/tools/trivy.d.ts +11 -0
- package/dist/tools/trivy.d.ts.map +1 -0
- package/dist/tools/trivy.js +74 -0
- package/dist/tools/trivy.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/diff.d.ts +53 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +103 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/stack-detect.d.ts +15 -0
- package/dist/utils/stack-detect.d.ts.map +1 -0
- package/dist/utils/stack-detect.js +54 -0
- package/dist/utils/stack-detect.js.map +1 -0
- package/dist/utils/token-budget.d.ts +31 -0
- package/dist/utils/token-budget.d.ts.map +1 -0
- package/dist/utils/token-budget.js +62 -0
- package/dist/utils/token-budget.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Provider factory using the Vercel AI SDK.
|
|
3
|
+
*
|
|
4
|
+
* Wraps @ai-sdk/anthropic, @ai-sdk/openai, @ai-sdk/google,
|
|
5
|
+
* GitHub Models, and Ollama behind a unified factory so the rest
|
|
6
|
+
* of the codebase doesn't need to know which provider is being used.
|
|
7
|
+
*
|
|
8
|
+
* GitHub Models uses the OpenAI-compatible endpoint at
|
|
9
|
+
* https://models.inference.ai.azure.com and authenticates with a
|
|
10
|
+
* GitHub Personal Access Token (PAT) with `models:read` scope.
|
|
11
|
+
*
|
|
12
|
+
* Ollama runs locally and exposes an OpenAI-compatible endpoint at
|
|
13
|
+
* http://localhost:11434/v1. No API key required.
|
|
14
|
+
*/
|
|
15
|
+
import type { LanguageModel } from 'ai';
|
|
16
|
+
import type { LLMProvider } from '../types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Create a provider instance configured with the given API key.
|
|
19
|
+
*
|
|
20
|
+
* Returns the provider's model creator function, which can be called
|
|
21
|
+
* with a model ID to get a LanguageModel instance.
|
|
22
|
+
*
|
|
23
|
+
* @param provider - Provider name ('anthropic' | 'openai' | 'google' | 'github' | 'ollama')
|
|
24
|
+
* @param apiKey - Decrypted API key for the provider
|
|
25
|
+
* @returns The provider's model creator function
|
|
26
|
+
*/
|
|
27
|
+
export declare function createProvider(provider: LLMProvider, apiKey: string): import("@ai-sdk/anthropic").AnthropicProvider | import("@ai-sdk/openai").OpenAIProvider | import("@ai-sdk/google").GoogleGenerativeAIProvider;
|
|
28
|
+
/**
|
|
29
|
+
* Create a LanguageModel instance for the given provider + model combo.
|
|
30
|
+
*
|
|
31
|
+
* This is the primary entry point for the rest of the codebase.
|
|
32
|
+
* It handles provider initialization and model creation in one step.
|
|
33
|
+
*
|
|
34
|
+
* @param provider - Provider name
|
|
35
|
+
* @param model - Model identifier (e.g., "claude-sonnet-4-20250514")
|
|
36
|
+
* @param apiKey - Decrypted API key
|
|
37
|
+
* @returns A LanguageModel ready for use with AI SDK's generateText/streamText
|
|
38
|
+
*/
|
|
39
|
+
export declare function createModel(provider: LLMProvider, model: string, apiKey: string): LanguageModel;
|
|
40
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAU/C;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,MAAM,iJA2Bf;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,WAAW,EACrB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,aAAa,CAGf"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Provider factory using the Vercel AI SDK.
|
|
3
|
+
*
|
|
4
|
+
* Wraps @ai-sdk/anthropic, @ai-sdk/openai, @ai-sdk/google,
|
|
5
|
+
* GitHub Models, and Ollama behind a unified factory so the rest
|
|
6
|
+
* of the codebase doesn't need to know which provider is being used.
|
|
7
|
+
*
|
|
8
|
+
* GitHub Models uses the OpenAI-compatible endpoint at
|
|
9
|
+
* https://models.inference.ai.azure.com and authenticates with a
|
|
10
|
+
* GitHub Personal Access Token (PAT) with `models:read` scope.
|
|
11
|
+
*
|
|
12
|
+
* Ollama runs locally and exposes an OpenAI-compatible endpoint at
|
|
13
|
+
* http://localhost:11434/v1. No API key required.
|
|
14
|
+
*/
|
|
15
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
16
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
17
|
+
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
18
|
+
/** GitHub Models inference endpoint (OpenAI-compatible) */
|
|
19
|
+
const GITHUB_MODELS_BASE_URL = 'https://models.inference.ai.azure.com';
|
|
20
|
+
/** Ollama local inference endpoint (OpenAI-compatible) */
|
|
21
|
+
const OLLAMA_BASE_URL = 'http://localhost:11434/v1';
|
|
22
|
+
// ─── Provider Factory ───────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Create a provider instance configured with the given API key.
|
|
25
|
+
*
|
|
26
|
+
* Returns the provider's model creator function, which can be called
|
|
27
|
+
* with a model ID to get a LanguageModel instance.
|
|
28
|
+
*
|
|
29
|
+
* @param provider - Provider name ('anthropic' | 'openai' | 'google' | 'github' | 'ollama')
|
|
30
|
+
* @param apiKey - Decrypted API key for the provider
|
|
31
|
+
* @returns The provider's model creator function
|
|
32
|
+
*/
|
|
33
|
+
export function createProvider(provider, apiKey) {
|
|
34
|
+
switch (provider) {
|
|
35
|
+
case 'anthropic':
|
|
36
|
+
return createAnthropic({ apiKey });
|
|
37
|
+
case 'openai':
|
|
38
|
+
return createOpenAI({ apiKey });
|
|
39
|
+
case 'google':
|
|
40
|
+
return createGoogleGenerativeAI({ apiKey });
|
|
41
|
+
case 'github':
|
|
42
|
+
return createOpenAI({
|
|
43
|
+
apiKey,
|
|
44
|
+
baseURL: GITHUB_MODELS_BASE_URL,
|
|
45
|
+
name: 'github-models',
|
|
46
|
+
});
|
|
47
|
+
case 'ollama':
|
|
48
|
+
return createOpenAI({
|
|
49
|
+
apiKey: apiKey || 'ollama',
|
|
50
|
+
baseURL: OLLAMA_BASE_URL,
|
|
51
|
+
name: 'ollama',
|
|
52
|
+
});
|
|
53
|
+
default: {
|
|
54
|
+
// Exhaustive check — TypeScript will error if a provider is missing
|
|
55
|
+
const _exhaustive = provider;
|
|
56
|
+
throw new Error(`Unknown provider: ${_exhaustive}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ─── Model Factory ──────────────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Create a LanguageModel instance for the given provider + model combo.
|
|
63
|
+
*
|
|
64
|
+
* This is the primary entry point for the rest of the codebase.
|
|
65
|
+
* It handles provider initialization and model creation in one step.
|
|
66
|
+
*
|
|
67
|
+
* @param provider - Provider name
|
|
68
|
+
* @param model - Model identifier (e.g., "claude-sonnet-4-20250514")
|
|
69
|
+
* @param apiKey - Decrypted API key
|
|
70
|
+
* @returns A LanguageModel ready for use with AI SDK's generateText/streamText
|
|
71
|
+
*/
|
|
72
|
+
export function createModel(provider, model, apiKey) {
|
|
73
|
+
const providerInstance = createProvider(provider, apiKey);
|
|
74
|
+
return providerInstance(model);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAI1D,2DAA2D;AAC3D,MAAM,sBAAsB,GAAG,uCAAuC,CAAC;AAEvE,0DAA0D;AAC1D,MAAM,eAAe,GAAG,2BAA2B,CAAC;AAEpD,mEAAmE;AAEnE;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAqB,EACrB,MAAc;IAEd,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,WAAW;YACd,OAAO,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QACrC,KAAK,QAAQ;YACX,OAAO,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QAClC,KAAK,QAAQ;YACX,OAAO,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9C,KAAK,QAAQ;YACX,OAAO,YAAY,CAAC;gBAClB,MAAM;gBACN,OAAO,EAAE,sBAAsB;gBAC/B,IAAI,EAAE,eAAe;aACtB,CAAC,CAAC;QACL,KAAK,QAAQ;YACX,OAAO,YAAY,CAAC;gBAClB,MAAM,EAAE,MAAM,IAAI,QAAQ;gBAC1B,OAAO,EAAE,eAAe;gBACxB,IAAI,EAAE,QAAQ;aACf,CAAC,CAAC;QACL,OAAO,CAAC,CAAC,CAAC;YACR,oEAAoE;YACpE,MAAM,WAAW,GAAU,QAAQ,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,qBAAqB,WAAW,EAAE,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;AACH,CAAC;AAED,mEAAmE;AAEnE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,QAAqB,EACrB,KAAa,EACb,MAAc;IAEd,MAAM,gBAAgB,GAAG,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1D,OAAO,gBAAgB,CAAC,KAAK,CAAkB,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PMD/CPD (Copy-Paste Detector) for duplicate code detection.
|
|
3
|
+
* Executes cpd as a child process and parses XML output.
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolResult, ReviewFinding } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Simple XML parser for CPD output.
|
|
8
|
+
* CPD XML format:
|
|
9
|
+
* <pmd-cpd>
|
|
10
|
+
* <duplication lines="N" tokens="N">
|
|
11
|
+
* <file path="..." line="N" endline="N" />
|
|
12
|
+
* <file path="..." line="N" endline="N" />
|
|
13
|
+
* <codefragment>...</codefragment>
|
|
14
|
+
* </duplication>
|
|
15
|
+
* </pmd-cpd>
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseCpdXml(xml: string, basePath: string): ReviewFinding[];
|
|
18
|
+
/**
|
|
19
|
+
* Run CPD against a directory to find duplicated code.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runCpd(scanPath: string, options?: {
|
|
22
|
+
minimumTokens?: number;
|
|
23
|
+
}): Promise<ToolResult>;
|
|
24
|
+
//# sourceMappingURL=cpd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cpd.d.ts","sourceRoot":"","sources":["../../src/tools/cpd.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAM7D;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAoC1E;AAsBD;;GAEG;AACH,wBAAsB,MAAM,CAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,OAAO,CAAC,UAAU,CAAC,CAwDrB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PMD/CPD (Copy-Paste Detector) for duplicate code detection.
|
|
3
|
+
* Executes cpd as a child process and parses XML output.
|
|
4
|
+
*/
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const TIMEOUT_MS = 60_000;
|
|
9
|
+
const DEFAULT_MIN_TOKENS = 100;
|
|
10
|
+
/**
|
|
11
|
+
* Simple XML parser for CPD output.
|
|
12
|
+
* CPD XML format:
|
|
13
|
+
* <pmd-cpd>
|
|
14
|
+
* <duplication lines="N" tokens="N">
|
|
15
|
+
* <file path="..." line="N" endline="N" />
|
|
16
|
+
* <file path="..." line="N" endline="N" />
|
|
17
|
+
* <codefragment>...</codefragment>
|
|
18
|
+
* </duplication>
|
|
19
|
+
* </pmd-cpd>
|
|
20
|
+
*/
|
|
21
|
+
export function parseCpdXml(xml, basePath) {
|
|
22
|
+
const findings = [];
|
|
23
|
+
const dupRegex = /<duplication lines="(\d+)" tokens="(\d+)">([\s\S]*?)<\/duplication>/g;
|
|
24
|
+
const fileRegex = /<file\s+path="([^"]+)"\s+line="(\d+)"/g;
|
|
25
|
+
let dupMatch;
|
|
26
|
+
while ((dupMatch = dupRegex.exec(xml)) !== null) {
|
|
27
|
+
const lines = parseInt(dupMatch[1], 10);
|
|
28
|
+
const tokens = parseInt(dupMatch[2], 10);
|
|
29
|
+
const inner = dupMatch[3];
|
|
30
|
+
const files = [];
|
|
31
|
+
let fileMatch;
|
|
32
|
+
fileRegex.lastIndex = 0;
|
|
33
|
+
while ((fileMatch = fileRegex.exec(inner)) !== null) {
|
|
34
|
+
files.push({
|
|
35
|
+
path: fileMatch[1].replace(basePath + '/', ''),
|
|
36
|
+
line: parseInt(fileMatch[2], 10),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (files.length >= 2) {
|
|
40
|
+
const locations = files.map((f) => `${f.path}:${f.line}`).join(', ');
|
|
41
|
+
findings.push({
|
|
42
|
+
severity: 'medium',
|
|
43
|
+
category: 'duplication',
|
|
44
|
+
file: files[0].path,
|
|
45
|
+
line: files[0].line,
|
|
46
|
+
message: `Duplicated code block (${lines} lines, ${tokens} tokens) found in: ${locations}`,
|
|
47
|
+
suggestion: 'Extract the duplicated code into a shared function or module.',
|
|
48
|
+
source: 'cpd',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return findings;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Detect the CPD binary name. Supports both `cpd` (standalone)
|
|
56
|
+
* and `pmd cpd` (PMD 7+ CLI).
|
|
57
|
+
*/
|
|
58
|
+
async function findCpdBinary() {
|
|
59
|
+
// Try standalone cpd first
|
|
60
|
+
try {
|
|
61
|
+
await execFileAsync('cpd', ['--help'], { timeout: 5_000 });
|
|
62
|
+
return { cmd: 'cpd', args: [] };
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Try pmd CLI (PMD 7+)
|
|
66
|
+
try {
|
|
67
|
+
await execFileAsync('pmd', ['cpd', '--help'], { timeout: 5_000 });
|
|
68
|
+
return { cmd: 'pmd', args: ['cpd'] };
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Run CPD against a directory to find duplicated code.
|
|
77
|
+
*/
|
|
78
|
+
export async function runCpd(scanPath, options = {}) {
|
|
79
|
+
const start = Date.now();
|
|
80
|
+
const minimumTokens = options.minimumTokens ?? DEFAULT_MIN_TOKENS;
|
|
81
|
+
const binary = await findCpdBinary();
|
|
82
|
+
if (!binary) {
|
|
83
|
+
return {
|
|
84
|
+
status: 'skipped',
|
|
85
|
+
findings: [],
|
|
86
|
+
error: 'CPD/PMD not available. Install from: https://pmd.github.io',
|
|
87
|
+
executionTimeMs: Date.now() - start,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const args = [
|
|
92
|
+
...binary.args,
|
|
93
|
+
'--format', 'xml',
|
|
94
|
+
'--minimum-tokens', String(minimumTokens),
|
|
95
|
+
'--dir', scanPath,
|
|
96
|
+
'--skip-lexical-errors',
|
|
97
|
+
];
|
|
98
|
+
const { stdout } = await execFileAsync(binary.cmd, args, {
|
|
99
|
+
timeout: TIMEOUT_MS,
|
|
100
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
101
|
+
});
|
|
102
|
+
const findings = parseCpdXml(stdout, scanPath);
|
|
103
|
+
return {
|
|
104
|
+
status: 'success',
|
|
105
|
+
findings,
|
|
106
|
+
executionTimeMs: Date.now() - start,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
// CPD exits with code 4 when duplications are found (not an error)
|
|
111
|
+
if (error && typeof error === 'object' && 'stdout' in error) {
|
|
112
|
+
const stdout = error.stdout;
|
|
113
|
+
if (stdout && stdout.includes('<pmd-cpd')) {
|
|
114
|
+
const findings = parseCpdXml(stdout, scanPath);
|
|
115
|
+
return {
|
|
116
|
+
status: 'success',
|
|
117
|
+
findings,
|
|
118
|
+
executionTimeMs: Date.now() - start,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
status: 'error',
|
|
124
|
+
findings: [],
|
|
125
|
+
error: `CPD failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
126
|
+
executionTimeMs: Date.now() - start,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=cpd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cpd.js","sourceRoot":"","sources":["../../src/tools/cpd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static analysis tool runner.
|
|
3
|
+
* Runs Semgrep, Trivy, and CPD in parallel and merges results.
|
|
4
|
+
*/
|
|
5
|
+
import type { StaticAnalysisResult, ReviewSettings } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Run all enabled static analysis tools in parallel.
|
|
8
|
+
*
|
|
9
|
+
* @param files - Map of file paths to file contents (for Semgrep)
|
|
10
|
+
* @param scanPath - Directory path on disk (for Trivy and CPD)
|
|
11
|
+
* @param settings - Which tools are enabled
|
|
12
|
+
*/
|
|
13
|
+
export declare function runStaticAnalysis(files: Map<string, string>, scanPath: string, settings: Pick<ReviewSettings, 'enableSemgrep' | 'enableTrivy' | 'enableCpd' | 'customRules'>): Promise<StaticAnalysisResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Format static analysis findings into a prompt context block.
|
|
16
|
+
* This is injected into LLM prompts so agents don't repeat findings.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatStaticAnalysisContext(result: StaticAnalysisResult): string;
|
|
19
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/tools/runner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAc,MAAM,aAAa,CAAC;AASpF;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,GAAG,aAAa,GAAG,WAAW,GAAG,aAAa,CAAC,GAC5F,OAAO,CAAC,oBAAoB,CAAC,CAkB/B;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAqBhF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static analysis tool runner.
|
|
3
|
+
* Runs Semgrep, Trivy, and CPD in parallel and merges results.
|
|
4
|
+
*/
|
|
5
|
+
import { runSemgrep } from './semgrep.js';
|
|
6
|
+
import { runTrivy } from './trivy.js';
|
|
7
|
+
import { runCpd } from './cpd.js';
|
|
8
|
+
const SKIPPED_RESULT = {
|
|
9
|
+
status: 'skipped',
|
|
10
|
+
findings: [],
|
|
11
|
+
error: 'Disabled in settings',
|
|
12
|
+
executionTimeMs: 0,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Run all enabled static analysis tools in parallel.
|
|
16
|
+
*
|
|
17
|
+
* @param files - Map of file paths to file contents (for Semgrep)
|
|
18
|
+
* @param scanPath - Directory path on disk (for Trivy and CPD)
|
|
19
|
+
* @param settings - Which tools are enabled
|
|
20
|
+
*/
|
|
21
|
+
export async function runStaticAnalysis(files, scanPath, settings) {
|
|
22
|
+
const [semgrepResult, trivyResult, cpdResult] = await Promise.allSettled([
|
|
23
|
+
settings.enableSemgrep ? runSemgrep(files) : Promise.resolve(SKIPPED_RESULT),
|
|
24
|
+
settings.enableTrivy ? runTrivy(scanPath) : Promise.resolve(SKIPPED_RESULT),
|
|
25
|
+
settings.enableCpd ? runCpd(scanPath) : Promise.resolve(SKIPPED_RESULT),
|
|
26
|
+
]);
|
|
27
|
+
return {
|
|
28
|
+
semgrep: semgrepResult.status === 'fulfilled'
|
|
29
|
+
? semgrepResult.value
|
|
30
|
+
: { status: 'error', findings: [], error: String(semgrepResult.reason), executionTimeMs: 0 },
|
|
31
|
+
trivy: trivyResult.status === 'fulfilled'
|
|
32
|
+
? trivyResult.value
|
|
33
|
+
: { status: 'error', findings: [], error: String(trivyResult.reason), executionTimeMs: 0 },
|
|
34
|
+
cpd: cpdResult.status === 'fulfilled'
|
|
35
|
+
? cpdResult.value
|
|
36
|
+
: { status: 'error', findings: [], error: String(cpdResult.reason), executionTimeMs: 0 },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Format static analysis findings into a prompt context block.
|
|
41
|
+
* This is injected into LLM prompts so agents don't repeat findings.
|
|
42
|
+
*/
|
|
43
|
+
export function formatStaticAnalysisContext(result) {
|
|
44
|
+
const allFindings = [
|
|
45
|
+
...result.semgrep.findings,
|
|
46
|
+
...result.trivy.findings,
|
|
47
|
+
...result.cpd.findings,
|
|
48
|
+
];
|
|
49
|
+
if (allFindings.length === 0)
|
|
50
|
+
return '';
|
|
51
|
+
const lines = ['## Pre-Review Static Analysis (confirmed issues - do NOT repeat these)', ''];
|
|
52
|
+
for (const finding of allFindings) {
|
|
53
|
+
const location = finding.line ? `${finding.file}:${finding.line}` : finding.file;
|
|
54
|
+
lines.push(`- **[${finding.source.toUpperCase()}]** [${finding.severity}] ${location}: ${finding.message}`);
|
|
55
|
+
}
|
|
56
|
+
lines.push('');
|
|
57
|
+
lines.push('> These issues were detected by automated tools. Do NOT repeat them in your review.');
|
|
58
|
+
lines.push('> Focus on logic, architecture, and issues that static analysis cannot detect.');
|
|
59
|
+
return lines.join('\n');
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/tools/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semgrep static analysis tool runner.
|
|
3
|
+
* Executes semgrep as a child process and parses JSON output.
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolResult, FindingSeverity } from '../types.js';
|
|
6
|
+
export declare function mapSeverity(semgrepSeverity: string): FindingSeverity;
|
|
7
|
+
/**
|
|
8
|
+
* Run Semgrep against file contents.
|
|
9
|
+
* Files are written to a temp directory, scanned, then cleaned up.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runSemgrep(files: Map<string, string>, customRulesPath?: string): Promise<ToolResult>;
|
|
12
|
+
//# sourceMappingURL=semgrep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep.d.ts","sourceRoot":"","sources":["../../src/tools/semgrep.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,OAAO,KAAK,EAAE,UAAU,EAAiB,eAAe,EAAE,MAAM,aAAa,CAAC;AAsB9E,wBAAgB,WAAW,CAAC,eAAe,EAAE,MAAM,GAAG,eAAe,CAWpE;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,UAAU,CAAC,CAwErB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semgrep static analysis tool runner.
|
|
3
|
+
* Executes semgrep as a child process and parses JSON output.
|
|
4
|
+
*/
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
import { writeFile, mkdtemp, rm } from 'node:fs/promises';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { promisify } from 'node:util';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { dirname } from 'node:path';
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const RULES_PATH = join(__dirname, 'semgrep-rules.yml');
|
|
15
|
+
const TIMEOUT_MS = 60_000;
|
|
16
|
+
export function mapSeverity(semgrepSeverity) {
|
|
17
|
+
switch (semgrepSeverity.toUpperCase()) {
|
|
18
|
+
case 'ERROR':
|
|
19
|
+
return 'high';
|
|
20
|
+
case 'WARNING':
|
|
21
|
+
return 'medium';
|
|
22
|
+
case 'INFO':
|
|
23
|
+
return 'info';
|
|
24
|
+
default:
|
|
25
|
+
return 'low';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Run Semgrep against file contents.
|
|
30
|
+
* Files are written to a temp directory, scanned, then cleaned up.
|
|
31
|
+
*/
|
|
32
|
+
export async function runSemgrep(files, customRulesPath) {
|
|
33
|
+
const start = Date.now();
|
|
34
|
+
try {
|
|
35
|
+
// Check if semgrep is available
|
|
36
|
+
await execFileAsync('semgrep', ['--version'], { timeout: 5_000 });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return {
|
|
40
|
+
status: 'skipped',
|
|
41
|
+
findings: [],
|
|
42
|
+
error: 'Semgrep not available. Install with: pip install semgrep',
|
|
43
|
+
executionTimeMs: Date.now() - start,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
let tempDir;
|
|
47
|
+
try {
|
|
48
|
+
// Write files to temp directory
|
|
49
|
+
tempDir = await mkdtemp(join(tmpdir(), 'ghagga-semgrep-'));
|
|
50
|
+
for (const [filePath, content] of files) {
|
|
51
|
+
const fullPath = join(tempDir, filePath);
|
|
52
|
+
const dir = dirname(fullPath);
|
|
53
|
+
await mkdtemp(dir).catch(() => { }); // ignore if exists
|
|
54
|
+
await writeFile(fullPath, content, 'utf8').catch(async () => {
|
|
55
|
+
// Create parent dirs recursively
|
|
56
|
+
const { mkdir } = await import('node:fs/promises');
|
|
57
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
58
|
+
await writeFile(fullPath, content, 'utf8');
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// Run semgrep
|
|
62
|
+
const configArgs = ['--config', RULES_PATH];
|
|
63
|
+
if (customRulesPath) {
|
|
64
|
+
configArgs.push('--config', customRulesPath);
|
|
65
|
+
}
|
|
66
|
+
const { stdout } = await execFileAsync('semgrep', ['--json', ...configArgs, tempDir], { timeout: TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 });
|
|
67
|
+
const result = JSON.parse(stdout);
|
|
68
|
+
const findings = result.results.map((r) => ({
|
|
69
|
+
severity: mapSeverity(r.extra.severity),
|
|
70
|
+
category: 'security',
|
|
71
|
+
file: r.path.replace(tempDir + '/', ''),
|
|
72
|
+
line: r.start.line,
|
|
73
|
+
message: r.extra.message,
|
|
74
|
+
suggestion: undefined,
|
|
75
|
+
source: 'semgrep',
|
|
76
|
+
}));
|
|
77
|
+
return {
|
|
78
|
+
status: 'success',
|
|
79
|
+
findings,
|
|
80
|
+
executionTimeMs: Date.now() - start,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
status: 'error',
|
|
86
|
+
findings: [],
|
|
87
|
+
error: `Semgrep failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
88
|
+
executionTimeMs: Date.now() - start,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
if (tempDir) {
|
|
93
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => { });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=semgrep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep.js","sourceRoot":"","sources":["../../src/tools/semgrep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trivy dependency vulnerability scanner.
|
|
3
|
+
* Executes trivy as a child process and parses JSON output.
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolResult, FindingSeverity } from '../types.js';
|
|
6
|
+
export declare function mapSeverity(trivySeverity: string): FindingSeverity;
|
|
7
|
+
/**
|
|
8
|
+
* Run Trivy against a directory to scan for dependency vulnerabilities.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runTrivy(scanPath: string): Promise<ToolResult>;
|
|
11
|
+
//# sourceMappingURL=trivy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trivy.d.ts","sourceRoot":"","sources":["../../src/tools/trivy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAiB,eAAe,EAAE,MAAM,aAAa,CAAC;AAqB9E,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,CAalE;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAuDpE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trivy dependency vulnerability scanner.
|
|
3
|
+
* Executes trivy as a child process and parses JSON output.
|
|
4
|
+
*/
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const TIMEOUT_MS = 120_000; // Trivy can be slow on first run (downloading DB)
|
|
9
|
+
export function mapSeverity(trivySeverity) {
|
|
10
|
+
switch (trivySeverity.toUpperCase()) {
|
|
11
|
+
case 'CRITICAL':
|
|
12
|
+
return 'critical';
|
|
13
|
+
case 'HIGH':
|
|
14
|
+
return 'high';
|
|
15
|
+
case 'MEDIUM':
|
|
16
|
+
return 'medium';
|
|
17
|
+
case 'LOW':
|
|
18
|
+
return 'low';
|
|
19
|
+
default:
|
|
20
|
+
return 'info';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run Trivy against a directory to scan for dependency vulnerabilities.
|
|
25
|
+
*/
|
|
26
|
+
export async function runTrivy(scanPath) {
|
|
27
|
+
const start = Date.now();
|
|
28
|
+
try {
|
|
29
|
+
// Check if trivy is available
|
|
30
|
+
await execFileAsync('trivy', ['--version'], { timeout: 5_000 });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {
|
|
34
|
+
status: 'skipped',
|
|
35
|
+
findings: [],
|
|
36
|
+
error: 'Trivy not available. Install from: https://trivy.dev',
|
|
37
|
+
executionTimeMs: Date.now() - start,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const { stdout } = await execFileAsync('trivy', ['fs', '--format', 'json', '--scanners', 'vuln', '--quiet', scanPath], { timeout: TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 });
|
|
42
|
+
const result = JSON.parse(stdout);
|
|
43
|
+
const findings = [];
|
|
44
|
+
for (const target of result.Results ?? []) {
|
|
45
|
+
for (const vuln of target.Vulnerabilities ?? []) {
|
|
46
|
+
const fixInfo = vuln.FixedVersion ? ` (fix: upgrade to ${vuln.FixedVersion})` : ' (no fix available)';
|
|
47
|
+
findings.push({
|
|
48
|
+
severity: mapSeverity(vuln.Severity),
|
|
49
|
+
category: 'dependency-vulnerability',
|
|
50
|
+
file: target.Target,
|
|
51
|
+
message: `${vuln.VulnerabilityID}: ${vuln.PkgName}@${vuln.InstalledVersion} - ${vuln.Title ?? vuln.Description ?? 'Known vulnerability'}${fixInfo}`,
|
|
52
|
+
suggestion: vuln.FixedVersion
|
|
53
|
+
? `Upgrade ${vuln.PkgName} to ${vuln.FixedVersion}`
|
|
54
|
+
: undefined,
|
|
55
|
+
source: 'trivy',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
status: 'success',
|
|
61
|
+
findings,
|
|
62
|
+
executionTimeMs: Date.now() - start,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return {
|
|
67
|
+
status: 'error',
|
|
68
|
+
findings: [],
|
|
69
|
+
error: `Trivy failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
70
|
+
executionTimeMs: Date.now() - start,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=trivy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trivy.js","sourceRoot":"","sources":["../../src/tools/trivy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC1C,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,kDAAkD;AAkB9E,MAAM,UAAU,WAAW,CAAC,aAAqB;IAC/C,QAAQ,aAAa,CAAC,WAAW,EAAE,EAAE,CAAC;QACpC,KAAK,UAAU;YACb,OAAO,UAAU,CAAC;QACpB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC;QAClB,KAAK,KAAK;YACR,OAAO,KAAK,CAAC;QACf;YACE,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEzB,IAAI,CAAC;QACH,8BAA8B;QAC9B,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,EAAE;YACZ,KAAK,EAAE,sDAAsD;YAC7D,eAAe,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SACpC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,OAAO,EACP,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,EACrE,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CACrD,CAAC;QAEF,MAAM,MAAM,GAAgB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAoB,EAAE,CAAC;QAErC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YAC1C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,eAAe,IAAI,EAAE,EAAE,CAAC;gBAChD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,qBAAqB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,qBAAqB,CAAC;gBAEtG,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;oBACpC,QAAQ,EAAE,0BAA0B;oBACpC,IAAI,EAAE,MAAM,CAAC,MAAM;oBACnB,OAAO,EAAE,GAAG,IAAI,CAAC,eAAe,KAAK,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,gBAAgB,MAAM,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,WAAW,IAAI,qBAAqB,GAAG,OAAO,EAAE;oBACnJ,UAAU,EAAE,IAAI,CAAC,YAAY;wBAC3B,CAAC,CAAC,WAAW,IAAI,CAAC,OAAO,OAAO,IAAI,CAAC,YAAY,EAAE;wBACnD,CAAC,CAAC,SAAS;oBACb,MAAM,EAAE,OAAgB;iBACzB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,QAAQ;YACR,eAAe,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SACpC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,MAAM,EAAE,OAAO;YACf,QAAQ,EAAE,EAAE;YACZ,KAAK,EAAE,iBAAiB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;YAChF,eAAe,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SACpC,CAAC;IACJ,CAAC;AACH,CAAC"}
|