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.
Files changed (45) hide show
  1. package/.env.example +14 -0
  2. package/LICENSE +22 -0
  3. package/README.md +145 -2
  4. package/bin/devintelligence.js +17 -0
  5. package/bin/devintelligence.ts +275 -0
  6. package/fixtures/django-analytics-service/requirements.txt +4 -0
  7. package/fixtures/django-analytics-service/settings.py +16 -0
  8. package/fixtures/express-api-gateway/package.json +14 -0
  9. package/fixtures/express-api-gateway/src/server.ts +16 -0
  10. package/fixtures/nextjs-saas-portal/package.json +21 -0
  11. package/fixtures/nextjs-saas-portal/src/app/dashboard/page.tsx +26 -0
  12. package/fixtures/rn-bare-fitness/android/build.gradle +19 -0
  13. package/fixtures/rn-bare-fitness/ios/Info.plist +14 -0
  14. package/fixtures/rn-bare-fitness/package.json +23 -0
  15. package/fixtures/rn-bare-fitness/src/WorkoutTracker.tsx +31 -0
  16. package/fixtures/rn-expo-commerce/android/app/build.gradle +20 -0
  17. package/fixtures/rn-expo-commerce/android/app/src/main/AndroidManifest.xml +21 -0
  18. package/fixtures/rn-expo-commerce/android/gradle.properties +6 -0
  19. package/fixtures/rn-expo-commerce/app.json +17 -0
  20. package/fixtures/rn-expo-commerce/babel.config.js +10 -0
  21. package/fixtures/rn-expo-commerce/ios/Podfile +13 -0
  22. package/fixtures/rn-expo-commerce/metro.config.js +7 -0
  23. package/fixtures/rn-expo-commerce/package.json +31 -0
  24. package/fixtures/rn-expo-commerce/src/components/LegacyPromoBanner.tsx +11 -0
  25. package/fixtures/rn-expo-commerce/src/screens/ProductFeedScreen.tsx +48 -0
  26. package/fixtures/rn-expo-commerce/src/services/api.ts +15 -0
  27. package/index.html +21 -0
  28. package/package.json +75 -4
  29. package/server.ts +275 -0
  30. package/src/App.tsx +783 -0
  31. package/src/components/DeepAnalysisViews.tsx +526 -0
  32. package/src/components/MobileAndPlatformViews.tsx +625 -0
  33. package/src/components/OverviewAndExplorerViews.tsx +753 -0
  34. package/src/components/RepairAndGateViews.tsx +814 -0
  35. package/src/engine/aiEngine.test.ts +86 -0
  36. package/src/engine/aiEngine.ts +346 -0
  37. package/src/engine/analyzerEngine.test.ts +110 -0
  38. package/src/engine/analyzerEngine.ts +1579 -0
  39. package/src/engine/repairEngine.ts +545 -0
  40. package/src/index.css +44 -0
  41. package/src/index.ts +59 -0
  42. package/src/main.tsx +5 -0
  43. package/src/types/devintelligence.ts +310 -0
  44. package/tsconfig.json +28 -0
  45. 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
+ }