@darrenjcoxon/vibeoptimise 1.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 ADDED
@@ -0,0 +1,122 @@
1
+ # 🚀 VibeOptimise
2
+
3
+ **The ultimate codebase optimisation scanner for AI-assisted development.**
4
+
5
+ Scan your codebase for dead code, duplication, circular dependencies, bundle bloat, inefficient queries, and more — then get an AI-actionable `OPTIMISE.md` for Claude Code, Cursor, or any AI agent to fix everything.
6
+
7
+ > **By the creator of [Vibeguard](https://www.npmjs.com/package/@darrenjcoxon/vibeguard)** — the security scanner for vibe coders.
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ npx @darrenjcoxon/vibeoptimise
13
+ ```
14
+
15
+ That's it. Run it from the root of any JS/TS project.
16
+
17
+ ## What It Scans
18
+
19
+ VibeOptimise runs **10 parallel scanners** that cover every dimension of codebase efficiency:
20
+
21
+ | # | Scanner | What It Finds | Tool |
22
+ |---|---------|---------------|------|
23
+ | 1 | **Knip** | Unused files, exports, dependencies, types | [knip](https://knip.dev) |
24
+ | 2 | **JSCPD** | Copy/pasted code blocks across the codebase | [jscpd](https://jscpd.dev) |
25
+ | 3 | **Madge** | Circular dependencies & orphaned modules | [madge](https://github.com/pahen/madge) |
26
+ | 4 | **Depcheck** | Unused & missing npm packages | [depcheck](https://github.com/depcheck/depcheck) |
27
+ | 5 | **BundlePhobia** | Heavy deps with lighter alternatives | [bundlephobia](https://bundlephobia.com) |
28
+ | 6 | **ESLint Perf** | Import cycles, barrel file bloat, unused imports | [eslint](https://eslint.org) |
29
+ | 7 | **Source Map Explorer** | Bundle composition analysis | [source-map-explorer](https://github.com/danvk/source-map-explorer) |
30
+ | 8 | **Regex Safety** | ReDoS-vulnerable patterns & catastrophic backtracking | Built-in |
31
+ | 9 | **CSS Efficiency** | Unused CSS, duplicate selectors, !important overuse | Built-in |
32
+ | 10 | **Query Efficiency** | N+1 patterns, unbounded queries, overfetching | Built-in |
33
+
34
+ ## Output: OPTIMISE.md
35
+
36
+ The report is structured for AI agents to work through systematically:
37
+
38
+ ```markdown
39
+ # OPTIMISE.md — Codebase Optimisation Report
40
+
41
+ ## 🔴 Critical — Immediate Performance Impact
42
+ - Circular dependencies causing bundle bloat
43
+ - ReDoS-vulnerable regex patterns
44
+ - N+1 query patterns
45
+
46
+ ## 🟠 High — Significant Optimisation Opportunities
47
+ - Unused dependencies (12 packages, ~45MB savings)
48
+ - Unbounded database queries
49
+
50
+ ## 🟡 Medium — Recommended Improvements
51
+ - Duplicated code blocks (with file pairs + line ranges)
52
+ - Heavy deps with lighter alternatives
53
+
54
+ ## 🔵 Low — Nice to Have
55
+ - Unused exports, console.log statements
56
+ - CSS shorthand opportunities
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ```bash
62
+ # Scan current directory
63
+ npx @darrenjcoxon/vibeoptimise
64
+
65
+ # Scan a specific project
66
+ npx @darrenjcoxon/vibeoptimise /path/to/project
67
+
68
+ # Quiet mode — just generate the report
69
+ npx @darrenjcoxon/vibeoptimise --quiet
70
+
71
+ # JSON output (for CI/CD integration)
72
+ npx @darrenjcoxon/vibeoptimise --json
73
+
74
+ # Include test files in analysis
75
+ npx @darrenjcoxon/vibeoptimise --include-tests
76
+
77
+ # Exclude specific patterns
78
+ npx @darrenjcoxon/vibeoptimise --exclude "legacy/**" "vendor/**"
79
+ ```
80
+
81
+ ## AI Agent Workflow
82
+
83
+ 1. Run `npx @darrenjcoxon/vibeoptimise`
84
+ 2. Feed `OPTIMISE.md` to your AI agent
85
+ 3. Tell it: *"Read OPTIMISE.md and implement all fixes, starting from Critical"*
86
+ 4. Re-run VibeOptimise to verify improvements
87
+
88
+ Works with **Claude Code**, **Cursor**, **GitHub Copilot**, **Windsurf**, and any AI coding assistant.
89
+
90
+ ## CI/CD Integration
91
+
92
+ VibeOptimise exits with code 1 when critical or high findings exist — perfect for CI quality gates:
93
+
94
+ ```yaml
95
+ # GitHub Actions
96
+ - name: Check codebase optimisation
97
+ run: npx @darrenjcoxon/vibeoptimise --quiet
98
+ ```
99
+
100
+ ## ORM Support
101
+
102
+ The Query Efficiency scanner understands:
103
+ - **Supabase** — `.from()` without `.select()` or `.limit()`
104
+ - **Prisma** — Unbounded `findMany()`, deeply nested includes
105
+ - **Drizzle** — Queries without `.limit()`
106
+ - **Mongoose** — `find({})` without limit, missing `.lean()`
107
+ - **Raw SQL** — `SELECT *`, missing `LIMIT`
108
+
109
+ ## Companion Tool: Vibeguard
110
+
111
+ Use VibeOptimise alongside [Vibeguard](https://www.npmjs.com/package/@darrenjcoxon/vibeguard) for complete coverage:
112
+
113
+ ```bash
114
+ npx @darrenjcoxon/vibeguard # Security scanning → FIXES.md
115
+ npx @darrenjcoxon/vibeoptimise # Optimisation scanning → OPTIMISE.md
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT — Free for everyone. Built for the vibe coding community. 🤙
121
+
122
+ **Made by [Darren Coxon](https://github.com/darrenjcoxon)**
package/dist/cli.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VibeOptimise CLI
4
+ *
5
+ * Usage:
6
+ * npx @darrenjcoxon/vibeoptimise # Scan current directory
7
+ * npx @darrenjcoxon/vibeoptimise /path # Scan specific directory
8
+ * npx @darrenjcoxon/vibeoptimise --help # Show help
9
+ */
10
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VibeOptimise CLI
4
+ *
5
+ * Usage:
6
+ * npx @darrenjcoxon/vibeoptimise # Scan current directory
7
+ * npx @darrenjcoxon/vibeoptimise /path # Scan specific directory
8
+ * npx @darrenjcoxon/vibeoptimise --help # Show help
9
+ */
10
+ import { Command } from 'commander';
11
+ import chalk from 'chalk';
12
+ import { resolve } from 'path';
13
+ import { existsSync } from 'fs';
14
+ import { Orchestrator } from './orchestrator.js';
15
+ import { Reporter } from './reporter.js';
16
+ const program = new Command();
17
+ program
18
+ .name('vibeoptimise')
19
+ .description('🚀 The ultimate codebase optimisation scanner — find dead code, duplication, circular deps, bundle bloat & more')
20
+ .version('1.0.0')
21
+ .argument('[directory]', 'Directory to scan (default: current directory)', '.')
22
+ .option('-q, --quiet', 'Minimal output — just generate OPTIMISE.md')
23
+ .option('-v, --verbose', 'Show detailed scanner output')
24
+ .option('--include-tests', 'Include test files in analysis')
25
+ .option('--include-css', 'Force CSS scanning even if no CSS files detected')
26
+ .option('--include-bundle', 'Force bundle analysis (requires build output)')
27
+ .option('--json', 'Output results as JSON instead of OPTIMISE.md')
28
+ .option('--no-report', 'Skip generating OPTIMISE.md file')
29
+ .option('-e, --exclude <patterns...>', 'Additional glob patterns to exclude')
30
+ .action(async (directory, opts) => {
31
+ try {
32
+ const targetDir = resolve(directory);
33
+ if (!existsSync(targetDir)) {
34
+ console.error(chalk.red(`\n ✖ Directory not found: ${targetDir}\n`));
35
+ process.exit(1);
36
+ }
37
+ const options = {
38
+ targetDir,
39
+ quiet: opts.quiet || false,
40
+ verbose: opts.verbose || false,
41
+ includeTests: opts.includeTests || false,
42
+ includeCss: opts.includeCss || false,
43
+ includeBundle: opts.includeBundle || false,
44
+ exclude: opts.exclude || [],
45
+ };
46
+ // Run the orchestrator
47
+ const orchestrator = new Orchestrator();
48
+ const report = await orchestrator.run(options);
49
+ // Output results
50
+ if (opts.json) {
51
+ console.log(JSON.stringify(report, null, 2));
52
+ }
53
+ else if (opts.report !== false) {
54
+ const reporter = new Reporter();
55
+ const outputPath = reporter.generateOptimiseMd(report, targetDir);
56
+ console.log('');
57
+ console.log(chalk.green.bold(` 📄 OPTIMISE.md generated → ${outputPath}`));
58
+ console.log('');
59
+ console.log(chalk.cyan(' Next steps:'));
60
+ console.log(chalk.gray(' 1. Feed OPTIMISE.md to your AI agent (Claude Code, Cursor, etc.)'));
61
+ console.log(chalk.gray(' 2. Ask it to "Read OPTIMISE.md and implement all fixes"'));
62
+ console.log(chalk.gray(' 3. Re-run vibeoptimise to verify the improvements'));
63
+ console.log('');
64
+ }
65
+ // Exit with error code if critical or high findings exist
66
+ const hasBlocking = report.summary.critical > 0 || report.summary.high > 0;
67
+ if (hasBlocking && !opts.quiet) {
68
+ console.log(chalk.yellow(' ⚠ Exiting with code 1 due to high/critical findings'));
69
+ console.log(chalk.gray(' Use this in CI to block merges with optimisation debt'));
70
+ console.log('');
71
+ }
72
+ process.exit(hasBlocking ? 1 : 0);
73
+ }
74
+ catch (err) {
75
+ console.error(chalk.red(`\n ✖ Fatal error: ${err.message}\n`));
76
+ if (opts.verbose)
77
+ console.error(err.stack);
78
+ process.exit(2);
79
+ }
80
+ });
81
+ program.parse();
@@ -0,0 +1,13 @@
1
+ /**
2
+ * VibeOptimise — The Ultimate Codebase Optimisation Scanner
3
+ *
4
+ * 10 parallel scanners that find every inefficiency in your codebase
5
+ * and generate OPTIMISE.md for AI agents to fix everything.
6
+ *
7
+ * @module @darrenjcoxon/vibeoptimise
8
+ */
9
+ export { Orchestrator } from './orchestrator.js';
10
+ export { Reporter } from './reporter.js';
11
+ export { getAllScanners } from './scanners/index.js';
12
+ export * from './types.js';
13
+ export { KnipScanner, JscpdScanner, MadgeScanner, DepcheckScanner, BundlePhobiaScanner, EslintPerfScanner, SourceMapExplorerScanner, RegexSafetyScanner, CssEfficiencyScanner, QueryEfficiencyScanner, } from './scanners/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * VibeOptimise — The Ultimate Codebase Optimisation Scanner
3
+ *
4
+ * 10 parallel scanners that find every inefficiency in your codebase
5
+ * and generate OPTIMISE.md for AI agents to fix everything.
6
+ *
7
+ * @module @darrenjcoxon/vibeoptimise
8
+ */
9
+ export { Orchestrator } from './orchestrator.js';
10
+ export { Reporter } from './reporter.js';
11
+ export { getAllScanners } from './scanners/index.js';
12
+ export * from './types.js';
13
+ // Export individual scanners for selective use
14
+ export { KnipScanner, JscpdScanner, MadgeScanner, DepcheckScanner, BundlePhobiaScanner, EslintPerfScanner, SourceMapExplorerScanner, RegexSafetyScanner, CssEfficiencyScanner, QueryEfficiencyScanner, } from './scanners/index.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * VibeOptimise Orchestrator
3
+ *
4
+ * Manages parallel execution of all 10 scanners, aggregates results,
5
+ * and produces the final OptimiseReport.
6
+ */
7
+ import { Scanner, ScanOptions, OptimiseReport } from './types.js';
8
+ export declare class Orchestrator {
9
+ private scanners;
10
+ constructor(scanners?: Scanner[]);
11
+ run(options: ScanOptions): Promise<OptimiseReport>;
12
+ private deduplicateFindings;
13
+ private buildReport;
14
+ private calculateSavings;
15
+ private printSummary;
16
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * VibeOptimise Orchestrator
3
+ *
4
+ * Manages parallel execution of all 10 scanners, aggregates results,
5
+ * and produces the final OptimiseReport.
6
+ */
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ import { SEVERITY_WEIGHTS } from './types.js';
10
+ import { getAllScanners } from './scanners/index.js';
11
+ export class Orchestrator {
12
+ scanners;
13
+ constructor(scanners) {
14
+ this.scanners = scanners || getAllScanners();
15
+ }
16
+ async run(options) {
17
+ const start = Date.now();
18
+ const results = [];
19
+ if (!options.quiet) {
20
+ console.log('');
21
+ console.log(chalk.cyan.bold(' 🚀 VibeOptimise — Codebase Optimisation Scanner'));
22
+ console.log(chalk.gray(` Scanning: ${options.targetDir}`));
23
+ console.log('');
24
+ }
25
+ // Phase 1: Check availability
26
+ const spinner = ora({ text: 'Checking scanner availability...', color: 'cyan' }).start();
27
+ const availability = [];
28
+ for (const scanner of this.scanners) {
29
+ const available = await scanner.isAvailable();
30
+ availability.push({ scanner, available });
31
+ }
32
+ spinner.stop();
33
+ if (!options.quiet) {
34
+ for (const { scanner, available } of availability) {
35
+ const icon = available ? chalk.green('✔') : chalk.yellow('○');
36
+ const status = available ? '' : chalk.gray(' (skipped)');
37
+ console.log(` ${icon} ${scanner.name}${status}`);
38
+ }
39
+ console.log('');
40
+ }
41
+ // Phase 2: Run available scanners in parallel
42
+ const availableScanners = availability
43
+ .filter(a => a.available)
44
+ .map(a => a.scanner);
45
+ if (availableScanners.length === 0) {
46
+ console.log(chalk.red(' ✖ No scanners available. Install dependencies and try again.'));
47
+ return this.buildReport(options, results, Date.now() - start, availability);
48
+ }
49
+ const scanSpinner = ora({
50
+ text: `Running ${availableScanners.length} scanners in parallel...`,
51
+ color: 'cyan',
52
+ }).start();
53
+ const scanPromises = availableScanners.map(async (scanner) => {
54
+ try {
55
+ const result = await scanner.scan(options.targetDir, options);
56
+ return result;
57
+ }
58
+ catch (err) {
59
+ return {
60
+ scanner: scanner.name,
61
+ findings: [],
62
+ summary: `Error: ${err.message}`,
63
+ duration: 0,
64
+ error: err.message,
65
+ };
66
+ }
67
+ });
68
+ const scanResults = await Promise.allSettled(scanPromises);
69
+ for (const result of scanResults) {
70
+ if (result.status === 'fulfilled') {
71
+ results.push(result.value);
72
+ }
73
+ else {
74
+ results.push({
75
+ scanner: 'Unknown',
76
+ findings: [],
77
+ summary: `Failed: ${result.reason}`,
78
+ duration: 0,
79
+ error: String(result.reason),
80
+ });
81
+ }
82
+ }
83
+ scanSpinner.stop();
84
+ // Phase 3: Deduplicate findings across scanners
85
+ const deduped = this.deduplicateFindings(results);
86
+ // Phase 4: Sort findings by severity
87
+ for (const result of deduped) {
88
+ result.findings.sort((a, b) => {
89
+ const weightDiff = SEVERITY_WEIGHTS[b.severity] - SEVERITY_WEIGHTS[a.severity];
90
+ if (weightDiff !== 0)
91
+ return weightDiff;
92
+ return (a.file || '').localeCompare(b.file || '');
93
+ });
94
+ }
95
+ const report = this.buildReport(options, deduped, Date.now() - start, availability);
96
+ // Phase 5: Print summary
97
+ if (!options.quiet) {
98
+ this.printSummary(report);
99
+ }
100
+ return report;
101
+ }
102
+ deduplicateFindings(results) {
103
+ // Knip and Depcheck both find unused dependencies — deduplicate
104
+ const seenDeps = new Set();
105
+ for (const result of results) {
106
+ if (result.scanner === 'Knip') {
107
+ for (const finding of result.findings) {
108
+ if (finding.title.startsWith('Unused dependency:') || finding.title.startsWith('Unused devDependency:')) {
109
+ const depName = finding.title.split(': ')[1];
110
+ seenDeps.add(depName);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ // Remove Depcheck findings that Knip already caught
116
+ for (const result of results) {
117
+ if (result.scanner === 'Depcheck') {
118
+ result.findings = result.findings.filter(finding => {
119
+ if (finding.title.startsWith('Unused dependency:') || finding.title.startsWith('Unused devDependency:')) {
120
+ const depName = finding.title.split(': ')[1];
121
+ return !seenDeps.has(depName);
122
+ }
123
+ return true;
124
+ });
125
+ }
126
+ }
127
+ return results;
128
+ }
129
+ buildReport(options, results, duration, availability) {
130
+ const allFindings = results.flatMap(r => r.findings);
131
+ const summary = {
132
+ totalFindings: allFindings.length,
133
+ critical: allFindings.filter(f => f.severity === 'critical').length,
134
+ high: allFindings.filter(f => f.severity === 'high').length,
135
+ medium: allFindings.filter(f => f.severity === 'medium').length,
136
+ low: allFindings.filter(f => f.severity === 'low').length,
137
+ info: allFindings.filter(f => f.severity === 'info').length,
138
+ estimatedSavings: this.calculateSavings(allFindings),
139
+ scannersPassed: availability.filter(a => a.available).length,
140
+ scannersSkipped: availability.filter(a => !a.available).length,
141
+ scannersFailed: results.filter(r => r.error).length,
142
+ };
143
+ return {
144
+ version: '1.0.0',
145
+ timestamp: new Date().toISOString(),
146
+ targetDir: options.targetDir,
147
+ duration,
148
+ scanners: results,
149
+ summary,
150
+ };
151
+ }
152
+ calculateSavings(findings) {
153
+ const parts = [];
154
+ const unusedFiles = findings.filter(f => f.title?.startsWith('Unused file')).length;
155
+ if (unusedFiles > 0)
156
+ parts.push(`${unusedFiles} dead files`);
157
+ const unusedDeps = findings.filter(f => f.title?.startsWith('Unused dependency')).length;
158
+ if (unusedDeps > 0)
159
+ parts.push(`${unusedDeps} unused packages`);
160
+ const duplication = findings.filter(f => f.category === 'duplication').length;
161
+ if (duplication > 0)
162
+ parts.push(`${duplication} duplication blocks`);
163
+ const circular = findings.filter(f => f.title?.startsWith('Circular')).length;
164
+ if (circular > 0)
165
+ parts.push(`${circular} circular deps`);
166
+ return parts.length > 0 ? parts.join(', ') : 'Codebase is clean';
167
+ }
168
+ printSummary(report) {
169
+ const { summary } = report;
170
+ console.log('');
171
+ console.log(chalk.cyan.bold(' ━━━ Results ━━━'));
172
+ console.log('');
173
+ if (summary.totalFindings === 0) {
174
+ console.log(chalk.green.bold(' ✅ Codebase is optimised — no issues found!'));
175
+ }
176
+ else {
177
+ if (summary.critical > 0)
178
+ console.log(` 🔴 Critical: ${chalk.red.bold(String(summary.critical))}`);
179
+ if (summary.high > 0)
180
+ console.log(` 🟠 High: ${chalk.yellow.bold(String(summary.high))}`);
181
+ if (summary.medium > 0)
182
+ console.log(` 🟡 Medium: ${chalk.yellow(String(summary.medium))}`);
183
+ if (summary.low > 0)
184
+ console.log(` 🔵 Low: ${chalk.blue(String(summary.low))}`);
185
+ if (summary.info > 0)
186
+ console.log(` ⚪ Info: ${chalk.gray(String(summary.info))}`);
187
+ console.log(` ─────────────`);
188
+ console.log(` Total: ${chalk.bold(String(summary.totalFindings))}`);
189
+ }
190
+ console.log('');
191
+ console.log(chalk.gray(` Scanned in ${(report.duration / 1000).toFixed(1)}s with ${summary.scannersPassed}/10 scanners`));
192
+ // Per-scanner breakdown
193
+ console.log('');
194
+ for (const scanner of report.scanners) {
195
+ const count = scanner.findings.length;
196
+ const icon = scanner.error ? chalk.red('✖') : count === 0 ? chalk.green('✔') : chalk.yellow('⚠');
197
+ console.log(` ${icon} ${scanner.scanner}: ${scanner.summary}`);
198
+ }
199
+ console.log('');
200
+ }
201
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * VibeOptimise Reporter
3
+ *
4
+ * Generates OPTIMISE.md — an AI-actionable report that Claude Code,
5
+ * Cursor, or any AI agent can use to automatically fix optimisation issues.
6
+ *
7
+ * Follows the Vibeguard FIXES.md pattern with clear severity separation.
8
+ */
9
+ import { OptimiseReport } from './types.js';
10
+ export declare class Reporter {
11
+ generateOptimiseMd(report: OptimiseReport, outputDir: string): string;
12
+ private buildMarkdown;
13
+ private renderFinding;
14
+ private formatCategoryName;
15
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * VibeOptimise Reporter
3
+ *
4
+ * Generates OPTIMISE.md — an AI-actionable report that Claude Code,
5
+ * Cursor, or any AI agent can use to automatically fix optimisation issues.
6
+ *
7
+ * Follows the Vibeguard FIXES.md pattern with clear severity separation.
8
+ */
9
+ import { writeFileSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { CATEGORY_ICONS } from './types.js';
12
+ export class Reporter {
13
+ generateOptimiseMd(report, outputDir) {
14
+ const outputPath = join(outputDir, 'OPTIMISE.md');
15
+ const content = this.buildMarkdown(report);
16
+ writeFileSync(outputPath, content, 'utf-8');
17
+ return outputPath;
18
+ }
19
+ buildMarkdown(report) {
20
+ const { summary, scanners } = report;
21
+ const allFindings = scanners.flatMap(s => s.findings);
22
+ const lines = [];
23
+ // Header
24
+ lines.push('# OPTIMISE.md — Codebase Optimisation Report');
25
+ lines.push('');
26
+ lines.push(`> Generated by [VibeOptimise](https://www.npmjs.com/package/@darrenjcoxon/vibeoptimise) v${report.version}`);
27
+ lines.push(`> Scanned: ${report.targetDir}`);
28
+ lines.push(`> Date: ${new Date(report.timestamp).toLocaleString()}`);
29
+ lines.push(`> Duration: ${(report.duration / 1000).toFixed(1)}s`);
30
+ lines.push('');
31
+ // AI Agent Instructions
32
+ lines.push('## Instructions for AI Agents');
33
+ lines.push('');
34
+ lines.push('This file contains actionable optimisation findings for your codebase.');
35
+ lines.push('Work through each section in order of priority (Critical → High → Medium → Low).');
36
+ lines.push('Each finding includes the file, line number, and specific fix to apply.');
37
+ lines.push('After making changes, re-run `npx @darrenjcoxon/vibeoptimise` to verify fixes.');
38
+ lines.push('');
39
+ // Summary
40
+ lines.push('## Summary');
41
+ lines.push('');
42
+ lines.push(`| Severity | Count |`);
43
+ lines.push(`|----------|-------|`);
44
+ if (summary.critical > 0)
45
+ lines.push(`| 🔴 Critical | ${summary.critical} |`);
46
+ if (summary.high > 0)
47
+ lines.push(`| 🟠 High | ${summary.high} |`);
48
+ if (summary.medium > 0)
49
+ lines.push(`| 🟡 Medium | ${summary.medium} |`);
50
+ if (summary.low > 0)
51
+ lines.push(`| 🔵 Low | ${summary.low} |`);
52
+ if (summary.info > 0)
53
+ lines.push(`| ⚪ Info | ${summary.info} |`);
54
+ lines.push(`| **Total** | **${summary.totalFindings}** |`);
55
+ lines.push('');
56
+ if (summary.totalFindings === 0) {
57
+ lines.push('✅ **Codebase is optimised — no issues found!**');
58
+ lines.push('');
59
+ return lines.join('\n');
60
+ }
61
+ // Group findings by severity, then by category
62
+ const severityOrder = ['critical', 'high', 'medium', 'low', 'info'];
63
+ const severityLabels = {
64
+ critical: '🔴 Critical — Immediate Performance Impact',
65
+ high: '🟠 High — Significant Optimisation Opportunities',
66
+ medium: '🟡 Medium — Recommended Improvements',
67
+ low: '🔵 Low — Nice to Have',
68
+ info: '⚪ Info — Informational',
69
+ };
70
+ for (const severity of severityOrder) {
71
+ const findings = allFindings.filter(f => f.severity === severity);
72
+ if (findings.length === 0)
73
+ continue;
74
+ lines.push(`---`);
75
+ lines.push('');
76
+ lines.push(`## ${severityLabels[severity]}`);
77
+ lines.push('');
78
+ // Group by category within severity
79
+ const categories = [...new Set(findings.map(f => f.category))];
80
+ for (const category of categories) {
81
+ const catFindings = findings.filter(f => f.category === category);
82
+ const icon = CATEGORY_ICONS[category] || '📋';
83
+ lines.push(`### ${icon} ${this.formatCategoryName(category)} (${catFindings.length})`);
84
+ lines.push('');
85
+ for (const finding of catFindings) {
86
+ this.renderFinding(finding, lines);
87
+ }
88
+ }
89
+ }
90
+ // Scanner Results Summary
91
+ lines.push('---');
92
+ lines.push('');
93
+ lines.push('## Scanner Results');
94
+ lines.push('');
95
+ lines.push('| Scanner | Findings | Duration | Status |');
96
+ lines.push('|---------|----------|----------|--------|');
97
+ for (const scanner of scanners) {
98
+ const status = scanner.error ? '❌ Error' : scanner.findings.length === 0 ? '✅ Clean' : `⚠️ ${scanner.findings.length} issues`;
99
+ const duration = `${(scanner.duration / 1000).toFixed(1)}s`;
100
+ lines.push(`| ${scanner.scanner} | ${scanner.findings.length} | ${duration} | ${status} |`);
101
+ }
102
+ lines.push('');
103
+ // Footer
104
+ lines.push('---');
105
+ lines.push('');
106
+ lines.push('*Generated by VibeOptimise — The ultimate codebase optimisation scanner*');
107
+ lines.push('*Run `npx @darrenjcoxon/vibeoptimise` to re-scan after fixes*');
108
+ lines.push('');
109
+ return lines.join('\n');
110
+ }
111
+ renderFinding(finding, lines) {
112
+ const location = finding.file
113
+ ? finding.line
114
+ ? `\`${finding.file}:${finding.line}\``
115
+ : `\`${finding.file}\``
116
+ : '';
117
+ lines.push(`#### ${finding.title}`);
118
+ lines.push('');
119
+ if (location)
120
+ lines.push(`**Location:** ${location}`);
121
+ if (finding.relatedFiles && finding.relatedFiles.length > 0) {
122
+ lines.push(`**Related files:** ${finding.relatedFiles.map(f => `\`${f}\``).join(', ')}`);
123
+ }
124
+ lines.push('');
125
+ lines.push(`**Issue:** ${finding.description}`);
126
+ lines.push('');
127
+ lines.push(`**Fix:** ${finding.suggestion}`);
128
+ lines.push('');
129
+ if (finding.estimatedImpact) {
130
+ lines.push(`**Impact:** ${finding.estimatedImpact}`);
131
+ lines.push('');
132
+ }
133
+ }
134
+ formatCategoryName(category) {
135
+ return category
136
+ .split('-')
137
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
138
+ .join(' ');
139
+ }
140
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * BundlePhobia Scanner — Dependency Weight Analysis
3
+ *
4
+ * Checks every dependency's bundle size impact and suggests lighter alternatives.
5
+ * Uses the BundlePhobia API to get minified + gzipped sizes.
6
+ *
7
+ * Tool: bundlephobia API (https://bundlephobia.com/api)
8
+ * Output: JSON API responses
9
+ */
10
+ import { Scanner, ScannerResult, ScanOptions } from '../types.js';
11
+ export declare class BundlePhobiaScanner implements Scanner {
12
+ name: string;
13
+ description: string;
14
+ category: "bundle-size";
15
+ isAvailable(): Promise<boolean>;
16
+ scan(targetDir: string, options: ScanOptions): Promise<ScannerResult>;
17
+ private checkBundleSizes;
18
+ private findDuplicatePackages;
19
+ }