projscan 0.1.11 → 0.2.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.
Files changed (38) hide show
  1. package/README.md +141 -32
  2. package/dist/cli/index.js +108 -144
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/core/fileInspector.d.ts +13 -0
  5. package/dist/core/fileInspector.js +205 -0
  6. package/dist/core/fileInspector.js.map +1 -0
  7. package/dist/core/hotspotAnalyzer.d.ts +16 -0
  8. package/dist/core/hotspotAnalyzer.js +342 -0
  9. package/dist/core/hotspotAnalyzer.js.map +1 -0
  10. package/dist/index.d.ts +7 -1
  11. package/dist/index.js +6 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp/prompts.d.ts +14 -0
  14. package/dist/mcp/prompts.js +126 -0
  15. package/dist/mcp/prompts.js.map +1 -0
  16. package/dist/mcp/resources.d.ts +8 -0
  17. package/dist/mcp/resources.js +57 -0
  18. package/dist/mcp/resources.js.map +1 -0
  19. package/dist/mcp/server.d.ts +5 -0
  20. package/dist/mcp/server.js +205 -0
  21. package/dist/mcp/server.js.map +1 -0
  22. package/dist/mcp/tools.d.ts +9 -0
  23. package/dist/mcp/tools.js +176 -0
  24. package/dist/mcp/tools.js.map +1 -0
  25. package/dist/reporters/consoleReporter.d.ts +3 -1
  26. package/dist/reporters/consoleReporter.js +127 -0
  27. package/dist/reporters/consoleReporter.js.map +1 -1
  28. package/dist/reporters/jsonReporter.d.ts +3 -1
  29. package/dist/reporters/jsonReporter.js +6 -0
  30. package/dist/reporters/jsonReporter.js.map +1 -1
  31. package/dist/reporters/markdownReporter.d.ts +3 -1
  32. package/dist/reporters/markdownReporter.js +99 -0
  33. package/dist/reporters/markdownReporter.js.map +1 -1
  34. package/dist/types.d.ts +88 -0
  35. package/dist/utils/baseline.d.ts +4 -4
  36. package/dist/utils/baseline.js +71 -5
  37. package/dist/utils/baseline.js.map +1 -1
  38. package/package.json +2 -2
package/README.md CHANGED
@@ -8,7 +8,9 @@
8
8
 
9
9
  **Instant codebase insights — doctor, x-ray, and architecture map for any repository.**
10
10
 
11
- [Install](#install) · [Quick Start](#quick-start) · [Commands](#commands) · [Full Guide](docs/GUIDE.md)
11
+ [Install](#install) · [Quick Start](#quick-start) · [Commands](#commands) · [Full Guide](docs/GUIDE.md) · [Roadmap](docs/ROADMAP.md)
12
+
13
+ <img src="docs/hero.png" alt="projscan banner" width="600">
12
14
 
13
15
  </div>
14
16
 
@@ -28,35 +30,19 @@ Answering these manually takes 10-30 minutes of poking through config files and
28
30
  **projscan answers all of this in one command, in under 2 seconds.**
29
31
 
30
32
  ```bash
31
- $ projscan doctor
32
-
33
- Project Health Report
34
- ──────────────────────────────────────────
35
-
36
- Health Score: C (67/100)
37
- Found 3 warnings, 2 info
38
-
39
- Issues Detected
40
- ──────────────────────────────────────────
41
- ⚠ No ESLint configuration
42
- ⚠ No Prettier configuration
43
- ⚠ No test framework detected
44
- ℹ Missing .editorconfig
45
- ℹ README is nearly empty
46
-
47
- Run projscan fix to auto-fix 4 issues.
33
+ npx projscan
48
34
  ```
49
35
 
50
- And it doesn't just report problems — it **fixes them**:
36
+ <img src="docs/npx%20projscan.png" alt="npx projscan" width="700">
37
+
38
+ Run `projscan doctor` for a focused health check:
51
39
 
52
40
  ```bash
53
- $ projscan fix -y
54
- ✔ Installed ESLint with TypeScript support
55
- ✔ Installed Prettier with sensible defaults
56
- ✔ Installed Vitest with sample test
57
- ✔ Created .editorconfig
41
+ npx projscan doctor
58
42
  ```
59
43
 
44
+ <img src="docs/npx%20projscan%20doctor.png" alt="npx projscan doctor" width="700">
45
+
60
46
  ## Install
61
47
 
62
48
  ```bash
@@ -74,15 +60,20 @@ npx projscan
74
60
  Run inside any repository:
75
61
 
76
62
  ```bash
77
- projscan # Full project analysis
78
- projscan doctor # Health check
79
- projscan fix # Auto-fix detected issues
80
- projscan ci # CI health gate (exits 1 on low score)
81
- projscan diff # Compare health against a baseline
82
- projscan diagram # Architecture visualization
83
- projscan structure # Directory tree
63
+ projscan # Full project analysis
64
+ projscan doctor # Health check
65
+ projscan hotspots # Rank files by risk (churn × complexity × issues × ownership)
66
+ projscan file <path> # Drill into a file purpose, risk, ownership, issues
67
+ projscan fix # Auto-fix detected issues
68
+ projscan ci # CI health gate (exits 1 on low score)
69
+ projscan diff # Compare health + hotspot trends against a baseline
70
+ projscan diagram # Architecture visualization
71
+ projscan structure # Directory tree
72
+ projscan mcp # Run as an MCP server for AI coding agents
84
73
  ```
85
74
 
75
+ <img src="docs/npx%20projscan%20--help.png" alt="npx projscan --help" width="700">
76
+
86
77
  For a comprehensive walkthrough, see the **[Full Guide](docs/GUIDE.md)**.
87
78
 
88
79
  ## Commands
@@ -91,14 +82,17 @@ For a comprehensive walkthrough, see the **[Full Guide](docs/GUIDE.md)**.
91
82
  |---------|-------------|
92
83
  | `projscan analyze` | Full analysis — languages, frameworks, dependencies, issues |
93
84
  | `projscan doctor` | Health check — missing tooling, architecture smells, security risks |
85
+ | `projscan hotspots` | Rank files by risk — churn × complexity × issues × ownership |
86
+ | `projscan file <path>` | Drill into a file — purpose, risk, ownership, related issues |
94
87
  | `projscan fix` | Auto-fix issues (ESLint, Prettier, Vitest, .editorconfig) |
95
88
  | `projscan ci` | CI pipeline health gate — exits 1 if score below threshold |
96
- | `projscan diff` | Compare current health against a saved baseline |
89
+ | `projscan diff` | Compare current health **and hotspot trends** against a baseline |
97
90
  | `projscan explain <file>` | Explain a file's purpose, imports, exports, and issues |
98
91
  | `projscan diagram` | ASCII architecture diagram of your project |
99
92
  | `projscan structure` | Directory tree with file counts |
100
93
  | `projscan dependencies` | Dependency analysis — counts, risks, recommendations |
101
94
  | `projscan badge` | Generate a health score badge for your README |
95
+ | `projscan mcp` | Run as an MCP server for AI coding agents (Claude Code, Cursor, …) |
102
96
 
103
97
  To see all commands and options, run:
104
98
 
@@ -106,6 +100,38 @@ To see all commands and options, run:
106
100
  projscan --help
107
101
  ```
108
102
 
103
+ ### Command Screenshots
104
+
105
+ <details>
106
+ <summary><strong>projscan structure</strong> — Directory tree with file counts</summary>
107
+
108
+ <img src="docs/npx%20projscan%20structure.png" alt="npx projscan structure" width="700">
109
+ </details>
110
+
111
+ <details>
112
+ <summary><strong>projscan diagram</strong> — Architecture visualization</summary>
113
+
114
+ <img src="docs/npx%20projscan%20diagram.png" alt="npx projscan diagram" width="700">
115
+ </details>
116
+
117
+ <details>
118
+ <summary><strong>projscan dependencies</strong> — Dependency analysis</summary>
119
+
120
+ <img src="docs/npx%20projscan%20dependencies.png" alt="npx projscan dependencies" width="700">
121
+ </details>
122
+
123
+ <details>
124
+ <summary><strong>projscan explain</strong> — File explanation</summary>
125
+
126
+ <img src="docs/npx%20projscan%20explain.png" alt="npx projscan explain" width="700">
127
+ </details>
128
+
129
+ <details>
130
+ <summary><strong>projscan badge</strong> — Health badge generation</summary>
131
+
132
+ <img src="docs/npx%20projscan%20badge.png" alt="npx projscan badge" width="700">
133
+ </details>
134
+
109
135
  ### Output Formats
110
136
 
111
137
  All commands support `--format` for different output targets:
@@ -147,6 +173,8 @@ projscan badge
147
173
 
148
174
  This outputs a [shields.io](https://shields.io) badge URL and markdown snippet you can paste into your README.
149
175
 
176
+ **Sample badge:** [![projscan health](https://img.shields.io/badge/projscan-D-orange)](https://github.com/abhiyoheswaran1/projscan)
177
+
150
178
  ## What It Detects
151
179
 
152
180
  **Languages**: TypeScript, JavaScript, Python, Go, Rust, Java, Ruby, C/C++, PHP, Swift, Kotlin, and 20+ more
@@ -180,6 +208,8 @@ projscan ci --min-score 70 # Exits 1 if score < 70
180
208
  projscan ci --min-score 80 --format json # JSON output for parsing
181
209
  ```
182
210
 
211
+ <img src="docs/npx%20projscan%20ci%20--min-score%2070.png" alt="npx projscan ci --min-score 70" width="700">
212
+
183
213
  ### GitHub Actions
184
214
 
185
215
  Copy the included workflow template to your project:
@@ -201,10 +231,89 @@ projscan diff # Compare against baseline
201
231
  projscan diff --format markdown # Markdown diff for PRs
202
232
  ```
203
233
 
234
+ <img src="docs/npx%20projscan%20diff%20--save-baseline.png" alt="npx projscan diff --save-baseline" width="700">
235
+
236
+ ## Hotspots — Where to Fix First
237
+
238
+ A flat health score doesn't tell you what to do. **`projscan hotspots`** combines `git log` churn, file complexity, open issues, recency, and **ownership** into a single risk score per file — so you know where refactoring or review will actually pay off.
239
+
240
+ ```bash
241
+ projscan hotspots # Top 10 hotspots
242
+ projscan hotspots --limit 20
243
+ projscan hotspots --since "6 months ago"
244
+ projscan hotspots --format json # Machine-readable for dashboards
245
+ projscan hotspots --format markdown # Drop into a PR or tech-debt ticket
246
+ ```
247
+
248
+ Hotspot ranking follows the classic Feathers "churn × complexity" heuristic with boosts for files that fail `projscan doctor`, changed recently, or show **bus factor 1** (single-author + high churn). Falls back gracefully outside a git repo.
249
+
250
+ ### Drill Into a Hotspot
251
+
252
+ ```bash
253
+ projscan file src/cli/index.ts
254
+ ```
255
+
256
+ Combines the file's purpose, imports, exports, hotspot risk, ownership, and every open issue that references it — the natural follow-up to `projscan hotspots`.
257
+
258
+ ### Track Trends Over Time
259
+
260
+ ```bash
261
+ projscan diff --save-baseline # Snapshots health + hotspots
262
+ # ...time passes, commits happen...
263
+ projscan diff # Shows which hotspots rose / fell
264
+ ```
265
+
266
+ The baseline file now captures top hotspots too, so `diff` surfaces files that are **getting worse** (not just new issues).
267
+
268
+ ## AI Agent Integration (MCP)
269
+
270
+ **`projscan mcp`** starts an [MCP](https://modelcontextprotocol.io) server over stdio so AI coding agents can query projscan during a session.
271
+
272
+ **Tools** (7):
273
+ - `projscan_analyze` — full project report
274
+ - `projscan_doctor` — health score + issues
275
+ - `projscan_hotspots` — risk-ranked files (with `limit`, `since` args)
276
+ - `projscan_file` — per-file risk + ownership + related issues
277
+ - `projscan_explain` — per-file purpose, imports, exports, smells
278
+ - `projscan_structure` — directory tree
279
+ - `projscan_dependencies` — package audit
280
+
281
+ **Prompts** (2, parameterized with live project data):
282
+ - `prioritize_refactoring` — ranked plan grounded in current hotspots
283
+ - `investigate_file` — senior-engineer brief for a specific file
284
+
285
+ **Resources** (3, readable on demand):
286
+ - `projscan://health` · `projscan://hotspots` · `projscan://structure`
287
+
288
+ ### Claude Code
289
+
290
+ ```bash
291
+ claude mcp add projscan -- npx projscan mcp
292
+ ```
293
+
294
+ ### Cursor / Windsurf / any MCP client
295
+
296
+ Add to your MCP config:
297
+
298
+ ```json
299
+ {
300
+ "mcpServers": {
301
+ "projscan": {
302
+ "command": "npx",
303
+ "args": ["projscan", "mcp"]
304
+ }
305
+ }
306
+ }
307
+ ```
308
+
309
+ Now your agent can ask *"what are the riskiest files in this repo?"* and get a grounded answer, or run `projscan_doctor` before proposing an edit.
310
+
204
311
  ## Use Cases
205
312
 
206
313
  - **Onboarding**: Understand any codebase in seconds, not hours
207
314
  - **Code reviews**: Run `projscan doctor --format markdown` and paste into PRs
315
+ - **Tech-debt prioritization**: Use `projscan hotspots` to decide what deserves refactoring time
316
+ - **AI-assisted development**: Mount `projscan mcp` in your agent of choice for grounded edits
208
317
  - **CI/CD**: Use `projscan ci` to enforce health standards in your pipeline
209
318
  - **Security**: Catch committed secrets and `.env` files before they reach production
210
319
  - **Consulting**: Quickly assess client projects before diving in
package/dist/cli/index.js CHANGED
@@ -14,14 +14,17 @@ import { detectLanguages } from '../core/languageDetector.js';
14
14
  import { detectFrameworks } from '../core/frameworkDetector.js';
15
15
  import { analyzeDependencies } from '../core/dependencyAnalyzer.js';
16
16
  import { collectIssues } from '../core/issueEngine.js';
17
+ import { analyzeHotspots } from '../core/hotspotAnalyzer.js';
18
+ import { inspectFile, extractImports, extractExports, inferPurpose, detectFileIssues, } from '../core/fileInspector.js';
17
19
  import { getAllAvailableFixes } from '../fixes/fixRegistry.js';
18
20
  import { setLogLevel } from '../utils/logger.js';
19
21
  import { calculateScore, badgeUrl, badgeMarkdown } from '../utils/scoreCalculator.js';
20
22
  import { showBanner, showCompactBanner, showHelp } from '../utils/banner.js';
21
23
  import { saveBaseline, loadBaseline, computeDiff } from '../utils/baseline.js';
22
- import { reportAnalysis, reportHealth, reportCi, reportDiff, reportDetectedIssues, reportExplanation, reportDiagram, reportStructure, reportDependencies, } from '../reporters/consoleReporter.js';
23
- import { reportAnalysisJson, reportHealthJson, reportCiJson, reportDiffJson, reportExplanationJson, reportDiagramJson, reportStructureJson, reportDependenciesJson, } from '../reporters/jsonReporter.js';
24
- import { reportAnalysisMarkdown, reportHealthMarkdown, reportCiMarkdown, reportDiffMarkdown, reportExplanationMarkdown, reportDiagramMarkdown, reportStructureMarkdown, reportDependenciesMarkdown, } from '../reporters/markdownReporter.js';
24
+ import { runMcpServer } from '../mcp/server.js';
25
+ import { reportAnalysis, reportHealth, reportCi, reportDiff, reportDetectedIssues, reportExplanation, reportDiagram, reportStructure, reportDependencies, reportHotspots, reportFileInspection, } from '../reporters/consoleReporter.js';
26
+ import { reportAnalysisJson, reportHealthJson, reportCiJson, reportDiffJson, reportExplanationJson, reportDiagramJson, reportStructureJson, reportDependenciesJson, reportHotspotsJson, reportFileJson, } from '../reporters/jsonReporter.js';
27
+ import { reportAnalysisMarkdown, reportHealthMarkdown, reportCiMarkdown, reportDiffMarkdown, reportExplanationMarkdown, reportDiagramMarkdown, reportStructureMarkdown, reportDependenciesMarkdown, reportHotspotsMarkdown, reportFileMarkdown, } from '../reporters/markdownReporter.js';
25
28
  // ── CLI Setup ─────────────────────────────────────────────
26
29
  const program = new Command();
27
30
  program
@@ -205,12 +208,19 @@ program
205
208
  try {
206
209
  const scan = await scanRepository(rootPath);
207
210
  const issues = await collectIssues(rootPath, scan.files);
211
+ const hotspotReport = await analyzeHotspots(rootPath, scan.files, issues, { limit: 20 });
208
212
  if (cmdOpts.saveBaseline) {
209
- const filePath = await saveBaseline(rootPath, issues);
213
+ const filePath = await saveBaseline(rootPath, issues, hotspotReport);
210
214
  const { score, grade } = calculateScore(issues);
211
215
  console.log(chalk.green(`\n Baseline saved to ${filePath}`));
212
216
  console.log(` Score: ${chalk.bold(`${grade} (${score}/100)`)}`);
213
- console.log(` Issues: ${issues.length}\n`);
217
+ console.log(` Issues: ${issues.length}`);
218
+ if (hotspotReport.available) {
219
+ console.log(` Hotspots snapshotted: ${hotspotReport.hotspots.length}\n`);
220
+ }
221
+ else {
222
+ console.log('');
223
+ }
214
224
  return;
215
225
  }
216
226
  let baseline;
@@ -222,7 +232,7 @@ program
222
232
  console.error(` Run ${chalk.bold.cyan('projscan diff --save-baseline')} first to create one.\n`);
223
233
  process.exit(1);
224
234
  }
225
- const diff = computeDiff(baseline, issues);
235
+ const diff = computeDiff(baseline, issues, hotspotReport);
226
236
  switch (format) {
227
237
  case 'json':
228
238
  reportDiffJson(diff);
@@ -299,6 +309,42 @@ program
299
309
  process.exit(1);
300
310
  }
301
311
  });
312
+ // ── Command: file ─────────────────────────────────────────
313
+ program
314
+ .command('file <file>')
315
+ .description('Drill into a file — purpose, risk, ownership, related issues')
316
+ .action(async (filePath) => {
317
+ setupLogLevel();
318
+ maybeCompactBanner();
319
+ const rootPath = getRootPath();
320
+ const format = getFormat();
321
+ const spinner = format === 'console' ? ora('Inspecting file...').start() : null;
322
+ try {
323
+ const inspection = await inspectFile(rootPath, filePath);
324
+ if (spinner)
325
+ spinner.stop();
326
+ if (!inspection.exists) {
327
+ console.error(chalk.red(`\n ${inspection.reason ?? 'File unavailable'}: ${filePath}\n`));
328
+ process.exit(1);
329
+ }
330
+ switch (format) {
331
+ case 'json':
332
+ reportFileJson(inspection);
333
+ break;
334
+ case 'markdown':
335
+ reportFileMarkdown(inspection);
336
+ break;
337
+ default:
338
+ reportFileInspection(inspection);
339
+ }
340
+ }
341
+ catch (error) {
342
+ if (spinner)
343
+ spinner.fail('File inspection failed');
344
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
345
+ process.exit(1);
346
+ }
347
+ });
302
348
  // ── Command: explain ──────────────────────────────────────
303
349
  program
304
350
  .command('explain <file>')
@@ -434,6 +480,61 @@ program
434
480
  process.exit(1);
435
481
  }
436
482
  });
483
+ // ── Command: hotspots ─────────────────────────────────────
484
+ program
485
+ .command('hotspots')
486
+ .description('Rank files by risk (git churn × complexity × open issues)')
487
+ .option('--limit <n>', 'number of hotspots to show', '10')
488
+ .option('--since <when>', 'git history window (e.g. "6 months ago", "2024-01-01")', '12 months ago')
489
+ .action(async (cmdOpts) => {
490
+ setupLogLevel();
491
+ maybeCompactBanner();
492
+ const rootPath = getRootPath();
493
+ const format = getFormat();
494
+ const spinner = format === 'console' ? ora('Analyzing hotspots...').start() : null;
495
+ try {
496
+ const scan = await scanRepository(rootPath);
497
+ const issues = await collectIssues(rootPath, scan.files);
498
+ const limit = Math.max(1, Math.min(100, parseInt(cmdOpts.limit, 10) || 10));
499
+ const report = await analyzeHotspots(rootPath, scan.files, issues, {
500
+ since: cmdOpts.since,
501
+ limit,
502
+ });
503
+ if (spinner)
504
+ spinner.stop();
505
+ switch (format) {
506
+ case 'json':
507
+ reportHotspotsJson(report);
508
+ break;
509
+ case 'markdown':
510
+ reportHotspotsMarkdown(report);
511
+ break;
512
+ default:
513
+ reportHotspots(report);
514
+ }
515
+ }
516
+ catch (error) {
517
+ if (spinner)
518
+ spinner.fail('Hotspot analysis failed');
519
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
520
+ process.exit(1);
521
+ }
522
+ });
523
+ // ── Command: mcp ──────────────────────────────────────────
524
+ program
525
+ .command('mcp')
526
+ .description('Run projscan as an MCP server (stdio) for AI coding agents')
527
+ .action(async () => {
528
+ setLogLevel('quiet');
529
+ const rootPath = getRootPath();
530
+ try {
531
+ await runMcpServer(rootPath);
532
+ }
533
+ catch (error) {
534
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
535
+ process.exit(1);
536
+ }
537
+ });
437
538
  // ── Command: badge ────────────────────────────────────────
438
539
  program
439
540
  .command('badge')
@@ -473,7 +574,7 @@ function analyzeFile(filePath, content) {
473
574
  const lines = content.split('\n');
474
575
  const imports = extractImports(content);
475
576
  const exports = extractExports(content);
476
- const purpose = inferPurpose(filePath, imports, exports);
577
+ const purpose = inferPurpose(filePath, exports);
477
578
  const potentialIssues = detectFileIssues(content, lines.length);
478
579
  return {
479
580
  filePath: path.relative(process.cwd(), filePath),
@@ -484,143 +585,6 @@ function analyzeFile(filePath, content) {
484
585
  lineCount: lines.length,
485
586
  };
486
587
  }
487
- function extractImports(content) {
488
- const imports = [];
489
- const seen = new Set();
490
- // ES import
491
- const esImportRegex = /import\s+(?:(?:\{[^}]*\}|[\w*]+(?:\s*,\s*\{[^}]*\})?|\*\s+as\s+\w+)\s+from\s+)?['"]([^'"]+)['"]/gm;
492
- let match;
493
- while ((match = esImportRegex.exec(content)) !== null) {
494
- const source = match[1];
495
- if (!seen.has(source)) {
496
- seen.add(source);
497
- imports.push({
498
- source,
499
- specifiers: [],
500
- isRelative: source.startsWith('.') || source.startsWith('/'),
501
- });
502
- }
503
- }
504
- // CommonJS require
505
- const requireRegex = /(?:const|let|var)\s+(?:\{[^}]*\}|\w+)\s*=\s*require\(\s*['"]([^'"]+)['"]\s*\)/gm;
506
- while ((match = requireRegex.exec(content)) !== null) {
507
- const source = match[1];
508
- if (!seen.has(source)) {
509
- seen.add(source);
510
- imports.push({
511
- source,
512
- specifiers: [],
513
- isRelative: source.startsWith('.') || source.startsWith('/'),
514
- });
515
- }
516
- }
517
- return imports;
518
- }
519
- function extractExports(content) {
520
- const exports = [];
521
- // export function
522
- const funcRegex = /^export\s+(?:async\s+)?function\s+(\w+)/gm;
523
- let match;
524
- while ((match = funcRegex.exec(content)) !== null) {
525
- exports.push({ name: match[1], type: 'function' });
526
- }
527
- // export class
528
- const classRegex = /^export\s+class\s+(\w+)/gm;
529
- while ((match = classRegex.exec(content)) !== null) {
530
- exports.push({ name: match[1], type: 'class' });
531
- }
532
- // export const/let/var
533
- const varRegex = /^export\s+(?:const|let|var)\s+(\w+)/gm;
534
- while ((match = varRegex.exec(content)) !== null) {
535
- exports.push({ name: match[1], type: 'variable' });
536
- }
537
- // export interface
538
- const interfaceRegex = /^export\s+interface\s+(\w+)/gm;
539
- while ((match = interfaceRegex.exec(content)) !== null) {
540
- exports.push({ name: match[1], type: 'interface' });
541
- }
542
- // export type
543
- const typeRegex = /^export\s+type\s+(\w+)/gm;
544
- while ((match = typeRegex.exec(content)) !== null) {
545
- exports.push({ name: match[1], type: 'type' });
546
- }
547
- // export default
548
- if (/^export\s+default/m.test(content)) {
549
- exports.push({ name: 'default', type: 'default' });
550
- }
551
- return exports;
552
- }
553
- function inferPurpose(filePath, imports, exports) {
554
- const name = path.basename(filePath, path.extname(filePath)).toLowerCase();
555
- const dir = path.dirname(filePath).toLowerCase();
556
- if (name.includes('test') || name.includes('spec'))
557
- return 'Test file';
558
- if (name.includes('config') || name.includes('rc'))
559
- return 'Configuration file';
560
- if (name === 'index')
561
- return 'Module entry point / barrel file';
562
- if (name === 'main' || name === 'app')
563
- return 'Application entry point';
564
- if (name.includes('route') || name.includes('router'))
565
- return 'Route definitions';
566
- if (name.includes('middleware'))
567
- return 'Middleware handler';
568
- if (name.includes('controller'))
569
- return 'Request controller';
570
- if (name.includes('service'))
571
- return 'Service layer logic';
572
- if (name.includes('model') || name.includes('schema'))
573
- return 'Data model / schema definition';
574
- if (name.includes('util') || name.includes('helper'))
575
- return 'Utility functions';
576
- if (name.includes('hook'))
577
- return 'Custom hook';
578
- if (name.includes('context') || name.includes('provider'))
579
- return 'Context / state provider';
580
- if (name.includes('type') || name.includes('interface'))
581
- return 'Type definitions';
582
- if (name.includes('constant') || name.includes('config'))
583
- return 'Constants / configuration';
584
- if (name.includes('migration'))
585
- return 'Database migration';
586
- if (name.includes('seed'))
587
- return 'Database seed data';
588
- if (name.includes('auth'))
589
- return 'Authentication logic';
590
- if (name.includes('api'))
591
- return 'API endpoint handler';
592
- if (dir.includes('component') || dir.includes('pages'))
593
- return 'UI component';
594
- if (dir.includes('service'))
595
- return 'Service module';
596
- if (dir.includes('model'))
597
- return 'Data model';
598
- if (dir.includes('util') || dir.includes('lib'))
599
- return 'Library / utility module';
600
- const exportTypes = exports.map((e) => e.type);
601
- if (exportTypes.includes('class'))
602
- return 'Class-based module';
603
- if (exportTypes.filter((t) => t === 'function').length > 2)
604
- return 'Function library';
605
- return 'Source module';
606
- }
607
- function detectFileIssues(content, lineCount) {
608
- const issues = [];
609
- if (lineCount > 500)
610
- issues.push(`Large file (${lineCount} lines) — consider splitting`);
611
- if (lineCount > 1000)
612
- issues.push('Very large file — strongly consider refactoring');
613
- if (/console\.(log|warn|error|debug)\s*\(/.test(content)) {
614
- issues.push('Contains console.log statements — consider using a proper logger');
615
- }
616
- if (/TODO|FIXME|HACK|XXX/i.test(content)) {
617
- issues.push('Contains TODO/FIXME comments');
618
- }
619
- if (/any\b/.test(content) && /\.tsx?$/.test(content)) {
620
- issues.push('Uses "any" type — consider using proper types');
621
- }
622
- return issues;
623
- }
624
588
  // ── Architecture Layer Detection ──────────────────────────
625
589
  function buildArchitectureLayers(files, frameworkNames) {
626
590
  const layers = [];