opencode-review-helper 0.1.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.
@@ -0,0 +1,187 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
2
+ import { extractExports } from "../utils/imports.js";
3
+ import { findAllReferencesToFile } from "../utils/references.js";
4
+ import { loadConfig } from "../config.js";
5
+ import { resolve } from "node:path";
6
+ import { existsSync } from "node:fs";
7
+ function hasTestFile(file, cwd) {
8
+ const testPatterns = [
9
+ file.replace(/\.(ts|tsx|js|jsx)$/, ".test.$1"),
10
+ file.replace(/\.(ts|tsx|js|jsx)$/, ".spec.$1"),
11
+ file.replace(/^src\//, "src/__tests__/").replace(/\.(ts|tsx|js|jsx)$/, ".test.$1"),
12
+ file.replace(/^src\//, "test/").replace(/\.(ts|tsx|js|jsx)$/, ".test.$1"),
13
+ file.replace(/^src\//, "tests/").replace(/\.(ts|tsx|js|jsx)$/, ".test.$1"),
14
+ ];
15
+ for (const pattern of testPatterns) {
16
+ if (existsSync(resolve(cwd, pattern))) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+ export const impactAnalysisTool = tool({
23
+ description: "Find code outside the changeset that could be affected by the changes. Analyzes imports and references to identify direct consumers and transitive dependencies. Helps identify potential breaking changes.",
24
+ args: {
25
+ files: tool.schema
26
+ .array(tool.schema.string())
27
+ .describe("Changed files to analyze for impact"),
28
+ depth: tool.schema
29
+ .number()
30
+ .optional()
31
+ .default(2)
32
+ .describe("Transitive depth (1=direct only, 2+=transitive). Max 3."),
33
+ max_results: tool.schema
34
+ .number()
35
+ .optional()
36
+ .default(50)
37
+ .describe("Maximum results per category to prevent overwhelming output"),
38
+ },
39
+ async execute(args, ctx) {
40
+ const cwd = process.cwd();
41
+ const config = await loadConfig(cwd);
42
+ const depth = Math.min(args.depth ?? 2, 3);
43
+ const maxResults = args.max_results ?? config.impact_analysis.max_results_per_level;
44
+ const excludeFiles = new Set(args.files);
45
+ const directImpacts = [];
46
+ const transitiveImpacts = [];
47
+ const testGaps = [];
48
+ const seenFiles = new Set();
49
+ let totalFound = 0;
50
+ for (const file of args.files) {
51
+ const fullPath = resolve(cwd, file);
52
+ if (!hasTestFile(file, cwd)) {
53
+ const isTestFile = file.includes(".test.") || file.includes(".spec.") || file.includes("__tests__");
54
+ if (!isTestFile) {
55
+ testGaps.push({
56
+ file,
57
+ reason: "No corresponding test file found",
58
+ });
59
+ }
60
+ }
61
+ const exports = await extractExports(fullPath);
62
+ const exportedSymbols = exports.map((e) => e.name).filter((n) => n !== "default" && !n.startsWith("*"));
63
+ const references = await findAllReferencesToFile(file, exportedSymbols, {
64
+ cwd,
65
+ excludeFiles: args.files,
66
+ maxResults,
67
+ });
68
+ for (const ref of references) {
69
+ if (excludeFiles.has(ref.file))
70
+ continue;
71
+ if (seenFiles.has(`${ref.file}:${ref.line}`))
72
+ continue;
73
+ seenFiles.add(`${ref.file}:${ref.line}`);
74
+ totalFound++;
75
+ if (directImpacts.length < maxResults) {
76
+ directImpacts.push({
77
+ file: ref.file,
78
+ line: ref.line,
79
+ type: ref.type,
80
+ context: ref.context,
81
+ });
82
+ }
83
+ }
84
+ }
85
+ if (depth >= 2 && directImpacts.length > 0) {
86
+ const directFiles = [...new Set(directImpacts.map((d) => d.file))];
87
+ for (const file of directFiles.slice(0, 10)) {
88
+ if (transitiveImpacts.length >= maxResults)
89
+ break;
90
+ const fullPath = resolve(cwd, file);
91
+ const exports = await extractExports(fullPath);
92
+ const exportedSymbols = exports.map((e) => e.name).filter((n) => n !== "default" && !n.startsWith("*"));
93
+ const allExcluded = [...args.files, ...directFiles];
94
+ const references = await findAllReferencesToFile(file, exportedSymbols, {
95
+ cwd,
96
+ excludeFiles: allExcluded,
97
+ maxResults: Math.min(20, maxResults - transitiveImpacts.length),
98
+ });
99
+ for (const ref of references) {
100
+ if (excludeFiles.has(ref.file))
101
+ continue;
102
+ if (seenFiles.has(`${ref.file}:${ref.line}`))
103
+ continue;
104
+ seenFiles.add(`${ref.file}:${ref.line}`);
105
+ totalFound++;
106
+ transitiveImpacts.push({
107
+ file: ref.file,
108
+ line: ref.line,
109
+ type: `via ${file}`,
110
+ context: ref.context,
111
+ });
112
+ }
113
+ }
114
+ }
115
+ const truncated = totalFound > maxResults;
116
+ let output = "## Impact Analysis\n\n";
117
+ if (directImpacts.length === 0 && transitiveImpacts.length === 0) {
118
+ output += "No external code found that imports or uses the changed files.\n";
119
+ output += "This could mean:\n";
120
+ output += "- The changed files are leaf nodes (not imported elsewhere)\n";
121
+ output += "- The changed files are entry points\n";
122
+ output += "- Consumers are in node_modules or excluded directories\n\n";
123
+ }
124
+ else {
125
+ output += `Found ${totalFound} references outside the changeset`;
126
+ if (truncated) {
127
+ output += ` (showing first ${maxResults})`;
128
+ }
129
+ output += ".\n\n";
130
+ }
131
+ if (directImpacts.length > 0) {
132
+ output += "### Direct Consumers\n\n";
133
+ output += "Files that directly import/use the changed code:\n\n";
134
+ output += "| File | Line | Type | Context |\n";
135
+ output += "|------|------|------|--------|\n";
136
+ for (const impact of directImpacts.slice(0, 25)) {
137
+ const truncatedContext = impact.context.length > 60
138
+ ? impact.context.slice(0, 57) + "..."
139
+ : impact.context;
140
+ output += `| \`${impact.file}\` | ${impact.line} | ${impact.type} | \`${truncatedContext}\` |\n`;
141
+ }
142
+ if (directImpacts.length > 25) {
143
+ output += `\n_...and ${directImpacts.length - 25} more direct consumers_\n`;
144
+ }
145
+ output += "\n";
146
+ }
147
+ if (transitiveImpacts.length > 0) {
148
+ output += "### Transitive Impact\n\n";
149
+ output += "Files that use code which uses the changed code:\n\n";
150
+ output += "| File | Line | Via | Context |\n";
151
+ output += "|------|------|-----|--------|\n";
152
+ for (const impact of transitiveImpacts.slice(0, 15)) {
153
+ const truncatedContext = impact.context.length > 50
154
+ ? impact.context.slice(0, 47) + "..."
155
+ : impact.context;
156
+ output += `| \`${impact.file}\` | ${impact.line} | ${impact.type} | \`${truncatedContext}\` |\n`;
157
+ }
158
+ if (transitiveImpacts.length > 15) {
159
+ output += `\n_...and ${transitiveImpacts.length - 15} more transitive impacts_\n`;
160
+ }
161
+ output += "\n";
162
+ }
163
+ if (testGaps.length > 0) {
164
+ output += "### Test Coverage Gaps\n\n";
165
+ output += "Changed files without corresponding test files:\n\n";
166
+ for (const gap of testGaps) {
167
+ output += `- \`${gap.file}\`: ${gap.reason}\n`;
168
+ }
169
+ output += "\n";
170
+ }
171
+ output += "### Recommendations\n\n";
172
+ if (directImpacts.length > 5) {
173
+ output += "- **High impact**: Many files depend on these changes. Review carefully for breaking changes.\n";
174
+ }
175
+ if (transitiveImpacts.length > 0) {
176
+ output += "- **Transitive risk**: Changes may have cascading effects. Consider integration testing.\n";
177
+ }
178
+ if (testGaps.length > 0) {
179
+ output += "- **Test coverage**: Consider adding tests for changed files without test coverage.\n";
180
+ }
181
+ if (directImpacts.length === 0 && transitiveImpacts.length === 0 && testGaps.length === 0) {
182
+ output += "- Changes appear isolated. Standard review process should suffice.\n";
183
+ }
184
+ return output;
185
+ },
186
+ });
187
+ //# sourceMappingURL=impact-analysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"impact-analysis.js","sourceRoot":"","sources":["../../src/tools/impact-analysis.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ export declare const reviewOrderTool: ToolDefinition;
3
+ //# sourceMappingURL=review-order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-order.d.ts","sourceRoot":"","sources":["../../src/tools/review-order.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AA2HrE,eAAO,MAAM,eAAe,EAAE,cAmF5B,CAAC"}
@@ -0,0 +1,150 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
2
+ import { getChangedFiles } from "../utils/git.js";
3
+ import { buildDependencyGraph } from "../utils/imports.js";
4
+ import { loadConfig } from "../config.js";
5
+ import { extname, basename } from "node:path";
6
+ function getFileTypePriority(file, typePriority) {
7
+ const lowerFile = file.toLowerCase();
8
+ const ext = extname(file);
9
+ const base = basename(file, ext).toLowerCase();
10
+ for (let i = 0; i < typePriority.length; i++) {
11
+ const type = typePriority[i].toLowerCase();
12
+ if (lowerFile.includes(type) ||
13
+ base.includes(type) ||
14
+ lowerFile.includes(`/${type}/`) ||
15
+ lowerFile.includes(`/${type}s/`)) {
16
+ return typePriority.length - i;
17
+ }
18
+ }
19
+ return 0;
20
+ }
21
+ function computeScores(files, dependencyGraph, typePriority) {
22
+ const fileNames = files.map((f) => f.file);
23
+ const dependentsMap = new Map();
24
+ for (const file of fileNames) {
25
+ dependentsMap.set(file, []);
26
+ }
27
+ for (const [file, deps] of dependencyGraph) {
28
+ for (const dep of deps) {
29
+ const existing = dependentsMap.get(dep) || [];
30
+ existing.push(file);
31
+ dependentsMap.set(dep, existing);
32
+ }
33
+ }
34
+ const scores = [];
35
+ for (const changedFile of files) {
36
+ const file = changedFile.file;
37
+ const reasons = [];
38
+ let score = 0;
39
+ const typePriorityScore = getFileTypePriority(file, typePriority);
40
+ if (typePriorityScore > 0) {
41
+ score += typePriorityScore * 10;
42
+ reasons.push(`Type priority: ${typePriorityScore}`);
43
+ }
44
+ const dependents = dependentsMap.get(file) || [];
45
+ if (dependents.length > 0) {
46
+ score += dependents.length * 15;
47
+ reasons.push(`${dependents.length} file(s) depend on this`);
48
+ }
49
+ const dependencies = dependencyGraph.get(file) || [];
50
+ if (dependencies.length === 0 && dependents.length > 0) {
51
+ score += 20;
52
+ reasons.push("Foundation file (no deps, has dependents)");
53
+ }
54
+ const complexity = changedFile.additions + changedFile.deletions;
55
+ if (complexity > 100) {
56
+ score += 10;
57
+ reasons.push(`High churn: ${complexity} lines changed`);
58
+ }
59
+ else if (complexity > 50) {
60
+ score += 5;
61
+ reasons.push(`Moderate churn: ${complexity} lines changed`);
62
+ }
63
+ if (file.includes("index.")) {
64
+ score += 5;
65
+ reasons.push("Index/barrel file");
66
+ }
67
+ scores.push({
68
+ file,
69
+ score,
70
+ reasons,
71
+ dependencies,
72
+ dependents,
73
+ });
74
+ }
75
+ return scores.sort((a, b) => b.score - a.score);
76
+ }
77
+ function generateReason(fileScore) {
78
+ if (fileScore.reasons.length === 0) {
79
+ return "Standard file";
80
+ }
81
+ const topReasons = fileScore.reasons.slice(0, 2);
82
+ return topReasons.join("; ");
83
+ }
84
+ export const reviewOrderTool = tool({
85
+ description: "Analyze changed files and suggest optimal review order based on dependencies, file type priority, and complexity. Returns a prioritized list with rationale for reviewing each file.",
86
+ args: {
87
+ files: tool.schema
88
+ .array(tool.schema.string())
89
+ .optional()
90
+ .describe("Files to analyze. If omitted, uses git diff to find changed files."),
91
+ instructions: tool.schema
92
+ .string()
93
+ .optional()
94
+ .describe("Additional ordering instructions (e.g., 'review migrations before models')"),
95
+ },
96
+ async execute(args, ctx) {
97
+ const cwd = process.cwd();
98
+ const config = await loadConfig(cwd);
99
+ let filesToAnalyze;
100
+ if (args.files && args.files.length > 0) {
101
+ filesToAnalyze = args.files.map((f) => ({
102
+ file: f,
103
+ status: "modified",
104
+ additions: 0,
105
+ deletions: 0,
106
+ }));
107
+ }
108
+ else {
109
+ filesToAnalyze = await getChangedFiles(cwd);
110
+ }
111
+ if (filesToAnalyze.length === 0) {
112
+ return JSON.stringify({
113
+ order: [],
114
+ dependency_graph: {},
115
+ message: "No changed files found",
116
+ });
117
+ }
118
+ const dependencyGraph = await buildDependencyGraph(filesToAnalyze.map((f) => f.file), cwd);
119
+ const scores = computeScores(filesToAnalyze, dependencyGraph, config.review_order.type_priority);
120
+ const result = {
121
+ order: scores.map((s, i) => ({
122
+ rank: i + 1,
123
+ file: s.file,
124
+ reason: generateReason(s),
125
+ score: s.score,
126
+ })),
127
+ dependency_graph: Object.fromEntries(dependencyGraph),
128
+ };
129
+ let output = "## Review Order\n\n";
130
+ output += "| # | File | Reason | Score |\n";
131
+ output += "|---|------|--------|-------|\n";
132
+ for (const item of result.order) {
133
+ output += `| ${item.rank} | \`${item.file}\` | ${item.reason} | ${item.score} |\n`;
134
+ }
135
+ if (Object.keys(result.dependency_graph).length > 0) {
136
+ output += "\n## Dependency Graph\n\n";
137
+ for (const [file, deps] of Object.entries(result.dependency_graph)) {
138
+ if (deps.length > 0) {
139
+ output += `- \`${file}\` imports: ${deps.map((d) => `\`${d}\``).join(", ")}\n`;
140
+ }
141
+ }
142
+ }
143
+ if (args.instructions) {
144
+ output += `\n## Custom Instructions\n\nUser requested: "${args.instructions}"\n`;
145
+ output += "Note: Custom instructions should be applied by the reviewing agent when prioritizing.\n";
146
+ }
147
+ return output;
148
+ },
149
+ });
150
+ //# sourceMappingURL=review-order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-order.js","sourceRoot":"","sources":["../../src/tools/review-order.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export interface ChangedFile {
2
+ file: string;
3
+ status: "added" | "modified" | "deleted" | "renamed";
4
+ additions: number;
5
+ deletions: number;
6
+ }
7
+ /**
8
+ * Get list of changed files from git diff
9
+ * Compares against the merge base with main/master or HEAD if not on a branch
10
+ */
11
+ export declare function getChangedFiles(cwd: string): Promise<ChangedFile[]>;
12
+ /**
13
+ * Get the content of a file at a specific git ref
14
+ */
15
+ export declare function getFileAtRef(cwd: string, file: string, ref?: string): Promise<string | null>;
16
+ /**
17
+ * Check if we're in a git repository
18
+ */
19
+ export declare function isGitRepo(cwd: string): Promise<boolean>;
20
+ /**
21
+ * Get the root of the git repository
22
+ */
23
+ export declare function getGitRoot(cwd: string): Promise<string | null>;
24
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA0DzE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAe,GACnB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7D;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOpE"}
@@ -0,0 +1,96 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execAsync = promisify(exec);
4
+ /**
5
+ * Get list of changed files from git diff
6
+ * Compares against the merge base with main/master or HEAD if not on a branch
7
+ */
8
+ export async function getChangedFiles(cwd) {
9
+ try {
10
+ // Try to find merge base with main or master
11
+ let base = "HEAD";
12
+ try {
13
+ const { stdout: mainBase } = await execAsync("git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null || echo HEAD", { cwd });
14
+ base = mainBase.trim() || "HEAD";
15
+ }
16
+ catch {
17
+ // Fall back to HEAD~1 if no main/master
18
+ try {
19
+ await execAsync("git rev-parse HEAD~1", { cwd });
20
+ base = "HEAD~1";
21
+ }
22
+ catch {
23
+ // Single commit repo, compare against empty tree
24
+ base = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; // empty tree hash
25
+ }
26
+ }
27
+ const { stdout } = await execAsync(`git diff --numstat ${base}`, { cwd });
28
+ const files = [];
29
+ for (const line of stdout.trim().split("\n")) {
30
+ if (!line)
31
+ continue;
32
+ const [addStr, delStr, file] = line.split("\t");
33
+ if (!file)
34
+ continue;
35
+ // Binary files show as "-"
36
+ const additions = addStr === "-" ? 0 : parseInt(addStr, 10);
37
+ const deletions = delStr === "-" ? 0 : parseInt(delStr, 10);
38
+ let status = "modified";
39
+ if (additions > 0 && deletions === 0) {
40
+ // Check if file is truly new
41
+ try {
42
+ await execAsync(`git show ${base}:${file}`, { cwd });
43
+ }
44
+ catch {
45
+ status = "added";
46
+ }
47
+ }
48
+ else if (additions === 0 && deletions > 0) {
49
+ status = "deleted";
50
+ }
51
+ files.push({ file, status, additions, deletions });
52
+ }
53
+ return files;
54
+ }
55
+ catch (error) {
56
+ // Not a git repo or other error
57
+ return [];
58
+ }
59
+ }
60
+ /**
61
+ * Get the content of a file at a specific git ref
62
+ */
63
+ export async function getFileAtRef(cwd, file, ref = "HEAD") {
64
+ try {
65
+ const { stdout } = await execAsync(`git show ${ref}:${file}`, { cwd });
66
+ return stdout;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ /**
73
+ * Check if we're in a git repository
74
+ */
75
+ export async function isGitRepo(cwd) {
76
+ try {
77
+ await execAsync("git rev-parse --git-dir", { cwd });
78
+ return true;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * Get the root of the git repository
86
+ */
87
+ export async function getGitRoot(cwd) {
88
+ try {
89
+ const { stdout } = await execAsync("git rev-parse --show-toplevel", { cwd });
90
+ return stdout.trim();
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ export interface ImportInfo {
2
+ source: string;
3
+ specifiers: string[];
4
+ isDefault: boolean;
5
+ isNamespace: boolean;
6
+ line: number;
7
+ }
8
+ export interface ExportInfo {
9
+ name: string;
10
+ type: "named" | "default" | "reexport";
11
+ line: number;
12
+ }
13
+ /**
14
+ * Extract imports from a TypeScript/JavaScript file
15
+ * Uses regex for speed - not perfect but handles common cases
16
+ */
17
+ export declare function extractImports(filePath: string): Promise<ImportInfo[]>;
18
+ /**
19
+ * Parse imports from file content
20
+ */
21
+ export declare function parseImports(content: string): ImportInfo[];
22
+ /**
23
+ * Extract exports from a TypeScript/JavaScript file
24
+ */
25
+ export declare function extractExports(filePath: string): Promise<ExportInfo[]>;
26
+ /**
27
+ * Parse exports from file content
28
+ */
29
+ export declare function parseExports(content: string): ExportInfo[];
30
+ /**
31
+ * Resolve an import path to a file path
32
+ * Handles relative imports, not aliases (those need tsconfig)
33
+ */
34
+ export declare function resolveImportPath(importSource: string, fromFile: string, extensions?: string[]): string | null;
35
+ /**
36
+ * Build a dependency graph from changed files
37
+ * Returns map of file -> files it imports (that are also in the changeset)
38
+ */
39
+ export declare function buildDependencyGraph(files: string[], cwd: string): Promise<Map<string, string[]>>;
40
+ //# sourceMappingURL=imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/utils/imports.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAY5E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAuG1D;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAY5E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAmE1D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,MAAM,EAAmC,GACpD,MAAM,GAAG,IAAI,CAYf;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EAAE,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAyChC"}