devintelligence 0.0.0-stage → 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/.env.example +14 -0
- package/LICENSE +22 -0
- package/README.md +145 -2
- package/bin/devintelligence.js +17 -0
- package/bin/devintelligence.ts +275 -0
- package/fixtures/django-analytics-service/requirements.txt +4 -0
- package/fixtures/django-analytics-service/settings.py +16 -0
- package/fixtures/express-api-gateway/package.json +14 -0
- package/fixtures/express-api-gateway/src/server.ts +16 -0
- package/fixtures/nextjs-saas-portal/package.json +21 -0
- package/fixtures/nextjs-saas-portal/src/app/dashboard/page.tsx +26 -0
- package/fixtures/rn-bare-fitness/android/build.gradle +19 -0
- package/fixtures/rn-bare-fitness/ios/Info.plist +14 -0
- package/fixtures/rn-bare-fitness/package.json +23 -0
- package/fixtures/rn-bare-fitness/src/WorkoutTracker.tsx +31 -0
- package/fixtures/rn-expo-commerce/android/app/build.gradle +20 -0
- package/fixtures/rn-expo-commerce/android/app/src/main/AndroidManifest.xml +21 -0
- package/fixtures/rn-expo-commerce/android/gradle.properties +6 -0
- package/fixtures/rn-expo-commerce/app.json +17 -0
- package/fixtures/rn-expo-commerce/babel.config.js +10 -0
- package/fixtures/rn-expo-commerce/ios/Podfile +13 -0
- package/fixtures/rn-expo-commerce/metro.config.js +7 -0
- package/fixtures/rn-expo-commerce/package.json +31 -0
- package/fixtures/rn-expo-commerce/src/components/LegacyPromoBanner.tsx +11 -0
- package/fixtures/rn-expo-commerce/src/screens/ProductFeedScreen.tsx +48 -0
- package/fixtures/rn-expo-commerce/src/services/api.ts +15 -0
- package/index.html +21 -0
- package/package.json +75 -4
- package/server.ts +275 -0
- package/src/App.tsx +783 -0
- package/src/components/DeepAnalysisViews.tsx +526 -0
- package/src/components/MobileAndPlatformViews.tsx +625 -0
- package/src/components/OverviewAndExplorerViews.tsx +753 -0
- package/src/components/RepairAndGateViews.tsx +814 -0
- package/src/engine/aiEngine.test.ts +86 -0
- package/src/engine/aiEngine.ts +346 -0
- package/src/engine/analyzerEngine.test.ts +110 -0
- package/src/engine/analyzerEngine.ts +1579 -0
- package/src/engine/repairEngine.ts +545 -0
- package/src/index.css +44 -0
- package/src/index.ts +59 -0
- package/src/main.tsx +5 -0
- package/src/types/devintelligence.ts +310 -0
- package/tsconfig.json +28 -0
- package/vite.config.ts +22 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Terminal,
|
|
4
|
+
FolderOpen,
|
|
5
|
+
FileCode,
|
|
6
|
+
Play,
|
|
7
|
+
Wrench,
|
|
8
|
+
CheckCircle2,
|
|
9
|
+
AlertTriangle,
|
|
10
|
+
XCircle,
|
|
11
|
+
ArrowRight,
|
|
12
|
+
Plus,
|
|
13
|
+
RefreshCw,
|
|
14
|
+
ShieldAlert,
|
|
15
|
+
Cpu,
|
|
16
|
+
Layers,
|
|
17
|
+
Smartphone,
|
|
18
|
+
} from 'lucide-react';
|
|
19
|
+
import {
|
|
20
|
+
DashboardModuleId,
|
|
21
|
+
DiagnosticIssue,
|
|
22
|
+
ProjectIntelligenceReport,
|
|
23
|
+
} from '../types/devintelligence.ts';
|
|
24
|
+
|
|
25
|
+
interface OverviewProps {
|
|
26
|
+
report: ProjectIntelligenceReport;
|
|
27
|
+
allProjects: ProjectIntelligenceReport[];
|
|
28
|
+
onSelectProject: (id: string) => void;
|
|
29
|
+
onNavigateModule: (module: DashboardModuleId) => void;
|
|
30
|
+
onRunScan: () => void;
|
|
31
|
+
onFixAllSafe: () => void;
|
|
32
|
+
onFixIssue: (issueId: string, allowReview?: boolean) => void;
|
|
33
|
+
onRegisterProject: (name: string, rootPath: string) => Promise<void>;
|
|
34
|
+
isBusy: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function SeverityText({ severity }: { severity: DiagnosticIssue['severity'] }) {
|
|
38
|
+
if (severity === 'critical') {
|
|
39
|
+
return (
|
|
40
|
+
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-red-400">
|
|
41
|
+
<XCircle className="w-3.5 h-3.5 shrink-0" />
|
|
42
|
+
<span>Critical</span>
|
|
43
|
+
</span>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (severity === 'high') {
|
|
47
|
+
return (
|
|
48
|
+
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-amber-400">
|
|
49
|
+
<AlertTriangle className="w-3.5 h-3.5 shrink-0" />
|
|
50
|
+
<span>High</span>
|
|
51
|
+
</span>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (severity === 'medium') {
|
|
55
|
+
return (
|
|
56
|
+
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-blue-400">
|
|
57
|
+
<AlertTriangle className="w-3.5 h-3.5 shrink-0" />
|
|
58
|
+
<span>Medium</span>
|
|
59
|
+
</span>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return (
|
|
63
|
+
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-slate-400">
|
|
64
|
+
<CheckCircle2 className="w-3.5 h-3.5 shrink-0" />
|
|
65
|
+
<span>Low</span>
|
|
66
|
+
</span>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function CommandCenterView({
|
|
71
|
+
report,
|
|
72
|
+
allProjects,
|
|
73
|
+
onSelectProject,
|
|
74
|
+
onNavigateModule,
|
|
75
|
+
onFixAllSafe,
|
|
76
|
+
onFixIssue,
|
|
77
|
+
isBusy,
|
|
78
|
+
}: OverviewProps) {
|
|
79
|
+
const safeFixableIssues = report.issues.filter(
|
|
80
|
+
(i) => i.fixAvailable && i.repairTier === 'SAFE'
|
|
81
|
+
);
|
|
82
|
+
const criticalIssues = report.issues.filter((i) => i.severity === 'critical');
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="space-y-8">
|
|
86
|
+
{/* Executive Header & Action Strip */}
|
|
87
|
+
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6 pb-6 border-b border-slate-800/80">
|
|
88
|
+
<div className="space-y-2 max-w-2xl">
|
|
89
|
+
<div className="flex items-center gap-2 text-xs text-slate-400 font-mono tabular-nums">
|
|
90
|
+
<span>{report.launchDoctor.frameworkName}</span>
|
|
91
|
+
<span aria-hidden="true">·</span>
|
|
92
|
+
<span>{report.filesAnalyzed.length} files analyzed</span>
|
|
93
|
+
<span aria-hidden="true">·</span>
|
|
94
|
+
<span>Scan {report.scanDurationMs}ms</span>
|
|
95
|
+
</div>
|
|
96
|
+
<h1 className="text-2xl font-bold tracking-tight text-slate-100">
|
|
97
|
+
{report.name}
|
|
98
|
+
</h1>
|
|
99
|
+
<p className="text-sm text-slate-400 leading-relaxed">
|
|
100
|
+
{report.description}
|
|
101
|
+
</p>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className="flex flex-wrap items-center gap-3 shrink-0">
|
|
105
|
+
{safeFixableIssues.length > 0 && (
|
|
106
|
+
<button
|
|
107
|
+
type="button"
|
|
108
|
+
disabled={isBusy}
|
|
109
|
+
onClick={onFixAllSafe}
|
|
110
|
+
className="inline-flex items-center gap-2 px-4 py-2 text-xs font-semibold text-white bg-blue-600 hover:bg-blue-500 disabled:opacity-50 rounded-lg transition-colors whitespace-nowrap shrink-0 cursor-pointer"
|
|
111
|
+
>
|
|
112
|
+
<Wrench className="w-3.5 h-3.5" />
|
|
113
|
+
<span>Fix All Safe Issues ({safeFixableIssues.length})</span>
|
|
114
|
+
</button>
|
|
115
|
+
)}
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
onClick={() => onNavigateModule('rn-doctor')}
|
|
119
|
+
className="inline-flex items-center gap-2 px-4 py-2 text-xs font-medium text-slate-200 bg-slate-900 hover:bg-slate-800 border border-slate-800 rounded-lg transition-colors whitespace-nowrap shrink-0 cursor-pointer"
|
|
120
|
+
>
|
|
121
|
+
<Smartphone className="w-3.5 h-3.5 text-blue-400" />
|
|
122
|
+
<span>Open React Native Doctor</span>
|
|
123
|
+
</button>
|
|
124
|
+
<button
|
|
125
|
+
type="button"
|
|
126
|
+
onClick={() => onNavigateModule('auto-repair')}
|
|
127
|
+
className="inline-flex items-center gap-2 px-4 py-2 text-xs font-medium text-slate-200 bg-slate-900 hover:bg-slate-800 border border-slate-800 rounded-lg transition-colors whitespace-nowrap shrink-0 cursor-pointer"
|
|
128
|
+
>
|
|
129
|
+
<span>Review Diffs & Repairs</span>
|
|
130
|
+
<ArrowRight className="w-3.5 h-3.5" />
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
{/* Primary Telemetry Strip (Single-Elevation Grid) */}
|
|
136
|
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
|
137
|
+
<div className="p-4 bg-slate-900/70 border border-slate-800/80 rounded-lg">
|
|
138
|
+
<div className="text-xs text-slate-400">Overall Health</div>
|
|
139
|
+
<div className="mt-2 text-2xl font-bold font-mono tabular-nums text-slate-100">
|
|
140
|
+
{report.scores.overall}
|
|
141
|
+
<span className="text-sm font-normal text-slate-500">/100</span>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="mt-1 text-xs text-slate-400">
|
|
144
|
+
{report.qualityGateEvaluation.passed ? 'Gate Passing' : 'Gate Action Needed'}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div className="p-4 bg-slate-900/70 border border-slate-800/80 rounded-lg">
|
|
149
|
+
<div className="text-xs text-slate-400">React Native</div>
|
|
150
|
+
<div className="mt-2 text-2xl font-bold font-mono tabular-nums text-blue-400">
|
|
151
|
+
{report.scores.reactNative}
|
|
152
|
+
<span className="text-sm font-normal text-slate-500">/100</span>
|
|
153
|
+
</div>
|
|
154
|
+
<div className="mt-1 text-xs text-slate-400">
|
|
155
|
+
{report.launchDoctor.workflowType}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div className="p-4 bg-slate-900/70 border border-slate-800/80 rounded-lg">
|
|
160
|
+
<div className="text-xs text-slate-400">Active Findings</div>
|
|
161
|
+
<div className="mt-2 text-2xl font-bold font-mono tabular-nums text-amber-400">
|
|
162
|
+
{report.issues.length}
|
|
163
|
+
</div>
|
|
164
|
+
<div className="mt-1 text-xs text-slate-400 font-mono tabular-nums">
|
|
165
|
+
{criticalIssues.length} critical · {safeFixableIssues.length} safe fix
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div className="p-4 bg-slate-900/70 border border-slate-800/80 rounded-lg">
|
|
170
|
+
<div className="text-xs text-slate-400">Security Audit</div>
|
|
171
|
+
<div className="mt-2 text-2xl font-bold font-mono tabular-nums text-slate-100">
|
|
172
|
+
{report.scores.security}
|
|
173
|
+
<span className="text-sm font-normal text-slate-500">/100</span>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="mt-1 text-xs text-slate-400 font-mono tabular-nums">
|
|
176
|
+
{report.issues.filter((i) => i.domain === 'security').length} security findings
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div className="p-4 bg-slate-900/70 border border-slate-800/80 rounded-lg">
|
|
181
|
+
<div className="text-xs text-slate-400">Design Compliance</div>
|
|
182
|
+
<div className="mt-2 text-2xl font-bold font-mono tabular-nums text-slate-100">
|
|
183
|
+
{report.scores.designQuality}
|
|
184
|
+
<span className="text-sm font-normal text-slate-500">/100</span>
|
|
185
|
+
</div>
|
|
186
|
+
<div className="mt-1 text-xs text-slate-400 font-mono tabular-nums">
|
|
187
|
+
{report.designSystem.emojiIconsCount} emoji icons · {report.designSystem.hardcodedColorsCount} raw hex
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div className="p-4 bg-slate-900/70 border border-slate-800/80 rounded-lg">
|
|
192
|
+
<div className="text-xs text-slate-400">Bundle Estimate</div>
|
|
193
|
+
<div className="mt-2 text-2xl font-bold font-mono tabular-nums text-slate-100">
|
|
194
|
+
{report.performance.estimatedBundleWeightKb}
|
|
195
|
+
<span className="text-sm font-normal text-slate-500"> KB</span>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="mt-1 text-xs text-slate-400 font-mono tabular-nums">
|
|
198
|
+
{report.performance.heavyDependencies.length} heavy packages
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
{/* Launch Doctor & Multi-Project Switcher Split */}
|
|
204
|
+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-stretch">
|
|
205
|
+
{/* Launch Doctor Panel */}
|
|
206
|
+
<div className="lg:col-span-7 p-6 bg-slate-900/70 border border-slate-800/80 rounded-lg flex flex-col justify-between space-y-5">
|
|
207
|
+
<div className="space-y-3">
|
|
208
|
+
<div className="flex items-center justify-between">
|
|
209
|
+
<h2 className="text-base font-semibold text-slate-100">
|
|
210
|
+
Project Launch Doctor
|
|
211
|
+
</h2>
|
|
212
|
+
<span className="text-xs font-mono text-slate-400">
|
|
213
|
+
Package Manager: {report.launchDoctor.packageManager}
|
|
214
|
+
</span>
|
|
215
|
+
</div>
|
|
216
|
+
<p className="text-xs text-slate-400 leading-relaxed">
|
|
217
|
+
Verified startup sequence, bundler configuration, and native target readiness without executing unapproved scripts.
|
|
218
|
+
</p>
|
|
219
|
+
|
|
220
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 pt-2 text-xs">
|
|
221
|
+
<div className="p-3 bg-slate-950/70 rounded border border-slate-800/60">
|
|
222
|
+
<div className="text-slate-400">Metro Config</div>
|
|
223
|
+
<div className="mt-1 font-mono font-medium text-slate-200">
|
|
224
|
+
{report.launchDoctor.metroConfigured ? 'Detected' : 'Not Applicable'}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
<div className="p-3 bg-slate-950/70 rounded border border-slate-800/60">
|
|
228
|
+
<div className="text-slate-400">Babel Pipeline</div>
|
|
229
|
+
<div className="mt-1 font-mono font-medium text-slate-200">
|
|
230
|
+
{report.launchDoctor.babelConfigured ? 'Detected' : 'Standard'}
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
<div className="p-3 bg-slate-950/70 rounded border border-slate-800/60">
|
|
234
|
+
<div className="text-slate-400">Android Target</div>
|
|
235
|
+
<div className="mt-1 font-mono font-medium text-slate-200">
|
|
236
|
+
{report.android.present
|
|
237
|
+
? report.launchDoctor.androidLaunchReady
|
|
238
|
+
? 'Ready (API 34)'
|
|
239
|
+
: 'Needs Repair'
|
|
240
|
+
: 'N/A'}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
<div className="p-3 bg-slate-950/70 rounded border border-slate-800/60">
|
|
244
|
+
<div className="text-slate-400">iOS Target</div>
|
|
245
|
+
<div className="mt-1 font-mono font-medium text-slate-200">
|
|
246
|
+
{report.ios.present
|
|
247
|
+
? report.ios.hostCanVerifyNativeBuild
|
|
248
|
+
? 'macOS Ready'
|
|
249
|
+
: 'Static Only (Linux)'
|
|
250
|
+
: 'N/A'}
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div className="space-y-2 pt-2 border-t border-slate-800/80">
|
|
257
|
+
<div className="text-xs font-medium text-slate-300">
|
|
258
|
+
Recommended Startup Command
|
|
259
|
+
</div>
|
|
260
|
+
<div className="flex items-center justify-between px-3.5 py-2.5 bg-slate-950 border border-slate-800 rounded font-mono text-xs text-blue-300">
|
|
261
|
+
<span>$ {report.launchDoctor.recommendedStartCommand}</span>
|
|
262
|
+
<span className="text-slate-500">Verified Entrypoint</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex flex-wrap items-center gap-4 pt-1 text-xs text-slate-400 font-mono">
|
|
265
|
+
{report.launchDoctor.secondaryCommands.map((cmd) => (
|
|
266
|
+
<span key={cmd.label}>
|
|
267
|
+
{cmd.label}: <strong className="text-slate-300">{cmd.command}</strong>
|
|
268
|
+
</span>
|
|
269
|
+
))}
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
{/* Multi-Project Workspace Switcher */}
|
|
275
|
+
<div className="lg:col-span-5 p-6 bg-slate-900/70 border border-slate-800/80 rounded-lg flex flex-col justify-between space-y-4">
|
|
276
|
+
<div className="space-y-1">
|
|
277
|
+
<div className="flex items-center justify-between">
|
|
278
|
+
<h2 className="text-base font-semibold text-slate-100">
|
|
279
|
+
Multi-Project Workspace ({allProjects.length})
|
|
280
|
+
</h2>
|
|
281
|
+
<button
|
|
282
|
+
type="button"
|
|
283
|
+
onClick={() => onNavigateModule('project-explorer')}
|
|
284
|
+
className="text-xs font-medium text-blue-400 hover:text-blue-300 transition-colors cursor-pointer"
|
|
285
|
+
>
|
|
286
|
+
Register Project
|
|
287
|
+
</button>
|
|
288
|
+
</div>
|
|
289
|
+
<p className="text-xs text-slate-400">
|
|
290
|
+
Isolated scan history, design tokens, and repair backups per repository.
|
|
291
|
+
</p>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<div className="divide-y divide-slate-800/80 border-t border-b border-slate-800/80">
|
|
295
|
+
{allProjects.map((proj) => {
|
|
296
|
+
const isSelected = proj.id === report.id;
|
|
297
|
+
return (
|
|
298
|
+
<button
|
|
299
|
+
key={proj.id}
|
|
300
|
+
type="button"
|
|
301
|
+
onClick={() => onSelectProject(proj.id)}
|
|
302
|
+
className={`w-full py-2.5 px-3 flex items-center justify-between text-left transition-colors cursor-pointer ${
|
|
303
|
+
isSelected ? 'bg-blue-950/30 text-white' : 'hover:bg-slate-800/50 text-slate-300'
|
|
304
|
+
}`}
|
|
305
|
+
>
|
|
306
|
+
<div className="min-w-0 pr-3">
|
|
307
|
+
<div className="text-xs font-semibold truncate">{proj.name}</div>
|
|
308
|
+
<div className="text-[11px] text-slate-400 font-mono truncate">
|
|
309
|
+
{proj.ecosystem} · {proj.issues.length} findings
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
<div className="text-right font-mono tabular-nums shrink-0">
|
|
313
|
+
<span
|
|
314
|
+
className={`text-xs font-bold ${
|
|
315
|
+
proj.scores.overall >= 85
|
|
316
|
+
? 'text-emerald-400'
|
|
317
|
+
: proj.scores.overall >= 70
|
|
318
|
+
? 'text-amber-400'
|
|
319
|
+
: 'text-red-400'
|
|
320
|
+
}`}
|
|
321
|
+
>
|
|
322
|
+
{proj.scores.overall}/100
|
|
323
|
+
</span>
|
|
324
|
+
</div>
|
|
325
|
+
</button>
|
|
326
|
+
);
|
|
327
|
+
})}
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
{/* Priority Findings Table with Direct Safe Fix Actions */}
|
|
333
|
+
<div className="space-y-3">
|
|
334
|
+
<div className="flex items-center justify-between">
|
|
335
|
+
<div>
|
|
336
|
+
<h2 className="text-base font-semibold text-slate-100">
|
|
337
|
+
Highest-Priority Diagnostic Findings ({report.issues.length})
|
|
338
|
+
</h2>
|
|
339
|
+
<p className="text-xs text-slate-400">
|
|
340
|
+
Root causes traced to exact source lines with deterministic SAFE repair verification.
|
|
341
|
+
</p>
|
|
342
|
+
</div>
|
|
343
|
+
<button
|
|
344
|
+
type="button"
|
|
345
|
+
onClick={() => onNavigateModule('auto-repair')}
|
|
346
|
+
className="text-xs font-medium text-blue-400 hover:text-blue-300 cursor-pointer"
|
|
347
|
+
>
|
|
348
|
+
Open Auto Repair Center →
|
|
349
|
+
</button>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
{report.issues.length === 0 ? (
|
|
353
|
+
<div className="p-8 bg-slate-900/50 border border-slate-800 rounded-lg text-center space-y-2">
|
|
354
|
+
<CheckCircle2 className="w-6 h-6 text-emerald-400 mx-auto" />
|
|
355
|
+
<div className="text-sm font-semibold text-slate-200">
|
|
356
|
+
Zero Active Engineering Findings
|
|
357
|
+
</div>
|
|
358
|
+
<p className="text-xs text-slate-400">
|
|
359
|
+
All detected issues in {report.name} have been repaired and verified. You can roll back repairs in the Auto Repair Center or Project History.
|
|
360
|
+
</p>
|
|
361
|
+
</div>
|
|
362
|
+
) : (
|
|
363
|
+
<div className="overflow-x-auto border border-slate-800/80 rounded-lg bg-slate-900/50">
|
|
364
|
+
<table className="w-full text-left border-collapse">
|
|
365
|
+
<thead>
|
|
366
|
+
<tr className="border-b border-slate-800 text-[11px] font-medium text-slate-400">
|
|
367
|
+
<th className="py-2.5 px-4">Severity</th>
|
|
368
|
+
<th className="py-2.5 px-4">Domain</th>
|
|
369
|
+
<th className="py-2.5 px-4">Finding & Root Cause</th>
|
|
370
|
+
<th className="py-2.5 px-4">Location</th>
|
|
371
|
+
<th className="py-2.5 px-4">Repair Tier</th>
|
|
372
|
+
<th className="py-2.5 px-4 text-right">Action</th>
|
|
373
|
+
</tr>
|
|
374
|
+
</thead>
|
|
375
|
+
<tbody className="divide-y divide-slate-800/60 text-xs">
|
|
376
|
+
{report.issues.map((issue) => (
|
|
377
|
+
<tr key={issue.id} className="hover:bg-slate-800/40 transition-colors">
|
|
378
|
+
<td className="py-3 px-4 whitespace-nowrap">
|
|
379
|
+
<SeverityText severity={issue.severity} />
|
|
380
|
+
</td>
|
|
381
|
+
<td className="py-3 px-4 whitespace-nowrap font-mono text-slate-300">
|
|
382
|
+
{issue.domain}
|
|
383
|
+
</td>
|
|
384
|
+
<td className="py-3 px-4 max-w-md">
|
|
385
|
+
<div className="font-semibold text-slate-100">{issue.title}</div>
|
|
386
|
+
<div className="text-slate-400 mt-0.5 line-clamp-1">{issue.rootCause}</div>
|
|
387
|
+
</td>
|
|
388
|
+
<td className="py-3 px-4 whitespace-nowrap font-mono tabular-nums text-slate-300">
|
|
389
|
+
{issue.filePath}:{issue.line}
|
|
390
|
+
</td>
|
|
391
|
+
<td className="py-3 px-4 whitespace-nowrap font-mono text-slate-300">
|
|
392
|
+
{issue.repairTier} · {issue.confidence}%
|
|
393
|
+
</td>
|
|
394
|
+
<td className="py-3 px-4 whitespace-nowrap text-right">
|
|
395
|
+
{issue.fixAvailable ? (
|
|
396
|
+
<button
|
|
397
|
+
type="button"
|
|
398
|
+
disabled={isBusy}
|
|
399
|
+
onClick={() =>
|
|
400
|
+
onFixIssue(issue.id, issue.repairTier !== 'SAFE')
|
|
401
|
+
}
|
|
402
|
+
className="px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-500 disabled:opacity-50 rounded transition-colors cursor-pointer whitespace-nowrap"
|
|
403
|
+
>
|
|
404
|
+
{issue.repairTier === 'SAFE' ? 'Apply Safe Fix' : 'Approve & Fix'}
|
|
405
|
+
</button>
|
|
406
|
+
) : (
|
|
407
|
+
<button
|
|
408
|
+
type="button"
|
|
409
|
+
onClick={() => onNavigateModule('auto-repair')}
|
|
410
|
+
className="px-3 py-1.5 text-xs font-medium text-slate-300 bg-slate-800 hover:bg-slate-700 rounded transition-colors cursor-pointer whitespace-nowrap"
|
|
411
|
+
>
|
|
412
|
+
Inspect Guide
|
|
413
|
+
</button>
|
|
414
|
+
)}
|
|
415
|
+
</td>
|
|
416
|
+
</tr>
|
|
417
|
+
))}
|
|
418
|
+
</tbody>
|
|
419
|
+
</table>
|
|
420
|
+
</div>
|
|
421
|
+
)}
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function ProjectExplorerView({
|
|
428
|
+
report,
|
|
429
|
+
allProjects,
|
|
430
|
+
onSelectProject,
|
|
431
|
+
onRegisterProject,
|
|
432
|
+
onFixIssue,
|
|
433
|
+
isBusy,
|
|
434
|
+
}: OverviewProps) {
|
|
435
|
+
const [selectedFile, setSelectedFile] = useState<string>(
|
|
436
|
+
report.filesAnalyzed[0] || 'package.json'
|
|
437
|
+
);
|
|
438
|
+
const [fileContent, setFileContent] = useState<string>('');
|
|
439
|
+
const [loadingFile, setLoadingFile] = useState<boolean>(false);
|
|
440
|
+
const [newProjName, setNewProjName] = useState('');
|
|
441
|
+
const [newProjPath, setNewProjPath] = useState('');
|
|
442
|
+
const [registerError, setRegisterError] = useState<string | null>(null);
|
|
443
|
+
const [registerSuccess, setRegisterSuccess] = useState<string | null>(null);
|
|
444
|
+
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
const defaultFile =
|
|
447
|
+
report.filesAnalyzed.find((f) => f.endsWith('.tsx') || f.endsWith('.ts') || f.endsWith('.py')) ||
|
|
448
|
+
report.filesAnalyzed[0] ||
|
|
449
|
+
'package.json';
|
|
450
|
+
setSelectedFile(defaultFile);
|
|
451
|
+
}, [report.id]);
|
|
452
|
+
|
|
453
|
+
useEffect(() => {
|
|
454
|
+
if (!selectedFile) return;
|
|
455
|
+
let active = true;
|
|
456
|
+
setLoadingFile(true);
|
|
457
|
+
fetch(
|
|
458
|
+
`/api/devintel/file-content?projectId=${encodeURIComponent(report.id)}&filePath=${encodeURIComponent(selectedFile)}`
|
|
459
|
+
)
|
|
460
|
+
.then((r) => r.json())
|
|
461
|
+
.then((data) => {
|
|
462
|
+
if (active) {
|
|
463
|
+
setFileContent(data.content || '// Empty or binary file');
|
|
464
|
+
setLoadingFile(false);
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
.catch(() => {
|
|
468
|
+
if (active) {
|
|
469
|
+
setFileContent('// Unable to read file from disk');
|
|
470
|
+
setLoadingFile(false);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
return () => {
|
|
474
|
+
active = false;
|
|
475
|
+
};
|
|
476
|
+
}, [report.id, selectedFile, report.lastScannedAt]);
|
|
477
|
+
|
|
478
|
+
const fileIssues = report.issues.filter((i) => i.filePath === selectedFile);
|
|
479
|
+
|
|
480
|
+
async function handleRegisterSubmit(e: React.FormEvent) {
|
|
481
|
+
e.preventDefault();
|
|
482
|
+
setRegisterError(null);
|
|
483
|
+
setRegisterSuccess(null);
|
|
484
|
+
if (!newProjName.trim() || !newProjPath.trim()) {
|
|
485
|
+
setRegisterError('Both project name and local filesystem directory path are required.');
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
await onRegisterProject(newProjName.trim(), newProjPath.trim());
|
|
490
|
+
setRegisterSuccess(`Registered and scanned "${newProjName.trim()}" successfully.`);
|
|
491
|
+
setNewProjName('');
|
|
492
|
+
setNewProjPath('');
|
|
493
|
+
} catch (err: any) {
|
|
494
|
+
setRegisterError(err.message || 'Failed to register directory.');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
<div className="space-y-8">
|
|
500
|
+
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 pb-5 border-b border-slate-800/80">
|
|
501
|
+
<div>
|
|
502
|
+
<h1 className="text-xl font-bold text-slate-100">
|
|
503
|
+
Project Explorer & Source Inspector
|
|
504
|
+
</h1>
|
|
505
|
+
<p className="text-xs text-slate-400 mt-1 font-mono">
|
|
506
|
+
Root Path: {report.rootPath} · {report.filesAnalyzed.length} source & config files indexed
|
|
507
|
+
</p>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
{/* Register New Local Project Form */}
|
|
512
|
+
<form
|
|
513
|
+
onSubmit={handleRegisterSubmit}
|
|
514
|
+
className="p-5 bg-slate-900/70 border border-slate-800/80 rounded-lg space-y-4"
|
|
515
|
+
>
|
|
516
|
+
<div className="flex items-center justify-between">
|
|
517
|
+
<div className="text-sm font-semibold text-slate-100">
|
|
518
|
+
Register Local Project Directory
|
|
519
|
+
</div>
|
|
520
|
+
<span className="text-xs text-slate-400 font-mono">
|
|
521
|
+
Supports React Native, Expo, Next.js, Vite, Express, Python/Django
|
|
522
|
+
</span>
|
|
523
|
+
</div>
|
|
524
|
+
<div className="grid grid-cols-1 md:grid-cols-12 gap-3 items-end">
|
|
525
|
+
<div className="md:col-span-4">
|
|
526
|
+
<label htmlFor="proj-name-input" className="block text-xs text-slate-400 mb-1">
|
|
527
|
+
Project Display Name
|
|
528
|
+
</label>
|
|
529
|
+
<input
|
|
530
|
+
id="proj-name-input"
|
|
531
|
+
type="text"
|
|
532
|
+
value={newProjName}
|
|
533
|
+
onChange={(e) => setNewProjName(e.target.value)}
|
|
534
|
+
placeholder="e.g. Host Workspace App"
|
|
535
|
+
className="w-full px-3 py-2 text-xs bg-slate-950 border border-slate-800 rounded text-slate-100 focus:outline-none focus:border-blue-500"
|
|
536
|
+
/>
|
|
537
|
+
</div>
|
|
538
|
+
<div className="md:col-span-6">
|
|
539
|
+
<label htmlFor="proj-path-input" className="block text-xs text-slate-400 mb-1">
|
|
540
|
+
Local Directory Path (Relative or Absolute)
|
|
541
|
+
</label>
|
|
542
|
+
<input
|
|
543
|
+
id="proj-path-input"
|
|
544
|
+
type="text"
|
|
545
|
+
value={newProjPath}
|
|
546
|
+
onChange={(e) => setNewProjPath(e.target.value)}
|
|
547
|
+
placeholder="e.g. fixtures/rn-expo-commerce or ."
|
|
548
|
+
className="w-full px-3 py-2 text-xs font-mono bg-slate-950 border border-slate-800 rounded text-slate-100 focus:outline-none focus:border-blue-500"
|
|
549
|
+
/>
|
|
550
|
+
</div>
|
|
551
|
+
<div className="md:col-span-2">
|
|
552
|
+
<button
|
|
553
|
+
type="submit"
|
|
554
|
+
disabled={isBusy}
|
|
555
|
+
className="w-full inline-flex items-center justify-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded transition-colors cursor-pointer whitespace-nowrap"
|
|
556
|
+
>
|
|
557
|
+
<Plus className="w-3.5 h-3.5" />
|
|
558
|
+
<span>Add & Scan</span>
|
|
559
|
+
</button>
|
|
560
|
+
</div>
|
|
561
|
+
</div>
|
|
562
|
+
{registerError && (
|
|
563
|
+
<div className="text-xs text-red-400 font-medium">{registerError}</div>
|
|
564
|
+
)}
|
|
565
|
+
{registerSuccess && (
|
|
566
|
+
<div className="text-xs text-emerald-400 font-medium">{registerSuccess}</div>
|
|
567
|
+
)}
|
|
568
|
+
</form>
|
|
569
|
+
|
|
570
|
+
{/* File Tree + Live Source Code & Inline Diagnostics */}
|
|
571
|
+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-start">
|
|
572
|
+
<div className="lg:col-span-4 bg-slate-900/70 border border-slate-800/80 rounded-lg overflow-hidden">
|
|
573
|
+
<div className="px-4 py-3 border-b border-slate-800 text-xs font-semibold text-slate-200 flex items-center justify-between">
|
|
574
|
+
<span>Repository Files ({report.filesAnalyzed.length})</span>
|
|
575
|
+
<span className="font-mono text-slate-400">{report.id}</span>
|
|
576
|
+
</div>
|
|
577
|
+
<div className="divide-y divide-slate-800/60 max-h-[520px] overflow-y-auto">
|
|
578
|
+
{report.filesAnalyzed.map((file) => {
|
|
579
|
+
const count = report.issues.filter((i) => i.filePath === file).length;
|
|
580
|
+
const active = file === selectedFile;
|
|
581
|
+
return (
|
|
582
|
+
<button
|
|
583
|
+
key={file}
|
|
584
|
+
type="button"
|
|
585
|
+
onClick={() => setSelectedFile(file)}
|
|
586
|
+
className={`w-full px-4 py-2.5 text-left flex items-center justify-between text-xs font-mono transition-colors cursor-pointer ${
|
|
587
|
+
active
|
|
588
|
+
? 'bg-blue-950/40 text-blue-300 font-medium'
|
|
589
|
+
: 'text-slate-300 hover:bg-slate-800/50'
|
|
590
|
+
}`}
|
|
591
|
+
>
|
|
592
|
+
<span className="truncate pr-2">{file}</span>
|
|
593
|
+
{count > 0 && (
|
|
594
|
+
<span className="text-[11px] text-amber-400 shrink-0 tabular-nums">
|
|
595
|
+
{count} {count === 1 ? 'issue' : 'issues'}
|
|
596
|
+
</span>
|
|
597
|
+
)}
|
|
598
|
+
</button>
|
|
599
|
+
);
|
|
600
|
+
})}
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
|
|
604
|
+
<div className="lg:col-span-8 space-y-4">
|
|
605
|
+
<div className="bg-slate-900/70 border border-slate-800/80 rounded-lg overflow-hidden">
|
|
606
|
+
<div className="px-4 py-3 border-b border-slate-800 flex items-center justify-between">
|
|
607
|
+
<div className="flex items-center gap-2 text-xs font-mono text-slate-200">
|
|
608
|
+
<FileCode className="w-4 h-4 text-blue-400" />
|
|
609
|
+
<span>{selectedFile}</span>
|
|
610
|
+
</div>
|
|
611
|
+
<span className="text-xs text-slate-400 font-mono tabular-nums">
|
|
612
|
+
{fileIssues.length} active finding(s) in file
|
|
613
|
+
</span>
|
|
614
|
+
</div>
|
|
615
|
+
|
|
616
|
+
{fileIssues.length > 0 && (
|
|
617
|
+
<div className="p-4 bg-slate-950/90 border-b border-slate-800 space-y-3">
|
|
618
|
+
{fileIssues.map((issue) => (
|
|
619
|
+
<div
|
|
620
|
+
key={issue.id}
|
|
621
|
+
className="p-3 bg-slate-900 border border-slate-800 rounded flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3"
|
|
622
|
+
>
|
|
623
|
+
<div className="space-y-1">
|
|
624
|
+
<div className="flex items-center gap-2 text-xs">
|
|
625
|
+
<SeverityText severity={issue.severity} />
|
|
626
|
+
<span className="text-slate-500">·</span>
|
|
627
|
+
<span className="font-mono text-slate-300">Line {issue.line}</span>
|
|
628
|
+
<span className="text-slate-500">·</span>
|
|
629
|
+
<span className="font-semibold text-slate-100">{issue.title}</span>
|
|
630
|
+
</div>
|
|
631
|
+
<p className="text-xs text-slate-400">{issue.recommendedRepair}</p>
|
|
632
|
+
</div>
|
|
633
|
+
{issue.fixAvailable && (
|
|
634
|
+
<button
|
|
635
|
+
type="button"
|
|
636
|
+
disabled={isBusy}
|
|
637
|
+
onClick={() => onFixIssue(issue.id, issue.repairTier !== 'SAFE')}
|
|
638
|
+
className="px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-500 rounded shrink-0 cursor-pointer whitespace-nowrap"
|
|
639
|
+
>
|
|
640
|
+
Repair File Now
|
|
641
|
+
</button>
|
|
642
|
+
)}
|
|
643
|
+
</div>
|
|
644
|
+
))}
|
|
645
|
+
</div>
|
|
646
|
+
)}
|
|
647
|
+
|
|
648
|
+
<pre className="p-4 text-xs font-mono text-slate-200 overflow-x-auto max-h-[460px] leading-relaxed bg-slate-950/60">
|
|
649
|
+
{loadingFile
|
|
650
|
+
? 'Reading file from local disk...'
|
|
651
|
+
: fileContent
|
|
652
|
+
.split('\n')
|
|
653
|
+
.map((line, idx) => `${String(idx + 1).padStart(3, ' ')} | ${line}`)
|
|
654
|
+
.join('\n')}
|
|
655
|
+
</pre>
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export function ProjectHealthView({ report, onNavigateModule }: OverviewProps) {
|
|
664
|
+
const domains: {
|
|
665
|
+
label: string;
|
|
666
|
+
score: number;
|
|
667
|
+
targetModule: DashboardModuleId;
|
|
668
|
+
summary: string;
|
|
669
|
+
}[] = [
|
|
670
|
+
{
|
|
671
|
+
label: 'React Native & Mobile Readiness',
|
|
672
|
+
score: report.scores.reactNative,
|
|
673
|
+
targetModule: 'rn-doctor',
|
|
674
|
+
summary: 'Metro bundler, Babel Reanimated worklet ordering, Android Gradle SDK, and iOS Podfile health.',
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
label: 'Code & Architecture Quality',
|
|
678
|
+
score: report.scores.codeQuality,
|
|
679
|
+
targetModule: 'code-intelligence',
|
|
680
|
+
summary: 'Unused file detection, hook lifecycle cleanup, dependency alignment, and modularity.',
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
label: 'Design System & Accessibility',
|
|
684
|
+
score: report.scores.designQuality,
|
|
685
|
+
targetModule: 'design-intelligence',
|
|
686
|
+
summary: 'Design token adherence, 4pt/8pt spatial grid, vector icon usage, and screen-reader labels.',
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
label: 'Runtime & Bundle Performance',
|
|
690
|
+
score: report.scores.performance,
|
|
691
|
+
targetModule: 'performance-lab',
|
|
692
|
+
summary: 'FlatList windowing, memory leak prevention, heavy dependency footprint, and non-blocking I/O.',
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
label: 'Security & Credential Hygiene',
|
|
696
|
+
score: report.scores.security,
|
|
697
|
+
targetModule: 'security-audit',
|
|
698
|
+
summary: 'Zero hardcoded API keys, parameterized SQL queries, HTTPS enforcement, and production flags.',
|
|
699
|
+
},
|
|
700
|
+
];
|
|
701
|
+
|
|
702
|
+
return (
|
|
703
|
+
<div className="space-y-8">
|
|
704
|
+
<div className="pb-5 border-b border-slate-800/80">
|
|
705
|
+
<h1 className="text-xl font-bold text-slate-100">
|
|
706
|
+
Project Health & Engineering Scorecard
|
|
707
|
+
</h1>
|
|
708
|
+
<p className="text-xs text-slate-400 mt-1">
|
|
709
|
+
Computed deterministically from real static, configuration, and runtime-pattern inspections on {report.name}.
|
|
710
|
+
</p>
|
|
711
|
+
</div>
|
|
712
|
+
|
|
713
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
|
714
|
+
{domains.map((d) => (
|
|
715
|
+
<div
|
|
716
|
+
key={d.label}
|
|
717
|
+
className="p-5 bg-slate-900/70 border border-slate-800/80 rounded-lg flex flex-col justify-between space-y-4"
|
|
718
|
+
>
|
|
719
|
+
<div className="space-y-2">
|
|
720
|
+
<div className="flex items-center justify-between">
|
|
721
|
+
<span className="text-sm font-semibold text-slate-100">{d.label}</span>
|
|
722
|
+
<span className="text-lg font-bold font-mono tabular-nums text-blue-400">
|
|
723
|
+
{d.score}/100
|
|
724
|
+
</span>
|
|
725
|
+
</div>
|
|
726
|
+
<div className="w-full h-1.5 bg-slate-800 rounded-full overflow-hidden">
|
|
727
|
+
<div
|
|
728
|
+
className={`h-full ${
|
|
729
|
+
d.score >= 85
|
|
730
|
+
? 'bg-emerald-500'
|
|
731
|
+
: d.score >= 70
|
|
732
|
+
? 'bg-blue-500'
|
|
733
|
+
: 'bg-amber-500'
|
|
734
|
+
}`}
|
|
735
|
+
style={{ width: `${d.score}%` }}
|
|
736
|
+
/>
|
|
737
|
+
</div>
|
|
738
|
+
<p className="text-xs text-slate-400 leading-relaxed pt-1">{d.summary}</p>
|
|
739
|
+
</div>
|
|
740
|
+
<button
|
|
741
|
+
type="button"
|
|
742
|
+
onClick={() => onNavigateModule(d.targetModule)}
|
|
743
|
+
className="inline-flex items-center gap-1.5 text-xs font-medium text-blue-400 hover:text-blue-300 cursor-pointer"
|
|
744
|
+
>
|
|
745
|
+
<span>Inspect Domain Diagnostics</span>
|
|
746
|
+
<ArrowRight className="w-3.5 h-3.5" />
|
|
747
|
+
</button>
|
|
748
|
+
</div>
|
|
749
|
+
))}
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
);
|
|
753
|
+
}
|