@sun-asterisk/sunlint 1.3.34 → 1.3.35

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.
Files changed (90) hide show
  1. package/core/architecture-integration.js +16 -7
  2. package/core/auto-performance-manager.js +1 -1
  3. package/core/cli-action-handler.js +92 -2
  4. package/core/cli-program.js +96 -138
  5. package/core/file-targeting-service.js +62 -4
  6. package/core/git-utils.js +19 -12
  7. package/core/github-annotate-service.js +326 -11
  8. package/core/html-report-generator.js +326 -731
  9. package/core/impact-integration.js +433 -0
  10. package/core/output-service.js +293 -21
  11. package/core/scoring-service.js +3 -2
  12. package/engines/arch-detect/core/analyzer.js +413 -0
  13. package/engines/arch-detect/core/index.js +22 -0
  14. package/engines/arch-detect/engine/hybrid-detector.js +176 -0
  15. package/engines/arch-detect/engine/index.js +24 -0
  16. package/engines/arch-detect/engine/rule-executor.js +228 -0
  17. package/engines/arch-detect/engine/score-calculator.js +214 -0
  18. package/engines/arch-detect/engine/violation-detector.js +616 -0
  19. package/engines/arch-detect/index.js +50 -0
  20. package/engines/arch-detect/rules/base-rule.js +187 -0
  21. package/engines/arch-detect/rules/index.js +35 -0
  22. package/engines/arch-detect/rules/layered/index.js +28 -0
  23. package/engines/arch-detect/rules/layered/l001-presentation-layer.js +237 -0
  24. package/engines/arch-detect/rules/layered/l002-business-layer.js +215 -0
  25. package/engines/arch-detect/rules/layered/l003-data-layer.js +229 -0
  26. package/engines/arch-detect/rules/layered/l004-model-layer.js +204 -0
  27. package/engines/arch-detect/rules/layered/l005-layer-separation.js +215 -0
  28. package/engines/arch-detect/rules/layered/l006-dependency-direction.js +221 -0
  29. package/engines/arch-detect/rules/layered/layered-rules-collection.js +445 -0
  30. package/engines/arch-detect/rules/modular/index.js +27 -0
  31. package/engines/arch-detect/rules/modular/m001-feature-modules.js +238 -0
  32. package/engines/arch-detect/rules/modular/m002-core-module.js +169 -0
  33. package/engines/arch-detect/rules/modular/m003-module-declaration.js +186 -0
  34. package/engines/arch-detect/rules/modular/m004-public-api.js +171 -0
  35. package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +220 -0
  36. package/engines/arch-detect/rules/modular/modular-rules-collection.js +357 -0
  37. package/engines/arch-detect/rules/presentation/index.js +27 -0
  38. package/engines/arch-detect/rules/presentation/pr001-view-layer.js +221 -0
  39. package/engines/arch-detect/rules/presentation/pr002-presentation-logic.js +192 -0
  40. package/engines/arch-detect/rules/presentation/pr004-data-binding.js +187 -0
  41. package/engines/arch-detect/rules/presentation/pr006-router-layer.js +185 -0
  42. package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +181 -0
  43. package/engines/arch-detect/rules/presentation/presentation-rules-collection.js +507 -0
  44. package/engines/arch-detect/rules/project-scanner/index.js +31 -0
  45. package/engines/arch-detect/rules/project-scanner/ps001-project-root.js +213 -0
  46. package/engines/arch-detect/rules/project-scanner/ps002-language-detection.js +192 -0
  47. package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +339 -0
  48. package/engines/arch-detect/rules/project-scanner/ps004-build-system.js +171 -0
  49. package/engines/arch-detect/rules/project-scanner/ps005-source-directory.js +163 -0
  50. package/engines/arch-detect/rules/project-scanner/ps006-test-directory.js +184 -0
  51. package/engines/arch-detect/rules/project-scanner/ps007-documentation.js +149 -0
  52. package/engines/arch-detect/rules/project-scanner/ps008-cicd-detection.js +163 -0
  53. package/engines/arch-detect/rules/project-scanner/ps009-code-quality.js +152 -0
  54. package/engines/arch-detect/rules/project-scanner/ps010-statistics.js +180 -0
  55. package/engines/arch-detect/rules/rule-registry.js +111 -0
  56. package/engines/arch-detect/types/context.types.js +60 -0
  57. package/engines/arch-detect/types/enums.js +161 -0
  58. package/engines/arch-detect/types/index.js +25 -0
  59. package/engines/arch-detect/types/result.types.js +7 -0
  60. package/engines/arch-detect/types/rule.types.js +7 -0
  61. package/engines/arch-detect/utils/file-scanner.js +411 -0
  62. package/engines/arch-detect/utils/index.js +23 -0
  63. package/engines/arch-detect/utils/pattern-matcher.js +328 -0
  64. package/engines/impact/cli.js +106 -0
  65. package/engines/impact/config/default-config.js +54 -0
  66. package/engines/impact/core/change-detector.js +258 -0
  67. package/engines/impact/core/detectors/database-detector.js +1317 -0
  68. package/engines/impact/core/detectors/endpoint-detector.js +55 -0
  69. package/engines/impact/core/impact-analyzer.js +124 -0
  70. package/engines/impact/core/report-generator.js +462 -0
  71. package/engines/impact/core/utils/ast-parser.js +241 -0
  72. package/engines/impact/core/utils/dependency-graph.js +159 -0
  73. package/engines/impact/core/utils/file-utils.js +116 -0
  74. package/engines/impact/core/utils/git-utils.js +203 -0
  75. package/engines/impact/core/utils/logger.js +13 -0
  76. package/engines/impact/core/utils/method-call-graph.js +1192 -0
  77. package/engines/impact/index.js +135 -0
  78. package/engines/impact/package.json +29 -0
  79. package/package.json +18 -43
  80. package/scripts/build-release.sh +0 -0
  81. package/scripts/copy-impact-analyzer.js +135 -0
  82. package/scripts/install.sh +0 -0
  83. package/scripts/manual-release.sh +0 -0
  84. package/scripts/pre-release-test.sh +0 -0
  85. package/scripts/prepare-release.sh +0 -0
  86. package/scripts/quick-performance-test.js +0 -0
  87. package/scripts/setup-github-registry.sh +0 -0
  88. package/scripts/trigger-release.sh +0 -0
  89. package/scripts/verify-install.sh +0 -0
  90. package/templates/combined-report.html +1418 -0
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ /**
3
+ * Pattern Matcher Utility
4
+ * Hỗ trợ matching các patterns trong code và config
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CommonPatterns = exports.PatternMatcher = void 0;
8
+ const enums_1 = require("../types/enums");
9
+ const file_scanner_1 = require("./file-scanner");
10
+ /**
11
+ * Pattern Matcher class
12
+ */
13
+ class PatternMatcher {
14
+ constructor(_projectRoot) {
15
+ this.fileContentCache = new Map();
16
+ }
17
+ /**
18
+ * Match folder patterns
19
+ */
20
+ async matchFolderPatterns(patterns, directoryTree) {
21
+ const matches = [];
22
+ for (const pattern of patterns) {
23
+ const regex = this.globToRegex(pattern);
24
+ const matchedFolders = file_scanner_1.FileScanner.findFoldersByPattern(directoryTree, regex);
25
+ for (const folder of matchedFolders) {
26
+ matches.push({
27
+ type: enums_1.MatchType.FOLDER,
28
+ path: folder.relativePath,
29
+ matchedPattern: pattern,
30
+ });
31
+ }
32
+ }
33
+ return {
34
+ matched: matches.length > 0,
35
+ matches,
36
+ score: Math.min(matches.length / patterns.length, 1),
37
+ occurrences: matches.length,
38
+ };
39
+ }
40
+ /**
41
+ * Match file patterns
42
+ */
43
+ async matchFilePatterns(patterns, files) {
44
+ const matches = [];
45
+ for (const pattern of patterns) {
46
+ const regex = this.globToRegex(pattern);
47
+ const matchedFiles = file_scanner_1.FileScanner.findFilesByPattern(files, regex);
48
+ for (const file of matchedFiles) {
49
+ matches.push({
50
+ type: enums_1.MatchType.FILE,
51
+ path: file.relativePath,
52
+ matchedPattern: pattern,
53
+ });
54
+ }
55
+ }
56
+ return {
57
+ matched: matches.length > 0,
58
+ matches,
59
+ score: Math.min(matches.length / patterns.length, 1),
60
+ occurrences: matches.length,
61
+ };
62
+ }
63
+ /**
64
+ * Match code patterns trong files
65
+ */
66
+ async matchCodePatterns(codePatterns, files) {
67
+ const matches = [];
68
+ for (const codePattern of codePatterns) {
69
+ const filteredFiles = this.filterFilesByLanguage(files, codePattern.language);
70
+ const regex = this.ensureRegex(codePattern.pattern);
71
+ for (const file of filteredFiles) {
72
+ try {
73
+ const content = await this.getFileContent(file.absolutePath);
74
+ const lines = content.split('\n');
75
+ for (let i = 0; i < lines.length; i++) {
76
+ const line = lines[i];
77
+ const match = line.match(regex);
78
+ if (match) {
79
+ matches.push({
80
+ type: enums_1.MatchType.CODE,
81
+ path: file.relativePath,
82
+ line: i + 1,
83
+ column: match.index || 0,
84
+ snippet: line.trim(),
85
+ matchedPattern: codePattern.pattern.toString(),
86
+ });
87
+ }
88
+ }
89
+ }
90
+ catch {
91
+ // Skip files that cannot be read
92
+ }
93
+ }
94
+ }
95
+ // Check minimum occurrences
96
+ const minOccurrences = codePatterns[0]?.minOccurrences || 1;
97
+ const passed = matches.length >= minOccurrences;
98
+ return {
99
+ matched: passed,
100
+ matches,
101
+ score: passed ? Math.min(matches.length / (minOccurrences * 2), 1) : 0,
102
+ occurrences: matches.length,
103
+ };
104
+ }
105
+ /**
106
+ * Match config patterns
107
+ */
108
+ async matchConfigPatterns(configPatterns, files) {
109
+ const matches = [];
110
+ for (const configPattern of configPatterns) {
111
+ const regex = this.globToRegex(configPattern.filePattern);
112
+ const configFiles = files.filter((f) => regex.test(f.relativePath) || regex.test(f.fileName));
113
+ for (const file of configFiles) {
114
+ try {
115
+ const content = await this.getFileContent(file.absolutePath);
116
+ let matched = false;
117
+ // Check contains
118
+ if (configPattern.contains) {
119
+ if (content.includes(configPattern.contains)) {
120
+ matched = true;
121
+ }
122
+ }
123
+ // Check regex
124
+ if (configPattern.regex) {
125
+ const patternRegex = this.ensureRegex(configPattern.regex);
126
+ if (patternRegex.test(content)) {
127
+ matched = true;
128
+ }
129
+ }
130
+ // Check JSON path
131
+ if (configPattern.jsonPath && file.extension === '.json') {
132
+ try {
133
+ const json = JSON.parse(content);
134
+ if (this.checkJsonPath(json, configPattern.jsonPath)) {
135
+ matched = true;
136
+ }
137
+ }
138
+ catch {
139
+ // Invalid JSON, skip
140
+ }
141
+ }
142
+ // If no specific check, just match file existence
143
+ if (!configPattern.contains && !configPattern.regex && !configPattern.jsonPath) {
144
+ matched = true;
145
+ }
146
+ if (matched) {
147
+ matches.push({
148
+ type: enums_1.MatchType.CONFIG,
149
+ path: file.relativePath,
150
+ matchedPattern: configPattern.filePattern,
151
+ });
152
+ }
153
+ }
154
+ catch {
155
+ // Skip files that cannot be read
156
+ }
157
+ }
158
+ }
159
+ return {
160
+ matched: matches.length > 0,
161
+ matches,
162
+ score: Math.min(matches.length / configPatterns.length, 1),
163
+ occurrences: matches.length,
164
+ };
165
+ }
166
+ /**
167
+ * Check JSON path exists
168
+ */
169
+ checkJsonPath(obj, jsonPath) {
170
+ // Simple JSON path like $.dependencies.express
171
+ const parts = jsonPath.replace(/^\$\.?/, '').split('.');
172
+ let current = obj;
173
+ for (const part of parts) {
174
+ if (current === null || current === undefined) {
175
+ return false;
176
+ }
177
+ if (typeof current === 'object' && part in current) {
178
+ current = current[part];
179
+ }
180
+ else {
181
+ return false;
182
+ }
183
+ }
184
+ return current !== undefined;
185
+ }
186
+ /**
187
+ * Filter files by language
188
+ */
189
+ filterFilesByLanguage(files, language) {
190
+ if (language === 'all') {
191
+ return files;
192
+ }
193
+ return files.filter((f) => f.language === language);
194
+ }
195
+ /**
196
+ * Get file content with caching
197
+ */
198
+ async getFileContent(filePath) {
199
+ if (this.fileContentCache.has(filePath)) {
200
+ return this.fileContentCache.get(filePath);
201
+ }
202
+ const content = await file_scanner_1.FileScanner.readFileContent(filePath);
203
+ this.fileContentCache.set(filePath, content);
204
+ return content;
205
+ }
206
+ /**
207
+ * Convert glob to regex
208
+ */
209
+ globToRegex(pattern) {
210
+ const regexStr = pattern
211
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
212
+ .replace(/\*\*/g, '{{GLOBSTAR}}')
213
+ .replace(/\*/g, '[^/]*')
214
+ .replace(/\?/g, '.')
215
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*');
216
+ return new RegExp(regexStr, 'i');
217
+ }
218
+ /**
219
+ * Ensure pattern is a RegExp
220
+ */
221
+ ensureRegex(pattern) {
222
+ return typeof pattern === 'string' ? new RegExp(pattern) : pattern;
223
+ }
224
+ /**
225
+ * Clear content cache
226
+ */
227
+ clearCache() {
228
+ this.fileContentCache.clear();
229
+ }
230
+ }
231
+ exports.PatternMatcher = PatternMatcher;
232
+ /**
233
+ * Common code patterns for various languages
234
+ */
235
+ exports.CommonPatterns = {
236
+ // Java patterns
237
+ java: {
238
+ controller: [
239
+ /@Controller\b/,
240
+ /@RestController\b/,
241
+ /@RequestMapping\b/,
242
+ /@GetMapping\b/,
243
+ /@PostMapping\b/,
244
+ /@PutMapping\b/,
245
+ /@DeleteMapping\b/,
246
+ ],
247
+ service: [/@Service\b/, /@Component\b/, /@Transactional\b/],
248
+ repository: [/@Repository\b/, /extends\s+(Jpa|Crud|Mongo)Repository\b/, /@Query\b/],
249
+ entity: [/@Entity\b/, /@Table\b/, /@Data\b/, /@Getter\b/, /@Setter\b/],
250
+ },
251
+ // TypeScript/JavaScript patterns
252
+ typescript: {
253
+ controller: [
254
+ /@Controller\s*\(/,
255
+ /@Get\s*\(/,
256
+ /@Post\s*\(/,
257
+ /@Put\s*\(/,
258
+ /@Delete\s*\(/,
259
+ /router\.(get|post|put|delete)\s*\(/,
260
+ /app\.(get|post|put|delete)\s*\(/,
261
+ ],
262
+ service: [/@Injectable\s*\(/, /\.service\.ts$/, /class\s+\w+Service\b/],
263
+ repository: [/@EntityRepository\s*\(/, /\.repository\.ts$/, /class\s+\w+Repository\b/],
264
+ entity: [/@Entity\s*\(/, /@Column\s*\(/, /interface\s+\w+\s*\{/],
265
+ module: [/@Module\s*\(/, /@NgModule\s*\(/],
266
+ viewModel: [
267
+ /useState\s*[<(]/,
268
+ /useReducer\s*\(/,
269
+ /ref\s*[<(]/,
270
+ /reactive\s*[<(]/,
271
+ /computed\s*\(/,
272
+ ],
273
+ },
274
+ // Swift patterns
275
+ swift: {
276
+ viewController: [
277
+ /class\s+\w+ViewController\s*:\s*UIViewController/,
278
+ /struct\s+\w+View\s*:\s*View/,
279
+ ],
280
+ viewModel: [
281
+ /class\s+\w+ViewModel\s*:\s*ObservableObject/,
282
+ /@Published\b/,
283
+ /@StateObject\b/,
284
+ /@ObservedObject\b/,
285
+ ],
286
+ interactor: [/protocol\s+\w+Interactor/, /class\s+\w+Interactor/],
287
+ router: [/protocol\s+\w+Router/, /class\s+\w+Router/, /class\s+\w+Coordinator/],
288
+ },
289
+ // Dart/Flutter patterns
290
+ dart: {
291
+ widget: [/class\s+\w+\s+extends\s+StatefulWidget/, /class\s+\w+\s+extends\s+StatelessWidget/],
292
+ viewModel: [/class\s+\w+\s+extends\s+ChangeNotifier/, /notifyListeners\s*\(/],
293
+ bloc: [/class\s+\w+Bloc\s+extends\s+Bloc/, /@freezed\b/],
294
+ },
295
+ // C# patterns
296
+ csharp: {
297
+ controller: [
298
+ /\[ApiController\]/,
299
+ /\[HttpGet\]/,
300
+ /\[HttpPost\]/,
301
+ /:\s*ControllerBase\b/,
302
+ /:\s*Controller\b/,
303
+ ],
304
+ service: [/class\s+\w+Service\b/, /interface\s+I\w+Service\b/],
305
+ repository: [/DbContext\b/, /DbSet\s*</, /interface\s+I\w+Repository\b/],
306
+ viewModel: [/:\s*INotifyPropertyChanged\b/, /PropertyChanged\?\s*\.Invoke/],
307
+ },
308
+ // PHP patterns
309
+ php: {
310
+ controller: [/extends\s+Controller\b/, /Route::get\s*\(/, /Route::post\s*\(/],
311
+ service: [/class\s+\w+Service\b/],
312
+ repository: [/extends\s+Model\b/, /\$fillable\s*=/, /DB::table\s*\(/],
313
+ },
314
+ // Python patterns
315
+ python: {
316
+ view: [/@app\.route\s*\(/, /@api_view\s*\(/, /class\s+\w+View\s*\(\s*APIView\s*\)/],
317
+ service: [/class\s+\w+Service\b/],
318
+ model: [/@dataclass\b/, /class\s+\w+\s*\(\s*models\.Model\s*\)/],
319
+ },
320
+ // Go patterns
321
+ go: {
322
+ handler: [/func\s+\(\w+\s+\*?\w*Handler\)/, /r\.HandleFunc\s*\(/, /gin\.Context/],
323
+ service: [/type\s+\w+Service\s+struct/],
324
+ repository: [/type\s+\w+Repository\s+struct/],
325
+ },
326
+ };
327
+ exports.default = PatternMatcher;
328
+ //# sourceMappingURL=pattern-matcher.js.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * CLI - Command Line Interface Parser
3
+ * Handles argument parsing and validation
4
+ */
5
+
6
+ export class CLI {
7
+ constructor(argv) {
8
+ this.args = new Map();
9
+ this.parseArgs(argv);
10
+ }
11
+
12
+ parseArgs(argv) {
13
+ for (let i = 2; i < argv.length; i++) {
14
+ const arg = argv[i];
15
+
16
+ if (arg.startsWith('--')) {
17
+ const [key, value] = arg.substring(2).split('=');
18
+ this.args.set(key, value || 'true');
19
+ } else if (arg.startsWith('-')) {
20
+ // Short flags
21
+ const key = arg.substring(1);
22
+ this.args.set(key, 'true');
23
+ }
24
+ }
25
+ }
26
+
27
+ getArg(key, defaultValue = '') {
28
+ return this.args.get(key) || defaultValue;
29
+ }
30
+
31
+ hasArg(key) {
32
+ return this.args.has(key);
33
+ }
34
+
35
+ getAllArgs() {
36
+ return Object.fromEntries(this.args);
37
+ }
38
+
39
+ showHelp() {
40
+ console.log(`
41
+ ╔═══════════════════════════════════════════════════════════════════╗
42
+ ║ 🔍 Impact Analyzer CLI - Help Guide ║
43
+ ╚═══════════════════════════════════════════════════════════════════╝
44
+
45
+ USAGE:
46
+ impact-analyzer [OPTIONS]
47
+
48
+ DESCRIPTION:
49
+ Analyzes code changes and their impact on endpoints, database, and
50
+ components in TypeScript/JavaScript projects.
51
+
52
+ OPTIONS:
53
+ --input=<path> Source directory to analyze
54
+ Default: src
55
+
56
+ --base=<ref> Base git reference for comparison
57
+ Default: HEAD~1 (previous commit)
58
+ Examples: origin/main, HEAD~5, abc123
59
+
60
+ --head=<ref> Head git reference (optional)
61
+ Default: (working directory)
62
+
63
+ --exclude=<paths> Comma-separated paths to exclude
64
+ Default: node_modules,dist,build,specs,coverage
65
+
66
+ --output=<file> Output markdown report file
67
+ Default: impact-report.md
68
+
69
+ --json=<file> Output JSON report file (optional)
70
+ Example: --json=impact-report.json
71
+
72
+ --max-depth=<n> Maximum call graph depth
73
+ Default: 3
74
+
75
+ --include-tests Include test files in analysis
76
+
77
+ --verbose Show verbose output and stack traces
78
+
79
+ --no-fail Don't exit with error on critical impact
80
+
81
+ --help, -h Show this help message
82
+
83
+ EXAMPLES:
84
+ # Analyze changes in last commit (default)
85
+ impact-analyzer
86
+
87
+ # Analyze changes between main branch and current
88
+ impact-analyzer --base=origin/main
89
+
90
+ # Analyze specific directory with JSON output
91
+ impact-analyzer --input=backend/src --json=report.json
92
+
93
+ # Analyze last 3 commits with verbose output
94
+ impact-analyzer --base=HEAD~3 --verbose
95
+
96
+ # Compare two specific commits
97
+ impact-analyzer --base=abc123 --head=def456
98
+
99
+ REFERENCE:
100
+ Architecture: .specify/plans/architecture.md
101
+ Coding Rules: .github/copilot-instructions.md
102
+
103
+ For more information, visit: https://github.com/sun-asterisk/impact-analyzer
104
+ `);
105
+ }
106
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Configuration Loader
3
+ * Loads and validates configuration from CLI args and defaults
4
+ */
5
+
6
+ export function loadConfig(cli) {
7
+ const config = {
8
+ // Source directory to analyze
9
+ sourceDir: cli.getArg('input', 'src'),
10
+
11
+ // Paths to exclude from analysis
12
+ excludePaths: cli.getArg('exclude', 'node_modules,dist,build,specs,coverage')
13
+ .split(',')
14
+ .map(p => p.trim()),
15
+
16
+ // Git references - defaults to HEAD~1 (previous commit)
17
+ baseRef: cli.getArg('base', 'HEAD~1'),
18
+ headRef: cli.getArg('head', ''), // Empty means current working directory
19
+
20
+ // Analysis options
21
+ maxDepth: parseInt(cli.getArg('max-depth', '3')),
22
+ includeTests: cli.hasArg('include-tests'),
23
+ verbose: cli.hasArg('verbose'),
24
+
25
+ // Report options
26
+ outputFormat: cli.getArg('format', 'markdown'),
27
+ outputFile: cli.getArg('output', 'impact-report.md'),
28
+ };
29
+
30
+ // Validate configuration
31
+ validateConfig(config);
32
+
33
+ return config;
34
+ }
35
+
36
+ function validateConfig(config) {
37
+ if (!config.sourceDir) {
38
+ throw new Error('Source directory (--input) is required');
39
+ }
40
+
41
+ // baseRef and headRef are optional, have sensible defaults
42
+ }
43
+
44
+ export const DEFAULT_CONFIG = {
45
+ sourceDir: 'src',
46
+ excludePaths: ['node_modules', 'dist', 'build', 'specs', 'coverage'],
47
+ baseRef: 'HEAD~1', // Previous commit
48
+ headRef: 'HEAD', // Current commit
49
+ maxDepth: 3,
50
+ includeTests: false,
51
+ verbose: false,
52
+ outputFormat: 'markdown',
53
+ outputFile: 'impact-report.md',
54
+ };