pumuki-ast-hooks 5.5.53 → 5.5.54
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/README.md +0 -9
- package/docs/MCP_SERVERS.md +16 -2
- package/docs/RELEASE_NOTES.md +34 -0
- package/package.json +1 -1
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +40 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +9 -205
- package/scripts/hooks-system/application/services/installation/mcp/McpGlobalConfigCleaner.js +49 -0
- package/scripts/hooks-system/application/services/installation/mcp/McpProjectConfigWriter.js +59 -0
- package/scripts/hooks-system/application/services/installation/mcp/McpServerConfigBuilder.js +103 -0
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +13 -8
- package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +15 -8
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDChecks.js +385 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDRules.js +38 -408
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMChecks.js +408 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMRules.js +36 -442
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenExtractor.js +146 -0
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenParser.js +22 -190
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenRunner.js +62 -0
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +27 -9
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
const { pushFinding } = require('../../ast-core');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function buildContext(base) {
|
|
6
|
+
return {
|
|
7
|
+
...base,
|
|
8
|
+
setPackageSwiftPath: (p) => { base.packageSwiftPath = p; },
|
|
9
|
+
getPackageSwiftPath: () => base.packageSwiftPath
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function checkPackageSwiftExists(ctx) {
|
|
14
|
+
const packagePath = path.join(ctx.projectRoot, 'Package.swift');
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(packagePath)) {
|
|
17
|
+
const swiftFiles = ctx.findSwiftFiles();
|
|
18
|
+
|
|
19
|
+
if (swiftFiles.length > 50) {
|
|
20
|
+
pushFinding(ctx.findings, {
|
|
21
|
+
ruleId: 'ios.spm.missing_package_swift',
|
|
22
|
+
severity: 'medium',
|
|
23
|
+
message: `Proyecto con ${swiftFiles.length} archivos Swift sin Package.swift. Considerar modularización con SPM.`,
|
|
24
|
+
filePath: 'PROJECT_ROOT',
|
|
25
|
+
line: 1,
|
|
26
|
+
suggestion: `Crear Package.swift para modularizar:
|
|
27
|
+
|
|
28
|
+
swift package init --type library
|
|
29
|
+
swift package init --type executable`
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
ctx.setPackageSwiftPath(packagePath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function checkFeatureModulesStructure(ctx) {
|
|
38
|
+
const sources = path.join(ctx.projectRoot, 'Sources');
|
|
39
|
+
if (!fs.existsSync(sources)) return;
|
|
40
|
+
|
|
41
|
+
const modules = fs.readdirSync(sources).filter(dir => fs.statSync(path.join(sources, dir)).isDirectory());
|
|
42
|
+
const featureModules = modules.filter(m => m.startsWith('Feature'));
|
|
43
|
+
const coreModules = modules.filter(m => m.startsWith('Core'));
|
|
44
|
+
|
|
45
|
+
if (modules.length > 5 && featureModules.length === 0) {
|
|
46
|
+
pushFinding(ctx.findings, {
|
|
47
|
+
ruleId: 'ios.spm.missing_feature_modules',
|
|
48
|
+
severity: 'medium',
|
|
49
|
+
message: `${modules.length} módulos sin naming convention Feature*. Considerar renombrar.`,
|
|
50
|
+
filePath: 'Sources/',
|
|
51
|
+
line: 1,
|
|
52
|
+
suggestion: `Naming convention recomendado:
|
|
53
|
+
|
|
54
|
+
Sources/
|
|
55
|
+
├── FeatureOrders/
|
|
56
|
+
├── FeatureUsers/
|
|
57
|
+
├── FeatureAuth/
|
|
58
|
+
├── CoreNetworking/
|
|
59
|
+
├── CoreDatabase/
|
|
60
|
+
└── CoreUI/`
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (modules.length > 10 && coreModules.length === 0) {
|
|
65
|
+
pushFinding(ctx.findings, {
|
|
66
|
+
ruleId: 'ios.spm.missing_core_modules',
|
|
67
|
+
severity: 'medium',
|
|
68
|
+
message: 'Proyecto grande sin módulos Core*. Considerar extraer código compartido.',
|
|
69
|
+
filePath: 'Sources/',
|
|
70
|
+
line: 1
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function checkCoreModulesStructure(ctx) {
|
|
76
|
+
const recommendedCoreModules = ['CoreNetworking', 'CoreDatabase', 'CoreUI', 'CoreModels'];
|
|
77
|
+
const sources = path.join(ctx.projectRoot, 'Sources');
|
|
78
|
+
if (!fs.existsSync(sources)) return;
|
|
79
|
+
|
|
80
|
+
const existingModules = fs.readdirSync(sources);
|
|
81
|
+
const missingCore = recommendedCoreModules.filter(core => !existingModules.includes(core));
|
|
82
|
+
|
|
83
|
+
if (missingCore.length > 0 && existingModules.length > 8) {
|
|
84
|
+
pushFinding(ctx.findings, {
|
|
85
|
+
ruleId: 'ios.spm.recommended_core_modules_missing',
|
|
86
|
+
severity: 'low',
|
|
87
|
+
message: `Módulos Core recomendados faltantes: ${missingCore.join(', ')}`,
|
|
88
|
+
filePath: 'Sources/',
|
|
89
|
+
line: 1,
|
|
90
|
+
suggestion: 'CoreNetworking, CoreDatabase, CoreUI ayudan a organizar código compartido'
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function checkPublicAPIExposure(ctx) {
|
|
96
|
+
const swiftFiles = ctx.findSwiftFiles();
|
|
97
|
+
|
|
98
|
+
swiftFiles.forEach(file => {
|
|
99
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
100
|
+
|
|
101
|
+
const publicCount = (content.match(/\bpublic\s+(class|struct|enum|func|var|let)/g) || []).length;
|
|
102
|
+
const totalDeclarations = (content.match(/\b(class|struct|enum|func)\s+\w+/g) || []).length;
|
|
103
|
+
|
|
104
|
+
if (totalDeclarations > 0) {
|
|
105
|
+
const publicPercentage = (publicCount / totalDeclarations) * 100;
|
|
106
|
+
|
|
107
|
+
if (publicPercentage > 70) {
|
|
108
|
+
pushFinding(ctx.findings, {
|
|
109
|
+
ruleId: 'ios.spm.excessive_public_api',
|
|
110
|
+
severity: 'medium',
|
|
111
|
+
message: `${publicPercentage.toFixed(0)}% de declaraciones son public. Minimizar API pública del módulo.`,
|
|
112
|
+
filePath: file,
|
|
113
|
+
line: 1,
|
|
114
|
+
suggestion: `Usar internal por defecto, public solo para API externa:
|
|
115
|
+
|
|
116
|
+
// ❌ Todo public
|
|
117
|
+
public class Helper { ... }
|
|
118
|
+
public func process() { ... }
|
|
119
|
+
|
|
120
|
+
internal class Helper { ... }
|
|
121
|
+
public func process() { ... } // Solo lo necesario`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (file.includes('/API/') && !content.includes('public ')) {
|
|
127
|
+
pushFinding(ctx.findings, {
|
|
128
|
+
ruleId: 'ios.spm.api_module_not_public',
|
|
129
|
+
severity: 'medium',
|
|
130
|
+
message: 'Módulo API sin declaraciones public. API module debe exponer funcionalidad.',
|
|
131
|
+
filePath: file,
|
|
132
|
+
line: 1
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function checkModuleDependencies(ctx) {
|
|
139
|
+
const packageSwiftPath = ctx.getPackageSwiftPath();
|
|
140
|
+
if (!packageSwiftPath) return;
|
|
141
|
+
|
|
142
|
+
const content = fs.readFileSync(packageSwiftPath, 'utf-8');
|
|
143
|
+
const targets = content.match(/\.target\([\s\S]*?name:\s*"([^"]+)"[\s\S]*?dependencies:\s*\[([\s\S]*?)\]/g);
|
|
144
|
+
|
|
145
|
+
if (!targets) return;
|
|
146
|
+
|
|
147
|
+
const dependencies = new Map();
|
|
148
|
+
targets.forEach(target => {
|
|
149
|
+
const name = target.match(/name:\s*"([^"]+)"/)?.[1];
|
|
150
|
+
const deps = target.match(/dependencies:\s*\[([\s\S]*?)\]/)?.[1];
|
|
151
|
+
if (name && deps) {
|
|
152
|
+
const depList = deps.match(/"([^"]+)"/g)?.map(d => d.replace(/"/g, '')) || [];
|
|
153
|
+
dependencies.set(name, depList);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
dependencies.forEach((deps, moduleName) => {
|
|
158
|
+
deps.forEach(dep => {
|
|
159
|
+
const depDeps = dependencies.get(dep) || [];
|
|
160
|
+
if (depDeps.includes(moduleName)) {
|
|
161
|
+
pushFinding(ctx.findings, {
|
|
162
|
+
ruleId: 'ios.spm.circular_dependency',
|
|
163
|
+
severity: 'critical',
|
|
164
|
+
message: `Dependencia circular detectada: ${moduleName} ↔ ${dep}`,
|
|
165
|
+
filePath: 'Package.swift',
|
|
166
|
+
line: 1,
|
|
167
|
+
suggestion: 'Romper dependencia circular extrayendo código compartido a módulo Core'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
dependencies.forEach((deps, moduleName) => {
|
|
174
|
+
if (!moduleName.startsWith('Feature')) return;
|
|
175
|
+
const featureDeps = deps.filter(d => d.startsWith('Feature'));
|
|
176
|
+
if (featureDeps.length > 0) {
|
|
177
|
+
pushFinding(ctx.findings, {
|
|
178
|
+
ruleId: 'ios.spm.feature_to_feature_dependency',
|
|
179
|
+
severity: 'high',
|
|
180
|
+
message: `Feature module '${moduleName}' depende de otro Feature: ${featureDeps.join(', ')}. Extraer a Core.`,
|
|
181
|
+
filePath: 'Package.swift',
|
|
182
|
+
line: 1,
|
|
183
|
+
suggestion: 'Features NO deben depender entre sí. Usar Core modules para compartir código.'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function checkCrossModuleViolations(ctx) {
|
|
190
|
+
const swiftFiles = ctx.findSwiftFiles();
|
|
191
|
+
|
|
192
|
+
swiftFiles.forEach(file => {
|
|
193
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
194
|
+
|
|
195
|
+
const internalImports = content.match(/@_implementationOnly\s+import\s+(\w+)/g);
|
|
196
|
+
if (internalImports) {
|
|
197
|
+
pushFinding(ctx.findings, {
|
|
198
|
+
ruleId: 'ios.spm.implementation_only_import',
|
|
199
|
+
severity: 'low',
|
|
200
|
+
message: '@_implementationOnly import detectado. Es válido pero indica posible leak de abstracción.',
|
|
201
|
+
filePath: file,
|
|
202
|
+
line: ctx.findLineNumber(content, '@_implementationOnly'),
|
|
203
|
+
suggestion: 'Verificar si el import es realmente necesario o hay leak de abstracción'
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (content.includes('@testable import') && !file.includes('Tests/') && !file.includes('Test.swift')) {
|
|
208
|
+
pushFinding(ctx.findings, {
|
|
209
|
+
ruleId: 'ios.spm.testable_import_in_production',
|
|
210
|
+
severity: 'high',
|
|
211
|
+
message: '@testable import en código de producción. Solo debe usarse en tests.',
|
|
212
|
+
filePath: file,
|
|
213
|
+
line: ctx.findLineNumber(content, '@testable import')
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function checkPackageSwiftConfiguration(ctx) {
|
|
220
|
+
const packageSwiftPath = ctx.getPackageSwiftPath();
|
|
221
|
+
if (!packageSwiftPath) return;
|
|
222
|
+
|
|
223
|
+
const content = fs.readFileSync(packageSwiftPath, 'utf-8');
|
|
224
|
+
const toolsVersion = content.match(/\/\/\s*swift-tools-version:\s*(\d+\.\d+)/)?.[1];
|
|
225
|
+
if (toolsVersion && parseFloat(toolsVersion) < 5.9) {
|
|
226
|
+
pushFinding(ctx.findings, {
|
|
227
|
+
ruleId: 'ios.spm.outdated_tools_version',
|
|
228
|
+
severity: 'medium',
|
|
229
|
+
message: `Swift tools version ${toolsVersion} desactualizado. Actualizar a 5.9+`,
|
|
230
|
+
filePath: 'Package.swift',
|
|
231
|
+
line: 1
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!content.includes('platforms:')) {
|
|
236
|
+
pushFinding(ctx.findings, {
|
|
237
|
+
ruleId: 'ios.spm.missing_platforms',
|
|
238
|
+
severity: 'medium',
|
|
239
|
+
message: 'Package.swift sin platforms: especificado. Definir versión mínima de iOS.',
|
|
240
|
+
filePath: 'Package.swift',
|
|
241
|
+
line: 1,
|
|
242
|
+
suggestion: 'platforms: [.iOS(.v15), .macOS(.v12)]'
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function checkTargetNaming(ctx) {
|
|
248
|
+
const packageSwiftPath = ctx.getPackageSwiftPath();
|
|
249
|
+
if (!packageSwiftPath) return;
|
|
250
|
+
|
|
251
|
+
const content = fs.readFileSync(packageSwiftPath, 'utf-8');
|
|
252
|
+
const targets = content.match(/\.target\([\s\S]*?name:\s*"([^"]+)"/g);
|
|
253
|
+
if (!targets) return;
|
|
254
|
+
|
|
255
|
+
const targetNames = targets.map(t => t.match(/name:\s*"([^"]+)"/)?.[1]).filter(Boolean);
|
|
256
|
+
const hasInconsistentNaming = targetNames.some(name =>
|
|
257
|
+
name.includes('_') || name.includes('-') || /[a-z][A-Z]/.test(name)
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (hasInconsistentNaming) {
|
|
261
|
+
pushFinding(ctx.findings, {
|
|
262
|
+
ruleId: 'ios.spm.inconsistent_target_naming',
|
|
263
|
+
severity: 'low',
|
|
264
|
+
message: 'Naming inconsistente en targets. Usar PascalCase sin separadores.',
|
|
265
|
+
filePath: 'Package.swift',
|
|
266
|
+
line: 1,
|
|
267
|
+
suggestion: 'FeatureOrders, CoreNetworking (no Feature-Orders, Core_Networking)'
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function checkProductConfiguration(ctx) {
|
|
273
|
+
const packageSwiftPath = ctx.getPackageSwiftPath();
|
|
274
|
+
if (!packageSwiftPath) return;
|
|
275
|
+
|
|
276
|
+
const content = fs.readFileSync(packageSwiftPath, 'utf-8');
|
|
277
|
+
|
|
278
|
+
if (!content.includes('.library(')) {
|
|
279
|
+
pushFinding(ctx.findings, {
|
|
280
|
+
ruleId: 'ios.spm.missing_products',
|
|
281
|
+
severity: 'medium',
|
|
282
|
+
message: 'Package.swift sin products definidos. Definir libraries para exponer módulos.',
|
|
283
|
+
filePath: 'Package.swift',
|
|
284
|
+
line: 1,
|
|
285
|
+
suggestion: `.library(name: "FeatureOrders", targets: ["FeatureOrders"])`
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function checkDependencyVersions(ctx) {
|
|
291
|
+
const packageSwiftPath = ctx.getPackageSwiftPath();
|
|
292
|
+
if (!packageSwiftPath) return;
|
|
293
|
+
|
|
294
|
+
const content = fs.readFileSync(packageSwiftPath, 'utf-8');
|
|
295
|
+
if (content.includes('.branch(')) {
|
|
296
|
+
pushFinding(ctx.findings, {
|
|
297
|
+
ruleId: 'ios.spm.dependency_branch_instead_version',
|
|
298
|
+
severity: 'high',
|
|
299
|
+
message: 'Dependencia usando .branch() en lugar de versión específica. Usar .upToNextMajor o .exact.',
|
|
300
|
+
filePath: 'Package.swift',
|
|
301
|
+
line: ctx.findLineNumber(content, '.branch('),
|
|
302
|
+
suggestion: `Usar versiones semánticas:
|
|
303
|
+
|
|
304
|
+
// ❌ Inestable
|
|
305
|
+
.package(url: "...", branch: "main")
|
|
306
|
+
|
|
307
|
+
// ✅ Estable
|
|
308
|
+
.package(url: "...", from: "1.0.0")
|
|
309
|
+
.package(url: "...", .upToNextMajor(from: "1.0.0"))`
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function checkTestTargets(ctx) {
|
|
315
|
+
const packageSwiftPath = ctx.getPackageSwiftPath();
|
|
316
|
+
if (!packageSwiftPath) return;
|
|
317
|
+
|
|
318
|
+
const content = fs.readFileSync(packageSwiftPath, 'utf-8');
|
|
319
|
+
const targets = content.match(/\.target\([\s\S]*?name:\s*"([^"]+)"/g) || [];
|
|
320
|
+
const testTargets = content.match(/\.testTarget\([\s\S]*?name:\s*"([^"]+)"/g) || [];
|
|
321
|
+
|
|
322
|
+
const targetNames = targets.map(t => t.match(/name:\s*"([^"]+)"/)?.[1]).filter(Boolean);
|
|
323
|
+
const testTargetNames = testTargets.map(t => t.match(/name:\s*"([^"]+)"/)?.[1]).filter(Boolean);
|
|
324
|
+
|
|
325
|
+
const targetsWithoutTests = targetNames.filter(name =>
|
|
326
|
+
!testTargetNames.includes(`${name}Tests`) && !name.includes('Tests')
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (targetsWithoutTests.length > 0) {
|
|
330
|
+
pushFinding(ctx.findings, {
|
|
331
|
+
ruleId: 'ios.spm.targets_without_tests',
|
|
332
|
+
severity: 'medium',
|
|
333
|
+
message: `${targetsWithoutTests.length} targets sin test targets: ${targetsWithoutTests.slice(0, 3).join(', ')}`,
|
|
334
|
+
filePath: 'Package.swift',
|
|
335
|
+
line: 1,
|
|
336
|
+
suggestion: `Añadir test targets:
|
|
337
|
+
|
|
338
|
+
.testTarget(
|
|
339
|
+
name: "FeatureOrdersTests",
|
|
340
|
+
dependencies: ["FeatureOrders"]
|
|
341
|
+
)`
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function checkModuleBoundaries(ctx) {
|
|
347
|
+
const sources = path.join(ctx.projectRoot, 'Sources');
|
|
348
|
+
if (!fs.existsSync(sources)) return;
|
|
349
|
+
|
|
350
|
+
const modules = fs.readdirSync(sources).filter(dir => fs.statSync(path.join(sources, dir)).isDirectory());
|
|
351
|
+
|
|
352
|
+
modules.forEach(moduleName => {
|
|
353
|
+
const modulePath = path.join(sources, moduleName);
|
|
354
|
+
const swiftFiles = ctx.findSwiftFilesInDirectory(modulePath);
|
|
355
|
+
|
|
356
|
+
swiftFiles.forEach(file => {
|
|
357
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
358
|
+
|
|
359
|
+
if (moduleName.startsWith('Feature')) {
|
|
360
|
+
modules.forEach(otherModule => {
|
|
361
|
+
if (otherModule.startsWith('Feature') && otherModule !== moduleName) {
|
|
362
|
+
if (content.includes(`import ${otherModule}`)) {
|
|
363
|
+
pushFinding(ctx.findings, {
|
|
364
|
+
ruleId: 'ios.spm.feature_imports_feature',
|
|
365
|
+
severity: 'high',
|
|
366
|
+
message: `Feature module '${moduleName}' importa otro Feature '${otherModule}'. Violación de boundaries.`,
|
|
367
|
+
filePath: file,
|
|
368
|
+
line: ctx.findLineNumber(content, `import ${otherModule}`),
|
|
369
|
+
suggestion: 'Extraer código compartido a Core module'
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (moduleName.startsWith('Core')) {
|
|
377
|
+
modules.forEach(featureModule => {
|
|
378
|
+
if (featureModule.startsWith('Feature') && content.includes(`import ${featureModule}`)) {
|
|
379
|
+
pushFinding(ctx.findings, {
|
|
380
|
+
ruleId: 'ios.spm.core_imports_feature',
|
|
381
|
+
severity: 'critical',
|
|
382
|
+
message: `Core module '${moduleName}' importa Feature '${featureModule}'. Dependencia invertida!`,
|
|
383
|
+
filePath: file,
|
|
384
|
+
line: ctx.findLineNumber(content, `import ${featureModule}`),
|
|
385
|
+
suggestion: 'Core NO debe depender de Features. Invertir dependencia.'
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
module.exports = {
|
|
395
|
+
buildContext,
|
|
396
|
+
checkPackageSwiftExists,
|
|
397
|
+
checkFeatureModulesStructure,
|
|
398
|
+
checkCoreModulesStructure,
|
|
399
|
+
checkPublicAPIExposure,
|
|
400
|
+
checkModuleDependencies,
|
|
401
|
+
checkCrossModuleViolations,
|
|
402
|
+
checkPackageSwiftConfiguration,
|
|
403
|
+
checkTargetNaming,
|
|
404
|
+
checkProductConfiguration,
|
|
405
|
+
checkDependencyVersions,
|
|
406
|
+
checkTestTargets,
|
|
407
|
+
checkModuleBoundaries,
|
|
408
|
+
};
|