@archora/core 1.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 (112) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +62 -0
  3. package/package.json +36 -0
  4. package/src/README.md +4 -0
  5. package/src/analyzer/__tests__/__snapshots__/referenceSnapshot.test.ts.snap +145 -0
  6. package/src/analyzer/__tests__/_paths.ts +8 -0
  7. package/src/analyzer/__tests__/analyze.test.ts +522 -0
  8. package/src/analyzer/__tests__/archDebt.test.ts +111 -0
  9. package/src/analyzer/__tests__/asyncLifecycleRisk.test.ts +122 -0
  10. package/src/analyzer/__tests__/browserFsAccessFileSource.test.ts +97 -0
  11. package/src/analyzer/__tests__/bundle.test.ts +191 -0
  12. package/src/analyzer/__tests__/classify.test.ts +99 -0
  13. package/src/analyzer/__tests__/contracts.test.ts +372 -0
  14. package/src/analyzer/__tests__/crossSourceConsistency.test.ts +317 -0
  15. package/src/analyzer/__tests__/cyclePatterns.test.ts +132 -0
  16. package/src/analyzer/__tests__/cycles.test.ts +74 -0
  17. package/src/analyzer/__tests__/detect.test.ts +62 -0
  18. package/src/analyzer/__tests__/discover.test.ts +68 -0
  19. package/src/analyzer/__tests__/displayId.test.ts +30 -0
  20. package/src/analyzer/__tests__/feedbackArcSet.test.ts +168 -0
  21. package/src/analyzer/__tests__/inMemoryFileSource.test.ts +34 -0
  22. package/src/analyzer/__tests__/incremental.test.ts +154 -0
  23. package/src/analyzer/__tests__/layers.test.ts +87 -0
  24. package/src/analyzer/__tests__/layersOverrides.test.ts +120 -0
  25. package/src/analyzer/__tests__/memoryRisk.test.ts +132 -0
  26. package/src/analyzer/__tests__/metrics.test.ts +59 -0
  27. package/src/analyzer/__tests__/parserRegistry.test.ts +54 -0
  28. package/src/analyzer/__tests__/parsers.test.ts +187 -0
  29. package/src/analyzer/__tests__/reactParser.test.ts +93 -0
  30. package/src/analyzer/__tests__/recommendations.test.ts +171 -0
  31. package/src/analyzer/__tests__/referenceSnapshot.test.ts +63 -0
  32. package/src/analyzer/__tests__/resolve.test.ts +294 -0
  33. package/src/analyzer/__tests__/rsc.test.ts +130 -0
  34. package/src/analyzer/__tests__/signals.test.ts +316 -0
  35. package/src/analyzer/__tests__/suggestContracts.test.ts +108 -0
  36. package/src/analyzer/__tests__/svelteParser.test.ts +108 -0
  37. package/src/analyzer/__tests__/typeOnlyCandidates.test.ts +163 -0
  38. package/src/analyzer/__tests__/vueAutoImport.test.ts +177 -0
  39. package/src/analyzer/archDebt.ts +68 -0
  40. package/src/analyzer/asyncLifecycleRisk.ts +234 -0
  41. package/src/analyzer/buildGraph.ts +683 -0
  42. package/src/analyzer/bundle/analyzeBundle.ts +147 -0
  43. package/src/analyzer/bundle/index.ts +12 -0
  44. package/src/analyzer/bundle/parseStats.ts +152 -0
  45. package/src/analyzer/bundle/types.ts +85 -0
  46. package/src/analyzer/classify.ts +54 -0
  47. package/src/analyzer/contracts.ts +265 -0
  48. package/src/analyzer/cyclePatterns.ts +138 -0
  49. package/src/analyzer/cycles.ts +98 -0
  50. package/src/analyzer/detect.ts +34 -0
  51. package/src/analyzer/discover.ts +131 -0
  52. package/src/analyzer/displayId.ts +21 -0
  53. package/src/analyzer/entryPoints.ts +136 -0
  54. package/src/analyzer/feedbackArcSet.ts +332 -0
  55. package/src/analyzer/fileSource.ts +8 -0
  56. package/src/analyzer/hotZones.ts +17 -0
  57. package/src/analyzer/incremental.ts +455 -0
  58. package/src/analyzer/index.ts +444 -0
  59. package/src/analyzer/layers.ts +183 -0
  60. package/src/analyzer/loadAliases.ts +288 -0
  61. package/src/analyzer/memoryRisk.ts +345 -0
  62. package/src/analyzer/metrics.ts +156 -0
  63. package/src/analyzer/parsers/index.ts +62 -0
  64. package/src/analyzer/parsers/reactParser.ts +24 -0
  65. package/src/analyzer/parsers/svelteParser.ts +46 -0
  66. package/src/analyzer/parsers/tsParser.ts +364 -0
  67. package/src/analyzer/parsers/vueParser.ts +109 -0
  68. package/src/analyzer/recommendations.ts +432 -0
  69. package/src/analyzer/resolve.ts +315 -0
  70. package/src/analyzer/rsc.ts +120 -0
  71. package/src/analyzer/signals.ts +684 -0
  72. package/src/analyzer/sources/browserFsAccessFileSource.ts +132 -0
  73. package/src/analyzer/sources/inMemoryFileSource.ts +24 -0
  74. package/src/analyzer/sources/nodeFsFileSource.ts +93 -0
  75. package/src/analyzer/sources/tauriFileSource.ts +68 -0
  76. package/src/analyzer/suggestContracts.ts +214 -0
  77. package/src/analyzer/typeOnlyCandidates.ts +233 -0
  78. package/src/analyzer/types.ts +537 -0
  79. package/src/cache/__tests__/cache.test.ts +316 -0
  80. package/src/cache/index.ts +432 -0
  81. package/src/codegen/__tests__/applyTypeOnlyFix.integration.test.ts +62 -0
  82. package/src/codegen/__tests__/applyTypeOnlyFix.test.ts +176 -0
  83. package/src/codegen/__tests__/configSnippets.test.ts +230 -0
  84. package/src/codegen/applyTypeOnlyFix.ts +344 -0
  85. package/src/codegen/configSnippets.ts +172 -0
  86. package/src/codegen/initConfig.ts +223 -0
  87. package/src/config/__tests__/frontScopeConfig.test.ts +187 -0
  88. package/src/config/frontScopeConfig.ts +830 -0
  89. package/src/diff/__tests__/diffScans.test.ts +103 -0
  90. package/src/diff/diffScans.ts +61 -0
  91. package/src/diff/index.ts +2 -0
  92. package/src/diff/types.ts +39 -0
  93. package/src/git/__tests__/computeChurn.test.ts +113 -0
  94. package/src/git/__tests__/computeTemporalCoupling.test.ts +125 -0
  95. package/src/git/__tests__/parseGitLog.test.ts +120 -0
  96. package/src/git/computeChurn.ts +111 -0
  97. package/src/git/computeTemporalCoupling.ts +114 -0
  98. package/src/git/index.ts +24 -0
  99. package/src/git/parseGitLog.ts +124 -0
  100. package/src/git/readGitHistory.ts +130 -0
  101. package/src/git/types.ts +119 -0
  102. package/src/index.ts +137 -0
  103. package/src/report/__tests__/buildFixPlan.test.ts +357 -0
  104. package/src/report/__tests__/buildJsonReport.test.ts +34 -0
  105. package/src/report/buildFixPlan.ts +481 -0
  106. package/src/report/buildJsonReport.ts +27 -0
  107. package/src/search/__tests__/parseQuery.test.ts +67 -0
  108. package/src/search/__tests__/search.test.ts +172 -0
  109. package/src/search/index.ts +281 -0
  110. package/src/search/parseQuery.ts +75 -0
  111. package/src/views/__tests__/analyzerViews.test.ts +558 -0
  112. package/src/views/analyzerViews.ts +1294 -0
@@ -0,0 +1,537 @@
1
+ export type ModuleId = string;
2
+
3
+ export type ModuleKind =
4
+ | 'api'
5
+ | 'component'
6
+ | 'composable'
7
+ | 'config'
8
+ | 'store'
9
+ | 'route'
10
+ | 'model'
11
+ | 'module'
12
+ | 'schema'
13
+ | 'service'
14
+ | 'style'
15
+ | 'test'
16
+ | 'util'
17
+ | 'entry'
18
+ | 'integration'
19
+ /** @deprecated kept for old scan JSON compatibility; new scans use `module`. */
20
+ | 'unknown';
21
+
22
+ export type ModuleLanguage = 'ts' | 'js' | 'vue' | 'svelte';
23
+
24
+ export type EdgeKind = 'static' | 'dynamic' | 'type-only' | 'side-effect' | 'auto-import';
25
+ export type FactConfidence = 'high' | 'medium' | 'low';
26
+ export type ImportResolutionKind =
27
+ | 'literal'
28
+ | 'dynamic-literal'
29
+ | 'glob'
30
+ | 'prefix'
31
+ | 'framework-auto'
32
+ | 'asset'
33
+ | 'external';
34
+
35
+ export type DetectedFramework =
36
+ | 'vue'
37
+ | 'react'
38
+ | 'svelte'
39
+ | 'nuxt'
40
+ | 'next'
41
+ | 'generic'
42
+ /** @deprecated kept for old scan JSON compatibility; new scans use `generic`. */
43
+ | 'unknown';
44
+
45
+ export interface ProjectRef {
46
+ id: string;
47
+ name: string;
48
+ rootPath: string;
49
+ detectedFramework: DetectedFramework;
50
+ tsconfigPath?: string;
51
+ }
52
+
53
+ export interface ModuleNode {
54
+ id: ModuleId;
55
+ absPath: string;
56
+ kind: ModuleKind;
57
+ language: ModuleLanguage;
58
+ loc: number;
59
+ exports: string[];
60
+ isInfra: boolean;
61
+ /**
62
+ * Server/client/shared classification (RSC).
63
+ * Inferred from `'use server'` / `'use client'` directives plus framework
64
+ * conventions (Next app/, Nuxt server/, SvelteKit `+server.ts`/`*.server.ts`).
65
+ * Defaults to `'shared'` when nothing pins it down.
66
+ */
67
+ runtime?: ModuleRuntime;
68
+ isGenerated?: boolean;
69
+ }
70
+
71
+ export type ModuleRuntime = 'server' | 'client' | 'shared';
72
+
73
+ export interface DependencyEdge {
74
+ from: ModuleId;
75
+ to: ModuleId;
76
+ kind: EdgeKind;
77
+ specifier: string;
78
+ resolved: boolean;
79
+ confidence?: FactConfidence;
80
+ resolutionKind?: ImportResolutionKind;
81
+ approximate?: boolean;
82
+ }
83
+
84
+ export interface CycleBreakpoint {
85
+ from: ModuleId;
86
+ to: ModuleId;
87
+ }
88
+
89
+ export interface Cycle {
90
+ // `cycle:<hex8>` content-addressed over the sorted member list. Stable
91
+ // across module insertion order; safe as a dictionary key and baseline
92
+ // marker.
93
+ id: string;
94
+ modules: ModuleId[];
95
+ length: number;
96
+ severity: 'direct' | 'indirect';
97
+ // Edge picked from the feedback arc set as the cheapest break point.
98
+ // Absent for SCCs with no recoverable internal edges.
99
+ suggestedBreakpoint?: CycleBreakpoint;
100
+ }
101
+
102
+ export interface ModuleMetrics {
103
+ fanIn: number;
104
+ fanOut: number;
105
+ instability: number;
106
+ depth: number;
107
+ inCycle: boolean;
108
+ couplingScore: number;
109
+ hotnessScore: number;
110
+ }
111
+
112
+ export interface AnalyzerWarning {
113
+ code:
114
+ | 'parse-failed'
115
+ | 'resolve-failed'
116
+ | 'self-import'
117
+ | 'tsconfig-missing'
118
+ | 'unsupported-extension';
119
+ message: string;
120
+ file?: string;
121
+ detail?: string;
122
+ }
123
+
124
+ // Forward import-as-type so `ScanResult` below can use `ContractViolation`.
125
+ // We re-export at the bottom so consumers can `import type { ContractViolation } from '@archora/core'`.
126
+ import type { ContractViolation as _ContractViolation } from './contracts';
127
+ import type { BundleReport as _BundleReport } from './bundle/types';
128
+ import type { ConfigDiagnostic as _ConfigDiagnostic } from '../config/frontScopeConfig';
129
+ import type {
130
+ ChurnByModule as _ChurnByModule,
131
+ GitHistory as _GitHistory,
132
+ TemporalCoupling as _TemporalCoupling,
133
+ } from '../git/types';
134
+
135
+ export interface ScanResult {
136
+ project: ProjectRef;
137
+ modules: ModuleNode[];
138
+ edges: DependencyEdge[];
139
+ cycles: Cycle[];
140
+ metrics: Record<ModuleId, ModuleMetrics>;
141
+ hotZones: ModuleId[];
142
+ layerViolations: LayerViolation[];
143
+ archDebt: ArchDebt;
144
+ recommendations: Recommendation[];
145
+ /**
146
+ * Trust layer. Signals are evidence-backed compatibility output
147
+ * derived from analyzer facts and legacy recommendations. The legacy
148
+ * `recommendations` array remains the UI/CLI compatibility projection.
149
+ */
150
+ signals?: ArchitectureSignal[];
151
+ insights?: ArchitectureInsight[];
152
+ /**
153
+ * Parser fact summaries. This is an additive compatibility layer:
154
+ * legacy ParsedFile/RawImport still drive graph construction, while these
155
+ * facts expose confidence/limitations for future signals.
156
+ */
157
+ parserFacts?: ParsedFileSummary[];
158
+ /**
159
+ * Violations of user-declared architectural contracts (boundaries / budgets
160
+ * / api-stability). Empty when no `contracts` block is defined in
161
+ * `.archora.json`.
162
+ */
163
+ contractViolations: _ContractViolation[];
164
+ /**
165
+ * First-class `.archora.json` setup diagnostics. Invalid config is still
166
+ * soft-failed into defaults, but callers can show the dropped fields and
167
+ * parse errors as fixable setup issues.
168
+ */
169
+ configDiagnostics?: _ConfigDiagnostic[];
170
+ /**
171
+ * Human-facing rules config state. `not-configured` is not an error: it means
172
+ * the scan used built-in defaults and no project rules file was found.
173
+ */
174
+ configStatus?: {
175
+ state: 'not-configured' | 'loaded' | 'invalid';
176
+ file: string | null;
177
+ };
178
+ /**
179
+ * Bundle-aware analysis. Populated only when the caller supplied
180
+ * a parsed bundler stats payload via `AnalyzeOptions.bundleStats`.
181
+ */
182
+ bundle?: _BundleReport;
183
+ /**
184
+ * Per-module churn. Populated only when the caller supplied
185
+ * `AnalyzeOptions.gitHistory`. Modules with zero touches in the history
186
+ * window are absent from the map.
187
+ */
188
+ churn?: _ChurnByModule;
189
+ /**
190
+ * The git history that was used to compute `churn`. Echoed back so
191
+ * consumers (UI, reports) can show window/commit-count metadata without
192
+ * re-running git.
193
+ */
194
+ gitHistory?: _GitHistory;
195
+ /**
196
+ * Top temporally coupled module pairs. Sorted: hidden
197
+ * couplings first, then by score desc. Capped at `maxPairs` (default 100).
198
+ */
199
+ temporalCoupling?: _TemporalCoupling[];
200
+ /**
201
+ * Static browser memory-risk heuristics. These findings identify cleanup
202
+ * patterns worth reviewing; they are not runtime leak proof.
203
+ */
204
+ memoryRisks?: MemoryRiskFinding[];
205
+ /**
206
+ * Static async lifecycle heuristics. These findings identify async work
207
+ * that can outlive component lifecycle without an abort or stale guard.
208
+ */
209
+ asyncLifecycleRisks?: AsyncLifecycleRiskFinding[];
210
+ scannedAt: string;
211
+ durationMs: number;
212
+ warnings: AnalyzerWarning[];
213
+ }
214
+
215
+ /**
216
+ * Re-exported here (rather than imported from `analyzer/contracts.ts`) so
217
+ * downstream packages don't need a sub-path import - keeps `ScanResult`
218
+ * self-contained.
219
+ */
220
+ export type { ContractViolation } from './contracts';
221
+ export type { ConfigDiagnostic, ConfigDiagnosticSeverity } from '../config/frontScopeConfig';
222
+ export type {
223
+ ChurnAuthor,
224
+ ChurnByModule,
225
+ ChurnMetric,
226
+ GitCommit,
227
+ GitFileChange,
228
+ GitHistory,
229
+ TemporalCoupling,
230
+ TemporalCouplingThresholds,
231
+ } from '../git/types';
232
+ export type {
233
+ BundleBloat,
234
+ BundleBloatKind,
235
+ BundleChunk,
236
+ BundleChunkModule,
237
+ BundleReport,
238
+ BundleThresholds,
239
+ ParsedBundleStats,
240
+ } from './bundle/types';
241
+
242
+ export interface LayerViolation {
243
+ edgeId: string;
244
+ from: ModuleId;
245
+ to: ModuleId;
246
+ fromLayer: string;
247
+ toLayer: string;
248
+ severity: 'error' | 'warning';
249
+ }
250
+
251
+ export interface Recommendation {
252
+ id: string;
253
+ kind:
254
+ | 'split-god-module'
255
+ | 'unused-utility'
256
+ /** @deprecated use `cycle-break-cluster`. Kept for snapshot compatibility. */
257
+ | 'cycle-break-candidate'
258
+ | 'cycle-break-cluster'
259
+ | 'type-only-candidate'
260
+ | 'misplaced-by-layer'
261
+ | 'isolated-cluster'
262
+ | 'contract-violation'
263
+ | 'bundle-bloat'
264
+ | 'temporal-coupling';
265
+ modules: ModuleId[];
266
+ /**
267
+ * Per-kind structured payload. Primitive values flow into the i18n template
268
+ * picked by `kind`; complex values (e.g. the feedback-edge list for
269
+ * `cycle-break-cluster`) are rendered explicitly by the UI. Storing
270
+ * structured data here keeps the recommendation localization-agnostic.
271
+ */
272
+ params: Record<string, string | number | readonly CycleFeedbackEdge[]>;
273
+ weight: number;
274
+ }
275
+
276
+ export interface CycleFeedbackEdge {
277
+ from: ModuleId;
278
+ to: ModuleId;
279
+ /** Number of elementary cycles broken by removing this edge. */
280
+ broken: number;
281
+ /** True when the count is an estimate (large SCC, cap reached). */
282
+ partial: boolean;
283
+ }
284
+
285
+ export interface ArchDebt {
286
+ /** 0..100, higher = worse. Composite of cycles, layer violations, hot-zone density and avg coupling. */
287
+ score: number;
288
+ grade: 'A' | 'B' | 'C' | 'D' | 'F';
289
+ breakdown: {
290
+ cycles: number;
291
+ layerViolations: number;
292
+ hotZones: number;
293
+ coupling: number;
294
+ };
295
+ }
296
+
297
+ export interface RawImport {
298
+ specifier: string;
299
+ kind: EdgeKind;
300
+ confidence?: FactConfidence;
301
+ resolutionKind?: ImportResolutionKind;
302
+ approximate?: boolean;
303
+ /**
304
+ * Match strategy for the specifier when the graph builder resolves it.
305
+ *
306
+ * - `literal` (default): exact specifier, resolves to a single module.
307
+ * - `prefix`: the specifier is the static prefix of a dynamic path (e.g. the
308
+ * head of a template literal `` `./mfes/${name}/index` `` is `./mfes/`).
309
+ * The graph builder treats it as a directory and creates one edge per
310
+ * matching file inside. Used to recover MFE-style dynamic loaders that
311
+ * would otherwise produce false-positive isolated-cluster insights.
312
+ * - `glob`: the specifier is a Vite-style `import.meta.glob(...)` pattern,
313
+ * resolved against the project's file list to one edge per match.
314
+ * Multiple patterns are emitted as multiple `RawImport` entries.
315
+ */
316
+ pattern?: 'literal' | 'prefix' | 'glob';
317
+ globEager?: boolean;
318
+ globImport?: string;
319
+ }
320
+
321
+ export interface ImportFact {
322
+ specifier: string;
323
+ kind: EdgeKind;
324
+ resolutionKind: ImportResolutionKind;
325
+ confidence: FactConfidence;
326
+ approximate: boolean;
327
+ isAsset?: boolean;
328
+ globEager?: boolean;
329
+ globImport?: string;
330
+ negative?: boolean;
331
+ }
332
+
333
+ export interface ExportFact {
334
+ name: string;
335
+ kind: 'value' | 'type' | 'default' | 'namespace' | 'named' | 'unknown';
336
+ source?: string;
337
+ confidence: FactConfidence;
338
+ }
339
+
340
+ export interface RuntimeFact {
341
+ runtime: ModuleRuntime;
342
+ source: 'directive' | 'framework-convention' | 'default';
343
+ confidence: FactConfidence;
344
+ }
345
+
346
+ export interface FrameworkFact {
347
+ kind:
348
+ | 'vue-template-ref'
349
+ | 'react-lazy'
350
+ | 'next-dynamic'
351
+ | 'nuxt-auto-component'
352
+ | 'nuxt-auto-composable'
353
+ | 'sveltekit-route'
354
+ | 'unknown';
355
+ value: string;
356
+ confidence: FactConfidence;
357
+ }
358
+
359
+ export interface RouteFact {
360
+ routeKind: 'page' | 'layout' | 'api' | 'middleware' | 'server-route';
361
+ confidence: FactConfidence;
362
+ }
363
+
364
+ export interface StateFact {
365
+ kind: 'pinia-store' | 'unknown';
366
+ confidence: FactConfidence;
367
+ }
368
+
369
+ export interface AssetFact {
370
+ specifier: string;
371
+ assetKind: 'style' | 'json' | 'image' | 'font' | 'other';
372
+ confidence: FactConfidence;
373
+ }
374
+
375
+ export interface ParsedFileSummary {
376
+ relPath: string;
377
+ language: ModuleLanguage;
378
+ loc: number;
379
+ imports: ImportFact[];
380
+ exports: ExportFact[];
381
+ runtimeFacts: RuntimeFact[];
382
+ frameworkFacts: FrameworkFact[];
383
+ routeFacts: RouteFact[];
384
+ stateFacts: StateFact[];
385
+ assetFacts: AssetFact[];
386
+ limitations: string[];
387
+ }
388
+
389
+ export interface ParsedFile {
390
+ relPath: string;
391
+ language: ModuleLanguage;
392
+ loc: number;
393
+ exports: string[];
394
+ imports: RawImport[];
395
+ summary?: ParsedFileSummary;
396
+ hasDefineStore: boolean;
397
+ /**
398
+ * Top-of-file directive prologue (`'use server'` / `'use client'`).
399
+ * Empty when none. Order-preserving for first-wins resolution.
400
+ */
401
+ directives?: ('use server' | 'use client')[];
402
+ /** PascalCase component tags from <template>, resolved against the component registry in buildGraph. */
403
+ templateRefs?: string[];
404
+ }
405
+
406
+ export type SignalSeverity = 'info' | 'low' | 'medium' | 'high' | 'critical';
407
+ export type SignalConfidence = FactConfidence;
408
+ export type SignalActionability = 'none' | 'manual' | 'guided' | 'safe-fix';
409
+ export type SignalStatus = 'new' | 'existing' | 'regressed' | 'resolved';
410
+ export type SignalMaturity = 'experimental' | 'beta' | 'stable';
411
+
412
+ export interface SignalEvidence {
413
+ kind:
414
+ | 'cycle'
415
+ | 'contract'
416
+ | 'bundle'
417
+ | 'temporal'
418
+ | 'memory'
419
+ | 'async-lifecycle'
420
+ | 'metric'
421
+ | 'layer'
422
+ | 'parser-fact'
423
+ | 'heuristic';
424
+ message: string;
425
+ modules?: ModuleId[];
426
+ confidence: SignalConfidence;
427
+ source?: string;
428
+ }
429
+
430
+ export type MemoryRiskKind =
431
+ | 'event-listener-cleanup'
432
+ | 'timer-cleanup'
433
+ | 'observer-cleanup'
434
+ | 'object-url-cleanup'
435
+ | 'subscription-cleanup';
436
+
437
+ export interface MemoryRiskEvidence {
438
+ message: string;
439
+ line?: number;
440
+ acquire: string;
441
+ expectedCleanup: string;
442
+ }
443
+
444
+ export interface MemoryRiskFinding {
445
+ id: string;
446
+ kind: MemoryRiskKind;
447
+ moduleId: ModuleId;
448
+ framework?: DetectedFramework;
449
+ severity: 'low' | 'medium';
450
+ confidence: FactConfidence;
451
+ evidence: MemoryRiskEvidence[];
452
+ remediation: string;
453
+ }
454
+
455
+ export type AsyncLifecycleRiskKind = 'async-effect-cleanup';
456
+
457
+ export interface AsyncLifecycleRiskEvidence {
458
+ message: string;
459
+ line?: number;
460
+ asyncSource: string;
461
+ expectedGuard: string;
462
+ }
463
+
464
+ export interface AsyncLifecycleRiskFinding {
465
+ id: string;
466
+ kind: AsyncLifecycleRiskKind;
467
+ moduleId: ModuleId;
468
+ framework?: DetectedFramework;
469
+ severity: 'low' | 'medium';
470
+ confidence: FactConfidence;
471
+ evidence: AsyncLifecycleRiskEvidence[];
472
+ remediation: string;
473
+ }
474
+
475
+ export interface SignalLifecycle {
476
+ status: SignalStatus;
477
+ firstSeenAt?: string;
478
+ lastSeenAt?: string;
479
+ baselineKey?: string;
480
+ }
481
+
482
+ export interface Suppression {
483
+ stableKey: string;
484
+ reason: string;
485
+ scope?: 'project' | 'module' | 'rule';
486
+ moduleId?: ModuleId;
487
+ createdAt?: string;
488
+ expiresAt?: string;
489
+ status?: 'active' | 'expired' | 'stale';
490
+ }
491
+
492
+ export interface SignalRanking {
493
+ score: number;
494
+ reasons: string[];
495
+ noisePenalty: number;
496
+ noveltyBoost: number;
497
+ }
498
+
499
+ export interface ArchitectureSignal {
500
+ id: string;
501
+ stableKey: string;
502
+ kind: string;
503
+ title: string;
504
+ severity: SignalSeverity;
505
+ confidence: SignalConfidence;
506
+ actionability: SignalActionability;
507
+ status: SignalStatus;
508
+ maturity: SignalMaturity;
509
+ modules: ModuleId[];
510
+ evidence: SignalEvidence[];
511
+ limitations: string[];
512
+ ranking: SignalRanking;
513
+ legacyRecommendationId?: string;
514
+ suppressed?: boolean;
515
+ suppressionReason?: string;
516
+ }
517
+
518
+ export interface ArchitectureInsight {
519
+ id: string;
520
+ title: string;
521
+ severity: SignalSeverity;
522
+ confidence: SignalConfidence;
523
+ signals: string[];
524
+ modules: ModuleId[];
525
+ rankingScore: number;
526
+ summary: string;
527
+ }
528
+
529
+ export type ScanPhase = 'discover' | 'parse' | 'graph' | 'cycles' | 'metrics' | 'done';
530
+
531
+ export interface ScanProgressEvent {
532
+ phase: ScanPhase;
533
+ current?: number;
534
+ total?: number;
535
+ }
536
+
537
+ export type ScanProgressCallback = (event: ScanProgressEvent) => void;