erne-universal 0.13.3 → 0.13.5

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.
@@ -991,93 +991,168 @@ const server = http.createServer(async (req, res) => {
991
991
  try {
992
992
  const projectDir = process.env.ERNE_PROJECT_DIR || process.cwd();
993
993
  const erneRoot = path.resolve(__dirname, '..');
994
+
995
+ // Step 1: Run quick audit for score + findings
994
996
  const { runAudit } = require(path.join(erneRoot, 'lib', 'audit'));
995
997
  const result = runAudit(projectDir);
996
998
 
997
- // Auto-generate markdown docs from audit data
999
+ // Step 2: Run deep scan to produce audit-data.json (drives doc generation)
1000
+ const docsDir = path.join(projectDir, 'erne-docs');
1001
+ fs.mkdirSync(docsDir, { recursive: true });
1002
+ try {
1003
+ const { runScan } = require(path.join(erneRoot, 'lib', 'audit-scanner'));
1004
+ const auditData = runScan(projectDir, { skipDepHealth: false, maxFiles: 500 });
1005
+ fs.writeFileSync(path.join(docsDir, 'audit-data.json'), JSON.stringify(auditData, null, 2));
1006
+ } catch { /* scanner may fail on some projects */ }
1007
+
1008
+ // Step 3: Generate all 12 markdown docs from audit-data.json
998
1009
  try {
999
- const docsDir = path.join(projectDir, 'erne-docs');
1000
- fs.mkdirSync(docsDir, { recursive: true });
1001
- const data = JSON.parse(fs.readFileSync(path.join(docsDir, 'audit-data.json'), 'utf-8'));
1010
+ const dataPath = path.join(docsDir, 'audit-data.json');
1011
+ const data = fs.existsSync(dataPath) ? JSON.parse(fs.readFileSync(dataPath, 'utf-8')) : {};
1002
1012
  const now = new Date().toISOString();
1003
1013
  const write = (name, lines) => fs.writeFileSync(path.join(docsDir, name), lines.join('\n'));
1004
1014
 
1005
- // audit-report
1015
+ // audit-report (always generate)
1006
1016
  const ar = [`# Audit Report\nGenerated: ${now}\n`];
1007
- if (data.meta)
1008
- ar.push(
1009
- `Score: ${data.meta.score ?? 'N/A'}`,
1010
- `Components: ${data.components?.length ?? 0}`,
1011
- `Hooks: ${data.hooks?.length ?? 0}`,
1012
- );
1017
+ ar.push(`Score: ${result.jsonReport?.score ?? data.meta?.score ?? 'N/A'}`);
1018
+ ar.push(`Components: ${data.components?.length ?? 0}`);
1019
+ ar.push(`Hooks: ${data.hooks?.length ?? 0}`);
1020
+ if (result.jsonReport?.strengths?.length) {
1021
+ ar.push(`\n## Strengths (${result.jsonReport.strengths.length})\n`);
1022
+ for (const s of result.jsonReport.strengths) ar.push(`- ${s.title}`);
1023
+ }
1024
+ if (result.jsonReport?.findings?.length) {
1025
+ ar.push(`\n## Findings (${result.jsonReport.findings.length})\n`);
1026
+ for (const f of result.jsonReport.findings) ar.push(`- [${f.severity}] ${f.title}: ${f.detail || ''}`);
1027
+ }
1013
1028
  write('audit-report.md', ar);
1014
1029
 
1015
- // stack-detection
1016
- if (data.config) {
1017
- const sd = [`# Stack Detection\nGenerated: ${now}\n`];
1018
- for (const [k, v] of Object.entries(data.config))
1019
- sd.push(`- **${k}**: ${typeof v === 'object' ? JSON.stringify(v) : v}`);
1020
- write('stack-detection.md', sd);
1030
+ // stack-detection (always generate)
1031
+ const sd = [`# Stack Detection\nGenerated: ${now}\n`];
1032
+ const stack = data.config || result.jsonReport?.stack || {};
1033
+ for (const [k, v] of Object.entries(stack))
1034
+ sd.push(`- **${k}**: ${typeof v === 'object' ? JSON.stringify(v) : v}`);
1035
+ if (result.jsonReport?.meta) {
1036
+ sd.push('');
1037
+ for (const [k, v] of Object.entries(result.jsonReport.meta))
1038
+ sd.push(`- **${k}**: ${v}`);
1021
1039
  }
1022
-
1023
- // dependency-report
1024
- if (data.dependencies) {
1025
- const dr = [`# Dependency Report\nGenerated: ${now}\n`];
1026
- const d = data.dependencies;
1027
- dr.push(`Total: ${d.production?.length ?? 0} production, ${d.dev?.length ?? 0} dev\n`);
1028
- if (d.outdated?.length) {
1029
- dr.push(`## Outdated (${d.outdated.length})\n`);
1030
- for (const o of d.outdated.slice(0, 30))
1031
- dr.push(`- ${o.name}: ${o.current} → ${o.latest}`);
1032
- }
1033
- write('dependency-report.md', dr);
1040
+ write('stack-detection.md', sd);
1041
+
1042
+ // dependency-report (always generate)
1043
+ const dr = [`# Dependency Report\nGenerated: ${now}\n`];
1044
+ const deps = data.dependencies || {};
1045
+ dr.push(`Total: ${deps.production?.length ?? 0} production, ${deps.dev?.length ?? 0} dev\n`);
1046
+ if (deps.production?.length) {
1047
+ dr.push('## Production Dependencies\n');
1048
+ for (const d of deps.production.slice(0, 50)) dr.push(`- ${d.name || d}: ${d.version || ''}`);
1034
1049
  }
1050
+ if (deps.outdated?.length) {
1051
+ dr.push(`\n## Outdated (${deps.outdated.length})\n`);
1052
+ for (const o of deps.outdated.slice(0, 30)) dr.push(`- ${o.name}: ${o.current} → ${o.latest}`);
1053
+ }
1054
+ write('dependency-report.md', dr);
1035
1055
 
1036
- // dead-code
1056
+ // dead-code (always generate)
1057
+ const dc = [`# Dead Code Report\nGenerated: ${now}\n`];
1037
1058
  if (data.deadCode?.length) {
1038
- const dc = [`# Dead Code\nGenerated: ${now}\nFound: ${data.deadCode.length}\n`];
1039
- for (const d of data.deadCode.slice(0, 50))
1040
- dc.push(`- \`${d.name}\` in ${d.file} (${d.type})`);
1041
- write('dead-code.md', dc);
1059
+ dc.push(`Found: ${data.deadCode.length} unused exports\n`);
1060
+ for (const d of data.deadCode.slice(0, 50)) dc.push(`- \`${d.name}\` in ${d.file} (${d.type})`);
1061
+ } else {
1062
+ dc.push('No dead code detected.');
1042
1063
  }
1064
+ write('dead-code.md', dc);
1043
1065
 
1044
- // todos
1066
+ // todos (always generate)
1067
+ const td = [`# TODOs & Tech Debt\nGenerated: ${now}\n`];
1045
1068
  if (data.techDebt?.length) {
1046
- const td = [`# TODOs & Tech Debt\nGenerated: ${now}\n`];
1047
1069
  for (const t of data.techDebt.slice(0, 50))
1048
1070
  td.push(`- **${t.type || 'TODO'}** ${t.file}:${t.line || '?'} — ${t.text || ''}`);
1049
- write('todos.md', td);
1050
- }
1051
-
1052
- // changelog
1053
- if (data.gitHistory?.length) {
1054
- const cl = [`# Changelog\nGenerated: ${now}\n`];
1055
- for (const c of data.gitHistory.slice(0, 30))
1056
- cl.push(`- ${(c.hash || '').slice(0, 7)} ${c.message || ''} (${c.date || ''})`);
1057
- write('changelog.md', cl);
1071
+ } else {
1072
+ td.push('No TODOs or tech debt markers found.');
1058
1073
  }
1074
+ write('todos.md', td);
1059
1075
 
1060
- // type-coverage
1061
- if (data.typeSafety) {
1062
- const tc = [`# Type Coverage\nGenerated: ${now}\n`];
1076
+ // type-coverage (always generate)
1077
+ const tc = [`# Type Coverage\nGenerated: ${now}\n`];
1078
+ if (data.typeSafety && Object.keys(data.typeSafety).length) {
1063
1079
  for (const [k, v] of Object.entries(data.typeSafety))
1064
1080
  tc.push(`- **${k}**: ${typeof v === 'object' ? JSON.stringify(v) : v}`);
1065
- write('type-coverage.md', tc);
1081
+ } else {
1082
+ tc.push('No TypeScript coverage data available.');
1066
1083
  }
1084
+ write('type-coverage.md', tc);
1067
1085
 
1068
- // architecture
1069
- if (data.structure) {
1070
- const arch = [`# Architecture\nGenerated: ${now}\n`];
1071
- if (data.structure.dirs) arch.push(`Directories: ${data.structure.dirs.length}\n`);
1072
- if (data.routes?.length) {
1073
- arch.push(`## Routes (${data.routes.length})\n`);
1074
- for (const r of data.routes.slice(0, 30)) arch.push(`- ${r.path || r.file || r}`);
1075
- }
1076
- write('architecture.md', arch);
1086
+ // test-coverage (always generate)
1087
+ const testc = [`# Test Coverage\nGenerated: ${now}\n`];
1088
+ if (data.testCoverage) {
1089
+ for (const [k, v] of Object.entries(data.testCoverage))
1090
+ testc.push(`- **${k}**: ${typeof v === 'object' ? JSON.stringify(v) : v}`);
1091
+ } else {
1092
+ testc.push('No test coverage data. Configure a testing framework to enable coverage reports.');
1077
1093
  }
1078
- } catch {
1079
- /* best-effort */
1080
- }
1094
+ write('test-coverage.md', testc);
1095
+
1096
+ // security-report (always generate)
1097
+ const sr = [`# Security Report\nGenerated: ${now}\n`];
1098
+ const secFindings = (result.jsonReport?.findings || []).filter(f => f.category === 'Security');
1099
+ if (secFindings.length) {
1100
+ for (const f of secFindings) sr.push(`- [${f.severity}] ${f.title}: ${f.detail || ''}`);
1101
+ } else {
1102
+ sr.push('No security issues detected.');
1103
+ }
1104
+ write('security-report.md', sr);
1105
+
1106
+ // performance-report (always generate)
1107
+ const pr = [`# Performance Report\nGenerated: ${now}\n`];
1108
+ const perfFindings = (result.jsonReport?.findings || []).filter(f => f.category === 'Performance');
1109
+ if (perfFindings.length) {
1110
+ for (const f of perfFindings) pr.push(`- [${f.severity}] ${f.title}: ${f.detail || ''}`);
1111
+ } else {
1112
+ pr.push('No performance issues detected.');
1113
+ }
1114
+ write('performance-report.md', pr);
1115
+
1116
+ // architecture (always generate)
1117
+ const arch = [`# Architecture\nGenerated: ${now}\n`];
1118
+ if (data.structure?.dirs) {
1119
+ arch.push(`## Directories (${data.structure.dirs.length})\n`);
1120
+ for (const d of data.structure.dirs.slice(0, 30)) arch.push(`- ${d}`);
1121
+ }
1122
+ if (data.routes?.length) {
1123
+ arch.push(`\n## Routes (${data.routes.length})\n`);
1124
+ for (const r of data.routes.slice(0, 30)) arch.push(`- ${r.path || r.file || r}`);
1125
+ }
1126
+ if (data.screens?.length) {
1127
+ arch.push(`\n## Screens (${data.screens.length})\n`);
1128
+ for (const s of data.screens.slice(0, 30)) arch.push(`- ${s.name || s.file || s}`);
1129
+ }
1130
+ if (!data.structure && !data.routes && !data.screens) {
1131
+ arch.push('Run a deeper audit scan for architecture analysis.');
1132
+ }
1133
+ write('architecture.md', arch);
1134
+
1135
+ // api-surface (always generate)
1136
+ const api = [`# API Surface\nGenerated: ${now}\n`];
1137
+ if (data.apiLayer?.length) {
1138
+ for (const a of data.apiLayer.slice(0, 50))
1139
+ api.push(`- \`${a.method || 'GET'} ${a.path || a.url || a.name}\` in ${a.file || 'unknown'}`);
1140
+ } else {
1141
+ api.push('No API endpoints detected.');
1142
+ }
1143
+ write('api-surface.md', api);
1144
+
1145
+ // changelog (always generate)
1146
+ const cl = [`# Changelog\nGenerated: ${now}\n`];
1147
+ if (data.gitHistory?.length) {
1148
+ for (const c of data.gitHistory.slice(0, 50))
1149
+ cl.push(`- ${(c.hash || '').slice(0, 7)} ${c.message || ''} (${c.date || ''})`);
1150
+ } else {
1151
+ cl.push('No git history data available.');
1152
+ }
1153
+ write('changelog.md', cl);
1154
+
1155
+ } catch { /* doc generation is best-effort */ }
1081
1156
 
1082
1157
  res.writeHead(200, { 'Content-Type': 'application/json' });
1083
1158
  res.end(JSON.stringify(result.jsonReport));
package/lib/audit.js CHANGED
@@ -159,8 +159,8 @@ function checkSecurity(cwd, pkg, deps, findings, strengths) {
159
159
  severity: SEVERITY.warning,
160
160
  category: CATEGORY.security,
161
161
  title: 'AsyncStorage used without secure storage',
162
- detail: 'Sensitive data like auth tokens should use expo-secure-store or react-native-keychain.',
163
- fix: 'npm install expo-secure-store',
162
+ detail: 'Sensitive data like auth tokens should use a secure storage solution, not AsyncStorage.',
163
+ fix: detection.framework === 'bare-rn' ? 'npm install react-native-keychain' : 'npx expo install expo-secure-store',
164
164
  });
165
165
  }
166
166
 
@@ -192,6 +192,7 @@ function checkPerformance(cwd, deps, detection, findings, strengths) {
192
192
  }
193
193
 
194
194
  // Image optimization
195
+ const isExpo = detection.framework === 'expo-managed' || detection.framework === 'expo-bare';
195
196
  if (deps['expo-image']) {
196
197
  strengths.push({ category: CATEGORY.performance, title: 'expo-image for optimized image loading' });
197
198
  } else if (deps['react-native-fast-image']) {
@@ -201,8 +202,10 @@ function checkPerformance(cwd, deps, detection, findings, strengths) {
201
202
  severity: SEVERITY.info,
202
203
  category: CATEGORY.performance,
203
204
  title: 'No optimized image library detected',
204
- detail: 'expo-image or react-native-fast-image provide better caching and performance.',
205
- fix: 'npm install expo-image',
205
+ detail: isExpo
206
+ ? 'expo-image provides better caching and performance than the built-in Image component.'
207
+ : 'react-native-fast-image provides better caching and performance than the built-in Image component.',
208
+ fix: isExpo ? 'npx expo install expo-image' : 'npm install react-native-fast-image',
206
209
  });
207
210
  }
208
211
 
@@ -283,7 +286,7 @@ function checkArchitecture(cwd, deps, detection, findings, strengths) {
283
286
  category: CATEGORY.codeQuality,
284
287
  title: 'No TypeScript configuration found',
285
288
  detail: 'TypeScript significantly reduces runtime errors in React Native apps.',
286
- fix: 'npx expo install typescript @types/react',
289
+ fix: detection.framework === 'bare-rn' ? 'npm install --save-dev typescript @types/react' : 'npx expo install typescript @types/react',
287
290
  });
288
291
  }
289
292
 
@@ -354,7 +357,9 @@ function checkTesting(cwd, deps, detection, findings, strengths) {
354
357
  detail: hasTestingLib ? 'Testing library installed but no tests written.' : 'No testing framework or tests found.',
355
358
  fix: hasTestingLib
356
359
  ? 'Create __tests__/ directory and add component tests'
357
- : 'npx expo install jest @testing-library/react-native jest-expo',
360
+ : detection.framework === 'bare-rn'
361
+ ? 'npm install --save-dev jest @testing-library/react-native react-test-renderer'
362
+ : 'npx expo install jest @testing-library/react-native jest-expo',
358
363
  });
359
364
  }
360
365
 
@@ -493,7 +498,7 @@ function checkCodeQuality(cwd, deps, detection, findings, strengths) {
493
498
  category: CATEGORY.codeQuality,
494
499
  title: 'No ESLint configuration detected',
495
500
  detail: 'Linting helps maintain consistent code quality.',
496
- fix: 'npx expo lint',
501
+ fix: detection.framework === 'bare-rn' ? 'npm install --save-dev eslint @react-native/eslint-config' : 'npx expo lint',
497
502
  });
498
503
  }
499
504
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "erne-universal",
3
- "version": "0.13.3",
3
+ "version": "0.13.5",
4
4
  "description": "Complete AI coding agent harness for React Native and Expo \u2014 13 specialized agents, autonomous worker mode, visual debugging, smart routing",
5
5
  "keywords": [
6
6
  "react-native",