@bfra.me/workspace-analyzer 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.
Files changed (73) hide show
  1. package/README.md +402 -0
  2. package/lib/chunk-4LSFAAZW.js +1 -0
  3. package/lib/chunk-JDF7DQ4V.js +27 -0
  4. package/lib/chunk-WOJ4C7N7.js +7122 -0
  5. package/lib/cli.d.ts +1 -0
  6. package/lib/cli.js +318 -0
  7. package/lib/index.d.ts +3701 -0
  8. package/lib/index.js +1262 -0
  9. package/lib/types/index.d.ts +146 -0
  10. package/lib/types/index.js +28 -0
  11. package/package.json +89 -0
  12. package/src/analyzers/analyzer.ts +201 -0
  13. package/src/analyzers/architectural-analyzer.ts +304 -0
  14. package/src/analyzers/build-config-analyzer.ts +334 -0
  15. package/src/analyzers/circular-import-analyzer.ts +463 -0
  16. package/src/analyzers/config-consistency-analyzer.ts +335 -0
  17. package/src/analyzers/dead-code-analyzer.ts +565 -0
  18. package/src/analyzers/duplicate-code-analyzer.ts +626 -0
  19. package/src/analyzers/duplicate-dependency-analyzer.ts +381 -0
  20. package/src/analyzers/eslint-config-analyzer.ts +281 -0
  21. package/src/analyzers/exports-field-analyzer.ts +324 -0
  22. package/src/analyzers/index.ts +388 -0
  23. package/src/analyzers/large-dependency-analyzer.ts +535 -0
  24. package/src/analyzers/package-json-analyzer.ts +349 -0
  25. package/src/analyzers/peer-dependency-analyzer.ts +275 -0
  26. package/src/analyzers/tree-shaking-analyzer.ts +623 -0
  27. package/src/analyzers/tsconfig-analyzer.ts +382 -0
  28. package/src/analyzers/unused-dependency-analyzer.ts +356 -0
  29. package/src/analyzers/version-alignment-analyzer.ts +308 -0
  30. package/src/api/analyze-workspace.ts +245 -0
  31. package/src/api/index.ts +11 -0
  32. package/src/cache/cache-manager.ts +495 -0
  33. package/src/cache/cache-schema.ts +247 -0
  34. package/src/cache/change-detector.ts +169 -0
  35. package/src/cache/file-hasher.ts +65 -0
  36. package/src/cache/index.ts +47 -0
  37. package/src/cli/commands/analyze.ts +240 -0
  38. package/src/cli/commands/index.ts +5 -0
  39. package/src/cli/index.ts +61 -0
  40. package/src/cli/types.ts +65 -0
  41. package/src/cli/ui.ts +213 -0
  42. package/src/cli.ts +9 -0
  43. package/src/config/defaults.ts +183 -0
  44. package/src/config/index.ts +81 -0
  45. package/src/config/loader.ts +270 -0
  46. package/src/config/merger.ts +229 -0
  47. package/src/config/schema.ts +263 -0
  48. package/src/core/incremental-analyzer.ts +462 -0
  49. package/src/core/index.ts +34 -0
  50. package/src/core/orchestrator.ts +416 -0
  51. package/src/graph/dependency-graph.ts +408 -0
  52. package/src/graph/index.ts +19 -0
  53. package/src/index.ts +417 -0
  54. package/src/parser/config-parser.ts +491 -0
  55. package/src/parser/import-extractor.ts +340 -0
  56. package/src/parser/index.ts +54 -0
  57. package/src/parser/typescript-parser.ts +95 -0
  58. package/src/performance/bundle-estimator.ts +444 -0
  59. package/src/performance/index.ts +27 -0
  60. package/src/reporters/console-reporter.ts +355 -0
  61. package/src/reporters/index.ts +49 -0
  62. package/src/reporters/json-reporter.ts +273 -0
  63. package/src/reporters/markdown-reporter.ts +349 -0
  64. package/src/reporters/reporter.ts +399 -0
  65. package/src/rules/builtin-rules.ts +709 -0
  66. package/src/rules/index.ts +52 -0
  67. package/src/rules/rule-engine.ts +409 -0
  68. package/src/scanner/index.ts +18 -0
  69. package/src/scanner/workspace-scanner.ts +403 -0
  70. package/src/types/index.ts +176 -0
  71. package/src/types/result.ts +19 -0
  72. package/src/utils/index.ts +7 -0
  73. package/src/utils/pattern-matcher.ts +48 -0
@@ -0,0 +1,324 @@
1
+ /**
2
+ * ExportsFieldAnalyzer - Validates package.json exports against actual source structure.
3
+ *
4
+ * Detects issues such as:
5
+ * - Exports pointing to non-existent files
6
+ * - Missing exports for existing source files
7
+ * - Invalid export conditions
8
+ * - Inconsistent export patterns
9
+ */
10
+
11
+ import type {ParsedPackageJson} from '../parser/config-parser'
12
+ import type {WorkspacePackage} from '../scanner/workspace-scanner'
13
+ import type {Issue, IssueLocation} from '../types/index'
14
+ import type {Result} from '../types/result'
15
+ import type {AnalysisContext, Analyzer, AnalyzerError, AnalyzerMetadata} from './analyzer'
16
+
17
+ import fs from 'node:fs/promises'
18
+ import path from 'node:path'
19
+
20
+ import {ok} from '@bfra.me/es/result'
21
+
22
+ import {createIssue, filterIssues} from './analyzer'
23
+
24
+ /**
25
+ * Configuration options specific to ExportsFieldAnalyzer.
26
+ */
27
+ export interface ExportsFieldAnalyzerOptions {
28
+ /** Whether to check if export paths resolve to existing files */
29
+ readonly validatePaths?: boolean
30
+ /** Whether to check export condition order */
31
+ readonly checkConditionOrder?: boolean
32
+ /** Whether to suggest missing exports for source files */
33
+ readonly suggestMissingExports?: boolean
34
+ /** Common output directories to check for built files */
35
+ readonly outputDirs?: readonly string[]
36
+ /** Package names exempt from checks */
37
+ readonly exemptPackages?: readonly string[]
38
+ }
39
+
40
+ const DEFAULT_OPTIONS: ExportsFieldAnalyzerOptions = {
41
+ validatePaths: true,
42
+ checkConditionOrder: true,
43
+ suggestMissingExports: false,
44
+ outputDirs: ['lib', 'dist', 'build', 'out'],
45
+ exemptPackages: [],
46
+ }
47
+
48
+ const METADATA: AnalyzerMetadata = {
49
+ id: 'exports-field',
50
+ name: 'Exports Field Analyzer',
51
+ description: 'Validates package.json exports against actual source file structure',
52
+ categories: ['configuration'],
53
+ defaultSeverity: 'warning',
54
+ }
55
+
56
+ /**
57
+ * Standard export conditions in recommended order.
58
+ */
59
+ const CONDITION_ORDER = ['types', 'import', 'require', 'default'] as const
60
+
61
+ /**
62
+ * Creates an ExportsFieldAnalyzer instance.
63
+ */
64
+ export function createExportsFieldAnalyzer(options: ExportsFieldAnalyzerOptions = {}): Analyzer {
65
+ const resolvedOptions = {...DEFAULT_OPTIONS, ...options}
66
+
67
+ return {
68
+ metadata: METADATA,
69
+ analyze: async (context: AnalysisContext): Promise<Result<readonly Issue[], AnalyzerError>> => {
70
+ const issues: Issue[] = []
71
+
72
+ for (const pkg of context.packages) {
73
+ if (isExemptPackage(pkg.name, resolvedOptions.exemptPackages)) {
74
+ continue
75
+ }
76
+
77
+ const pkgJson = pkg.packageJson as ParsedPackageJson
78
+
79
+ if (pkgJson.exports === undefined) {
80
+ continue
81
+ }
82
+
83
+ const packageIssues = await analyzePackageExports(pkg, pkgJson, resolvedOptions)
84
+ issues.push(...packageIssues)
85
+ }
86
+
87
+ return ok(filterIssues(issues, context.config))
88
+ },
89
+ }
90
+ }
91
+
92
+ function isExemptPackage(name: string, exemptPackages: readonly string[] | undefined): boolean {
93
+ return exemptPackages?.includes(name) ?? false
94
+ }
95
+
96
+ function createLocation(filePath: string): IssueLocation {
97
+ return {filePath}
98
+ }
99
+
100
+ async function analyzePackageExports(
101
+ pkg: WorkspacePackage,
102
+ pkgJson: ParsedPackageJson,
103
+ options: ExportsFieldAnalyzerOptions,
104
+ ): Promise<Issue[]> {
105
+ const issues: Issue[] = []
106
+ const exports = pkgJson.exports
107
+
108
+ if (exports === undefined) {
109
+ return issues
110
+ }
111
+
112
+ // Analyze each export entry
113
+ const exportEntries = normalizeExports(exports)
114
+
115
+ for (const [exportPath, conditions] of exportEntries) {
116
+ // Check condition order
117
+ if (options.checkConditionOrder && typeof conditions === 'object' && conditions !== null) {
118
+ const orderIssues = checkConditionOrder(
119
+ pkg,
120
+ exportPath,
121
+ conditions as Record<string, unknown>,
122
+ )
123
+ issues.push(...orderIssues)
124
+ }
125
+
126
+ // Validate export paths
127
+ if (options.validatePaths) {
128
+ const pathIssues = await validateExportPaths(pkg, exportPath, conditions, options)
129
+ issues.push(...pathIssues)
130
+ }
131
+ }
132
+
133
+ // Check for package.json export (required for many tools)
134
+ issues.push(...checkPackageJsonExport(pkg, exports))
135
+
136
+ return issues
137
+ }
138
+
139
+ /**
140
+ * Normalizes exports to a consistent Map format.
141
+ */
142
+ function normalizeExports(exports: Record<string, unknown>): Map<string, unknown> {
143
+ const normalized = new Map<string, unknown>()
144
+
145
+ for (const [key, value] of Object.entries(exports)) {
146
+ normalized.set(key, value)
147
+ }
148
+
149
+ return normalized
150
+ }
151
+
152
+ /**
153
+ * Checks if export conditions are in the recommended order.
154
+ */
155
+ function checkConditionOrder(
156
+ pkg: WorkspacePackage,
157
+ exportPath: string,
158
+ conditions: Record<string, unknown>,
159
+ ): Issue[] {
160
+ const issues: Issue[] = []
161
+ const conditionKeys = Object.keys(conditions)
162
+
163
+ // Find the positions of standard conditions
164
+ const positions = new Map<string, number>()
165
+ for (const [index, key] of conditionKeys.entries()) {
166
+ if ((CONDITION_ORDER as readonly string[]).includes(key)) {
167
+ positions.set(key, index)
168
+ }
169
+ }
170
+
171
+ // Check if types comes before import/require
172
+ const typesPos = positions.get('types')
173
+ const importPos = positions.get('import')
174
+ const requirePos = positions.get('require')
175
+
176
+ if (typesPos !== undefined) {
177
+ if (importPos !== undefined && typesPos > importPos) {
178
+ issues.push(
179
+ createIssue({
180
+ id: 'exports-types-after-import',
181
+ title: 'Export types condition should come before import',
182
+ description: `Package "${pkg.name}" export "${exportPath}" has "types" after "import" condition`,
183
+ severity: 'warning',
184
+ category: 'configuration',
185
+ location: createLocation(pkg.packageJsonPath),
186
+ suggestion: 'Move "types" condition before "import" for proper TypeScript resolution',
187
+ metadata: {exportPath, conditions: conditionKeys},
188
+ }),
189
+ )
190
+ }
191
+
192
+ if (requirePos !== undefined && typesPos > requirePos) {
193
+ issues.push(
194
+ createIssue({
195
+ id: 'exports-types-after-require',
196
+ title: 'Export types condition should come before require',
197
+ description: `Package "${pkg.name}" export "${exportPath}" has "types" after "require" condition`,
198
+ severity: 'warning',
199
+ category: 'configuration',
200
+ location: createLocation(pkg.packageJsonPath),
201
+ suggestion: 'Move "types" condition before "require" for proper TypeScript resolution',
202
+ metadata: {exportPath, conditions: conditionKeys},
203
+ }),
204
+ )
205
+ }
206
+ }
207
+
208
+ return issues
209
+ }
210
+
211
+ /**
212
+ * Validates that export paths resolve to existing files.
213
+ */
214
+ async function validateExportPaths(
215
+ pkg: WorkspacePackage,
216
+ exportPath: string,
217
+ conditions: unknown,
218
+ options: ExportsFieldAnalyzerOptions,
219
+ ): Promise<Issue[]> {
220
+ const issues: Issue[] = []
221
+
222
+ const paths = extractPaths(conditions)
223
+
224
+ for (const filePath of paths) {
225
+ // Skip non-file paths
226
+ if (!filePath.startsWith('./')) {
227
+ continue
228
+ }
229
+
230
+ const absolutePath = path.join(pkg.packagePath, filePath)
231
+ const exists = await fileExists(absolutePath)
232
+
233
+ if (!exists) {
234
+ // Check if this looks like a build output
235
+ const isBuildOutput = (options.outputDirs ?? []).some(dir => filePath.startsWith(`./${dir}/`))
236
+
237
+ if (isBuildOutput) {
238
+ // Build outputs might not exist yet - info level
239
+ issues.push(
240
+ createIssue({
241
+ id: 'exports-build-output-missing',
242
+ title: 'Export path points to non-existent build output',
243
+ description: `Package "${pkg.name}" export "${exportPath}" points to "${filePath}" which does not exist (may need build)`,
244
+ severity: 'info',
245
+ category: 'configuration',
246
+ location: createLocation(pkg.packageJsonPath),
247
+ suggestion: 'Run build to generate the file, or verify the path is correct',
248
+ metadata: {exportPath, filePath},
249
+ }),
250
+ )
251
+ } else {
252
+ // Source file missing - warning level
253
+ issues.push(
254
+ createIssue({
255
+ id: 'exports-path-missing',
256
+ title: 'Export path points to non-existent file',
257
+ description: `Package "${pkg.name}" export "${exportPath}" points to "${filePath}" which does not exist`,
258
+ severity: 'warning',
259
+ category: 'configuration',
260
+ location: createLocation(pkg.packageJsonPath),
261
+ suggestion: 'Verify the export path is correct or create the missing file',
262
+ metadata: {exportPath, filePath},
263
+ }),
264
+ )
265
+ }
266
+ }
267
+ }
268
+
269
+ return issues
270
+ }
271
+
272
+ /**
273
+ * Extracts all file paths from an export condition structure.
274
+ */
275
+ function extractPaths(value: unknown): string[] {
276
+ const paths: string[] = []
277
+
278
+ if (typeof value === 'string') {
279
+ paths.push(value)
280
+ } else if (typeof value === 'object' && value !== null) {
281
+ for (const v of Object.values(value)) {
282
+ paths.push(...extractPaths(v))
283
+ }
284
+ }
285
+
286
+ return paths
287
+ }
288
+
289
+ /**
290
+ * Checks if package.json is exported (required by many tools).
291
+ */
292
+ function checkPackageJsonExport(pkg: WorkspacePackage, exports: Record<string, unknown>): Issue[] {
293
+ const issues: Issue[] = []
294
+
295
+ const hasPackageJsonExport =
296
+ exports['./package.json'] !== undefined || exports['./package.json'] === './package.json'
297
+
298
+ if (!hasPackageJsonExport) {
299
+ issues.push(
300
+ createIssue({
301
+ id: 'exports-no-package-json',
302
+ title: 'Missing package.json export',
303
+ description: `Package "${pkg.name}" does not export "./package.json"`,
304
+ severity: 'info',
305
+ category: 'configuration',
306
+ location: createLocation(pkg.packageJsonPath),
307
+ suggestion: 'Add "./package.json": "./package.json" to exports for tool compatibility',
308
+ }),
309
+ )
310
+ }
311
+
312
+ return issues
313
+ }
314
+
315
+ async function fileExists(filePath: string): Promise<boolean> {
316
+ try {
317
+ await fs.access(filePath)
318
+ return true
319
+ } catch {
320
+ return false
321
+ }
322
+ }
323
+
324
+ export {METADATA as exportsFieldAnalyzerMetadata}
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Analyzer registry and exports for workspace analysis.
3
+ *
4
+ * Provides a plugin architecture for registering and managing analyzers,
5
+ * with built-in analyzers for configuration validation and dependency analysis.
6
+ */
7
+
8
+ import type {Analyzer, AnalyzerRegistration} from './analyzer'
9
+
10
+ import {createArchitecturalAnalyzer} from './architectural-analyzer'
11
+ import {createBuildConfigAnalyzer} from './build-config-analyzer'
12
+ import {createCircularImportAnalyzer} from './circular-import-analyzer'
13
+ import {createConfigConsistencyAnalyzer} from './config-consistency-analyzer'
14
+ import {createDeadCodeAnalyzer} from './dead-code-analyzer'
15
+ import {createDuplicateCodeAnalyzer} from './duplicate-code-analyzer'
16
+ import {createDuplicateDependencyAnalyzer} from './duplicate-dependency-analyzer'
17
+ import {createEslintConfigAnalyzer} from './eslint-config-analyzer'
18
+ import {createExportsFieldAnalyzer} from './exports-field-analyzer'
19
+ import {createLargeDependencyAnalyzer} from './large-dependency-analyzer'
20
+ import {createPackageJsonAnalyzer} from './package-json-analyzer'
21
+ import {createPeerDependencyAnalyzer} from './peer-dependency-analyzer'
22
+ import {createTreeShakingBlockerAnalyzer} from './tree-shaking-analyzer'
23
+ import {createTsconfigAnalyzer} from './tsconfig-analyzer'
24
+ import {createUnusedDependencyAnalyzer} from './unused-dependency-analyzer'
25
+ import {createVersionAlignmentAnalyzer} from './version-alignment-analyzer'
26
+
27
+ export type {
28
+ AnalysisContext,
29
+ Analyzer,
30
+ AnalyzerError,
31
+ AnalyzerFactory,
32
+ AnalyzerMetadata,
33
+ AnalyzerOptions,
34
+ AnalyzerRegistration,
35
+ } from './analyzer'
36
+ export {createIssue, filterIssues, meetsMinSeverity, shouldAnalyzeCategory} from './analyzer'
37
+
38
+ export type {ArchitecturalAnalyzerOptions} from './architectural-analyzer'
39
+ export {architecturalAnalyzerMetadata, createArchitecturalAnalyzer} from './architectural-analyzer'
40
+
41
+ export type {BuildConfigAnalyzerOptions} from './build-config-analyzer'
42
+ export {buildConfigAnalyzerMetadata, createBuildConfigAnalyzer} from './build-config-analyzer'
43
+
44
+ // Dependency analysis exports
45
+ export type {CircularImportAnalyzerOptions} from './circular-import-analyzer'
46
+ export {
47
+ circularImportAnalyzerMetadata,
48
+ computeCycleStats,
49
+ createCircularImportAnalyzer,
50
+ generateCycleVisualization,
51
+ } from './circular-import-analyzer'
52
+
53
+ export type {
54
+ CircularImportStats,
55
+ CycleEdge,
56
+ CycleNode,
57
+ CycleVisualization,
58
+ } from './circular-import-analyzer'
59
+ export type {ConfigConsistencyAnalyzerOptions} from './config-consistency-analyzer'
60
+
61
+ export {
62
+ configConsistencyAnalyzerMetadata,
63
+ createConfigConsistencyAnalyzer,
64
+ } from './config-consistency-analyzer'
65
+
66
+ // Performance analysis exports (Phase 6)
67
+ export type {DeadCodeAnalyzerOptions, DeadCodeStats, ExportedSymbol} from './dead-code-analyzer'
68
+ export {
69
+ computeDeadCodeStats,
70
+ createDeadCodeAnalyzer,
71
+ deadCodeAnalyzerMetadata,
72
+ } from './dead-code-analyzer'
73
+
74
+ export type {
75
+ CodeFingerprint,
76
+ DuplicateCodeAnalyzerOptions,
77
+ DuplicatePattern,
78
+ } from './duplicate-code-analyzer'
79
+ export {createDuplicateCodeAnalyzer, duplicateCodeAnalyzerMetadata} from './duplicate-code-analyzer'
80
+
81
+ export type {DuplicateDependencyAnalyzerOptions} from './duplicate-dependency-analyzer'
82
+
83
+ export {
84
+ computeDuplicateStats,
85
+ createDuplicateDependencyAnalyzer,
86
+ duplicateDependencyAnalyzerMetadata,
87
+ } from './duplicate-dependency-analyzer'
88
+ export type {DuplicateDependencyStats} from './duplicate-dependency-analyzer'
89
+
90
+ export type {EslintConfigAnalyzerOptions} from './eslint-config-analyzer'
91
+ export {createEslintConfigAnalyzer, eslintConfigAnalyzerMetadata} from './eslint-config-analyzer'
92
+
93
+ export type {ExportsFieldAnalyzerOptions} from './exports-field-analyzer'
94
+ export {createExportsFieldAnalyzer, exportsFieldAnalyzerMetadata} from './exports-field-analyzer'
95
+
96
+ export type {LargeDependencyAnalyzerOptions} from './large-dependency-analyzer'
97
+ export {
98
+ createLargeDependencyAnalyzer,
99
+ getKnownLargePackages,
100
+ getPackageInfo,
101
+ largeDependencyAnalyzerMetadata,
102
+ } from './large-dependency-analyzer'
103
+
104
+ export type {PackageJsonAnalyzerOptions} from './package-json-analyzer'
105
+ export {createPackageJsonAnalyzer, packageJsonAnalyzerMetadata} from './package-json-analyzer'
106
+ export type {PeerDependencyAnalyzerOptions} from './peer-dependency-analyzer'
107
+
108
+ export {
109
+ createPeerDependencyAnalyzer,
110
+ peerDependencyAnalyzerMetadata,
111
+ } from './peer-dependency-analyzer'
112
+
113
+ export type {
114
+ TreeShakingBlocker,
115
+ TreeShakingBlockerAnalyzerOptions,
116
+ TreeShakingBlockerType,
117
+ } from './tree-shaking-analyzer'
118
+ export {
119
+ createTreeShakingBlockerAnalyzer,
120
+ treeShakingBlockerAnalyzerMetadata,
121
+ } from './tree-shaking-analyzer'
122
+
123
+ export type {TsconfigAnalyzerOptions} from './tsconfig-analyzer'
124
+
125
+ export {createTsconfigAnalyzer, tsconfigAnalyzerMetadata} from './tsconfig-analyzer'
126
+ export type {UnusedDependencyAnalyzerOptions} from './unused-dependency-analyzer'
127
+
128
+ export {
129
+ aggregatePackageImports,
130
+ createUnusedDependencyAnalyzer,
131
+ unusedDependencyAnalyzerMetadata,
132
+ } from './unused-dependency-analyzer'
133
+ export type {VersionAlignmentAnalyzerOptions} from './version-alignment-analyzer'
134
+ export {
135
+ createVersionAlignmentAnalyzer,
136
+ versionAlignmentAnalyzerMetadata,
137
+ } from './version-alignment-analyzer'
138
+
139
+ /**
140
+ * Registry for managing analyzer registrations.
141
+ */
142
+ export interface AnalyzerRegistry {
143
+ /** Register an analyzer */
144
+ readonly register: (id: string, registration: AnalyzerRegistration) => void
145
+ /** Unregister an analyzer */
146
+ readonly unregister: (id: string) => boolean
147
+ /** Get a registered analyzer */
148
+ readonly get: (id: string) => AnalyzerRegistration | undefined
149
+ /** Get all registered analyzers */
150
+ readonly getAll: () => Map<string, AnalyzerRegistration>
151
+ /** Get enabled analyzers sorted by priority */
152
+ readonly getEnabled: () => Analyzer[]
153
+ /** Check if an analyzer is registered */
154
+ readonly has: (id: string) => boolean
155
+ }
156
+
157
+ /**
158
+ * Creates a new analyzer registry.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * const registry = createAnalyzerRegistry()
163
+ *
164
+ * // Register a custom analyzer
165
+ * registry.register('my-analyzer', {
166
+ * analyzer: createMyAnalyzer(),
167
+ * enabled: true,
168
+ * priority: 50,
169
+ * })
170
+ *
171
+ * // Get all enabled analyzers
172
+ * const analyzers = registry.getEnabled()
173
+ * ```
174
+ */
175
+ export function createAnalyzerRegistry(): AnalyzerRegistry {
176
+ const registrations = new Map<string, AnalyzerRegistration>()
177
+
178
+ function resolveAnalyzer(registration: AnalyzerRegistration): Analyzer {
179
+ if (typeof registration.analyzer === 'function') {
180
+ return registration.analyzer()
181
+ }
182
+ return registration.analyzer
183
+ }
184
+
185
+ return {
186
+ register(id: string, registration: AnalyzerRegistration): void {
187
+ registrations.set(id, registration)
188
+ },
189
+
190
+ unregister(id: string): boolean {
191
+ return registrations.delete(id)
192
+ },
193
+
194
+ get(id: string): AnalyzerRegistration | undefined {
195
+ return registrations.get(id)
196
+ },
197
+
198
+ getAll(): Map<string, AnalyzerRegistration> {
199
+ return new Map(registrations)
200
+ },
201
+
202
+ getEnabled(): Analyzer[] {
203
+ const enabled = Array.from(registrations.entries())
204
+ .filter(([_id, reg]) => reg.enabled)
205
+ .sort((a, b) => a[1].priority - b[1].priority)
206
+ .map(([_id, reg]) => resolveAnalyzer(reg))
207
+
208
+ return enabled
209
+ },
210
+
211
+ has(id: string): boolean {
212
+ return registrations.has(id)
213
+ },
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Built-in analyzer IDs.
219
+ */
220
+ export const BUILTIN_ANALYZER_IDS = {
221
+ PACKAGE_JSON: 'package-json',
222
+ TSCONFIG: 'tsconfig',
223
+ ESLINT_CONFIG: 'eslint-config',
224
+ BUILD_CONFIG: 'build-config',
225
+ CONFIG_CONSISTENCY: 'config-consistency',
226
+ VERSION_ALIGNMENT: 'version-alignment',
227
+ EXPORTS_FIELD: 'exports-field',
228
+ // Dependency analyzers
229
+ UNUSED_DEPENDENCY: 'unused-dependency',
230
+ CIRCULAR_IMPORT: 'circular-import',
231
+ PEER_DEPENDENCY: 'peer-dependency',
232
+ DUPLICATE_DEPENDENCY: 'duplicate-dependency',
233
+ // Architectural analyzer
234
+ ARCHITECTURAL: 'architectural',
235
+ // Performance analyzers (Phase 6)
236
+ DEAD_CODE: 'dead-code',
237
+ DUPLICATE_CODE: 'duplicate-code',
238
+ LARGE_DEPENDENCY: 'large-dependency',
239
+ TREE_SHAKING_BLOCKER: 'tree-shaking-blocker',
240
+ } as const
241
+
242
+ /**
243
+ * Creates a registry with all built-in analyzers registered.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * const registry = createDefaultRegistry()
248
+ *
249
+ * // Disable a specific analyzer
250
+ * const registration = registry.get('eslint-config')
251
+ * if (registration) {
252
+ * registry.register('eslint-config', {...registration, enabled: false})
253
+ * }
254
+ *
255
+ * // Run enabled analyzers
256
+ * const analyzers = registry.getEnabled()
257
+ * ```
258
+ */
259
+ export function createDefaultRegistry(): AnalyzerRegistry {
260
+ const registry = createAnalyzerRegistry()
261
+
262
+ // Register built-in analyzers with default priorities
263
+ registry.register(BUILTIN_ANALYZER_IDS.PACKAGE_JSON, {
264
+ analyzer: createPackageJsonAnalyzer(),
265
+ enabled: true,
266
+ priority: 10,
267
+ })
268
+
269
+ registry.register(BUILTIN_ANALYZER_IDS.TSCONFIG, {
270
+ analyzer: createTsconfigAnalyzer(),
271
+ enabled: true,
272
+ priority: 20,
273
+ })
274
+
275
+ registry.register(BUILTIN_ANALYZER_IDS.ESLINT_CONFIG, {
276
+ analyzer: createEslintConfigAnalyzer(),
277
+ enabled: true,
278
+ priority: 30,
279
+ })
280
+
281
+ registry.register(BUILTIN_ANALYZER_IDS.BUILD_CONFIG, {
282
+ analyzer: createBuildConfigAnalyzer(),
283
+ enabled: true,
284
+ priority: 40,
285
+ })
286
+
287
+ registry.register(BUILTIN_ANALYZER_IDS.CONFIG_CONSISTENCY, {
288
+ analyzer: createConfigConsistencyAnalyzer(),
289
+ enabled: true,
290
+ priority: 50,
291
+ })
292
+
293
+ registry.register(BUILTIN_ANALYZER_IDS.VERSION_ALIGNMENT, {
294
+ analyzer: createVersionAlignmentAnalyzer(),
295
+ enabled: true,
296
+ priority: 60,
297
+ })
298
+
299
+ registry.register(BUILTIN_ANALYZER_IDS.EXPORTS_FIELD, {
300
+ analyzer: createExportsFieldAnalyzer(),
301
+ enabled: true,
302
+ priority: 70,
303
+ })
304
+
305
+ // Dependency analyzers
306
+ registry.register(BUILTIN_ANALYZER_IDS.UNUSED_DEPENDENCY, {
307
+ analyzer: createUnusedDependencyAnalyzer(),
308
+ enabled: true,
309
+ priority: 80,
310
+ })
311
+
312
+ registry.register(BUILTIN_ANALYZER_IDS.CIRCULAR_IMPORT, {
313
+ analyzer: createCircularImportAnalyzer(),
314
+ enabled: true,
315
+ priority: 90,
316
+ })
317
+
318
+ registry.register(BUILTIN_ANALYZER_IDS.PEER_DEPENDENCY, {
319
+ analyzer: createPeerDependencyAnalyzer(),
320
+ enabled: true,
321
+ priority: 100,
322
+ })
323
+
324
+ registry.register(BUILTIN_ANALYZER_IDS.DUPLICATE_DEPENDENCY, {
325
+ analyzer: createDuplicateDependencyAnalyzer(),
326
+ enabled: true,
327
+ priority: 110,
328
+ })
329
+
330
+ // Architectural analyzer
331
+ registry.register(BUILTIN_ANALYZER_IDS.ARCHITECTURAL, {
332
+ analyzer: createArchitecturalAnalyzer(),
333
+ enabled: true,
334
+ priority: 120,
335
+ })
336
+
337
+ // Performance analyzers (Phase 6)
338
+ registry.register(BUILTIN_ANALYZER_IDS.DEAD_CODE, {
339
+ analyzer: createDeadCodeAnalyzer(),
340
+ enabled: true,
341
+ priority: 130,
342
+ })
343
+
344
+ registry.register(BUILTIN_ANALYZER_IDS.DUPLICATE_CODE, {
345
+ analyzer: createDuplicateCodeAnalyzer(),
346
+ enabled: true,
347
+ priority: 140,
348
+ })
349
+
350
+ registry.register(BUILTIN_ANALYZER_IDS.LARGE_DEPENDENCY, {
351
+ analyzer: createLargeDependencyAnalyzer(),
352
+ enabled: true,
353
+ priority: 150,
354
+ })
355
+
356
+ registry.register(BUILTIN_ANALYZER_IDS.TREE_SHAKING_BLOCKER, {
357
+ analyzer: createTreeShakingBlockerAnalyzer(),
358
+ enabled: true,
359
+ priority: 160,
360
+ })
361
+
362
+ return registry
363
+ }
364
+
365
+ /**
366
+ * All built-in analyzer factories for direct use.
367
+ */
368
+ export const builtinAnalyzers = {
369
+ packageJson: createPackageJsonAnalyzer,
370
+ tsconfig: createTsconfigAnalyzer,
371
+ eslintConfig: createEslintConfigAnalyzer,
372
+ buildConfig: createBuildConfigAnalyzer,
373
+ configConsistency: createConfigConsistencyAnalyzer,
374
+ versionAlignment: createVersionAlignmentAnalyzer,
375
+ exportsField: createExportsFieldAnalyzer,
376
+ // Dependency analyzers
377
+ unusedDependency: createUnusedDependencyAnalyzer,
378
+ circularImport: createCircularImportAnalyzer,
379
+ peerDependency: createPeerDependencyAnalyzer,
380
+ duplicateDependency: createDuplicateDependencyAnalyzer,
381
+ // Architectural analyzer
382
+ architectural: createArchitecturalAnalyzer,
383
+ // Performance analyzers (Phase 6)
384
+ deadCode: createDeadCodeAnalyzer,
385
+ duplicateCode: createDuplicateCodeAnalyzer,
386
+ largeDependency: createLargeDependencyAnalyzer,
387
+ treeShakingBlocker: createTreeShakingBlockerAnalyzer,
388
+ } as const