pumuki 6.3.209 → 6.3.211
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/core/facts/detectors/text/ios.test.ts +56 -0
- package/core/facts/detectors/text/ios.ts +31 -0
- package/core/facts/extractHeuristicFacts.ts +2 -0
- package/core/rules/presets/heuristics/ios.ts +38 -0
- package/integrations/config/skillsDetectorRegistry.ts +16 -0
- package/integrations/config/skillsMarkdownRules.ts +17 -0
- package/package.json +1 -1
- package/skills.lock.json +6 -18
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
hasSwiftLegacyExpectationDescriptionUsage,
|
|
30
30
|
hasSwiftLegacySwiftUiObservableWrapperUsage,
|
|
31
31
|
hasSwiftMainThreadBlockingSleepUsage,
|
|
32
|
+
hasSwiftMassiveViewControllerResponsibilityUsage,
|
|
32
33
|
hasSwiftMixedTestingFrameworksUsage,
|
|
33
34
|
hasSwiftLegacyXCTestImportUsage,
|
|
34
35
|
hasSwiftModernizableXCTestSuiteUsage,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
hasSwiftNSManagedObjectBoundaryUsage,
|
|
41
42
|
hasSwiftNSManagedObjectStateLeakUsage,
|
|
42
43
|
hasSwiftNavigationViewUsage,
|
|
44
|
+
hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage,
|
|
43
45
|
hasSwiftObservableObjectUsage,
|
|
44
46
|
hasSwiftOnTapGestureUsage,
|
|
45
47
|
hasSwiftOperationQueueUsage,
|
|
@@ -283,6 +285,60 @@ final class APIClient {
|
|
|
283
285
|
assert.equal(hasSwiftCustomSingletonUsage(ignored), false);
|
|
284
286
|
});
|
|
285
287
|
|
|
288
|
+
test('hasSwiftMassiveViewControllerResponsibilityUsage detecta ViewControllers con acceso directo a infraestructura', () => {
|
|
289
|
+
const source = `
|
|
290
|
+
final class CheckoutViewController: UIViewController {
|
|
291
|
+
override func viewDidLoad() {
|
|
292
|
+
super.viewDidLoad()
|
|
293
|
+
URLSession.shared.dataTask(with: URL(string: "https://example.com")!)
|
|
294
|
+
UserDefaults.standard.set(true, forKey: "seen")
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
`;
|
|
298
|
+
const ignored = `
|
|
299
|
+
final class CheckoutViewController: UIViewController {
|
|
300
|
+
private let viewModel: CheckoutViewModel
|
|
301
|
+
|
|
302
|
+
init(viewModel: CheckoutViewModel) {
|
|
303
|
+
self.viewModel = viewModel
|
|
304
|
+
super.init(nibName: nil, bundle: nil)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
override func viewDidLoad() {
|
|
308
|
+
super.viewDidLoad()
|
|
309
|
+
viewModel.load()
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let text = "URLSession.shared.dataTask"
|
|
314
|
+
// UserDefaults.standard.set(true, forKey: "seen")
|
|
315
|
+
`;
|
|
316
|
+
|
|
317
|
+
assert.equal(hasSwiftMassiveViewControllerResponsibilityUsage(source), true);
|
|
318
|
+
assert.equal(hasSwiftMassiveViewControllerResponsibilityUsage(ignored), false);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage detecta IUO fuera de IBOutlet', () => {
|
|
322
|
+
const source = `
|
|
323
|
+
final class CheckoutViewModel {
|
|
324
|
+
var selectedOrder: Order!
|
|
325
|
+
private let formatter: DateFormatter!
|
|
326
|
+
}
|
|
327
|
+
`;
|
|
328
|
+
const ignored = `
|
|
329
|
+
final class CheckoutViewController: UIViewController {
|
|
330
|
+
@IBOutlet weak var titleLabel: UILabel!
|
|
331
|
+
@IBOutlet
|
|
332
|
+
private weak var tableView: UITableView!
|
|
333
|
+
let text = "var selectedOrder: Order!"
|
|
334
|
+
// var selectedOrder: Order!
|
|
335
|
+
}
|
|
336
|
+
`;
|
|
337
|
+
|
|
338
|
+
assert.equal(hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage(source), true);
|
|
339
|
+
assert.equal(hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage(ignored), false);
|
|
340
|
+
});
|
|
341
|
+
|
|
286
342
|
test('detectores de logging iOS detectan logs ad-hoc y PII en produccion', () => {
|
|
287
343
|
const adHoc = `
|
|
288
344
|
print(user.id)
|
|
@@ -528,6 +528,37 @@ export const hasSwiftCustomSingletonUsage = (source: string): boolean => {
|
|
|
528
528
|
});
|
|
529
529
|
};
|
|
530
530
|
|
|
531
|
+
export const hasSwiftMassiveViewControllerResponsibilityUsage = (source: string): boolean => {
|
|
532
|
+
const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
|
|
533
|
+
const viewControllerPattern =
|
|
534
|
+
/\bclass\s+[A-Za-z_][A-Za-z0-9_]*\s*:\s*(?:[A-Za-z_][A-Za-z0-9_]*\s*,\s*)*UIViewController\b[\s\S]{0,8000}?\n\}/g;
|
|
535
|
+
const infrastructurePattern =
|
|
536
|
+
/\b(?:URLSession\s*\.\s*shared|JSONSerialization|UserDefaults\s*\.\s*standard|NSManagedObjectContext|NSPersistentContainer|NSFetchRequest|FileManager\s*\.\s*default)\b/;
|
|
537
|
+
|
|
538
|
+
for (const match of sanitized.matchAll(viewControllerPattern)) {
|
|
539
|
+
const body = match[0] ?? '';
|
|
540
|
+
if (infrastructurePattern.test(body)) {
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return false;
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
export const hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage = (source: string): boolean => {
|
|
548
|
+
const lines = source.split(/\r?\n/);
|
|
549
|
+
const implicitlyUnwrappedPropertyPattern =
|
|
550
|
+
/\b(?:var|let)\s+[A-Za-z_][A-Za-z0-9_]*\s*:\s*(?:[A-Za-z_][A-Za-z0-9_.<>?]*\s*)!\s*(?:[=,{]|$)/;
|
|
551
|
+
|
|
552
|
+
return lines.some((line, index) => {
|
|
553
|
+
const sanitizedLine = stripSwiftLineForSemanticScan(line);
|
|
554
|
+
if (!implicitlyUnwrappedPropertyPattern.test(sanitizedLine)) {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
const previousLine = index > 0 ? stripSwiftLineForSemanticScan(lines[index - 1] ?? '') : '';
|
|
558
|
+
return !/\B@IBOutlet\b/.test(`${previousLine} ${sanitizedLine}`);
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
|
|
531
562
|
export const hasSwiftAdHocLoggingUsage = (source: string): boolean => {
|
|
532
563
|
return collectSwiftRegexLines(
|
|
533
564
|
source,
|
|
@@ -649,6 +649,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
649
649
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongDelegateReferenceUsage, ruleId: 'heuristics.ios.memory.strong-delegate.ast', code: 'HEURISTICS_IOS_MEMORY_STRONG_DELEGATE_AST', message: 'AST heuristic detected a strong delegate/dataSource reference; weak delegates remain the preferred baseline to avoid retain cycles.' },
|
|
650
650
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongSelfEscapingClosureUsage, ruleId: 'heuristics.ios.memory.strong-self-escaping-closure.ast', code: 'HEURISTICS_IOS_MEMORY_STRONG_SELF_ESCAPING_CLOSURE_AST', message: 'AST heuristic detected strong self capture in an escaping iOS closure; weak or unowned captures remain the preferred baseline when ownership is not explicit.' },
|
|
651
651
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCustomSingletonUsage, ruleId: 'heuristics.ios.architecture.custom-singleton.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_CUSTOM_SINGLETON_AST', message: 'AST heuristic detected a custom static shared singleton in iOS production code; dependency injection remains the preferred baseline for app-owned services.' },
|
|
652
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMassiveViewControllerResponsibilityUsage, ruleId: 'heuristics.ios.architecture.massive-view-controller.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_MASSIVE_VIEW_CONTROLLER_AST', message: 'AST heuristic detected a UIViewController with direct infrastructure/data access; move data access behind application/domain boundaries.' },
|
|
653
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage, ruleId: 'heuristics.ios.safety.non-iboutlet-iuo.ast', code: 'HEURISTICS_IOS_SAFETY_NON_IBOUTLET_IUO_AST', message: 'AST heuristic detected an implicitly unwrapped optional outside IBOutlet wiring; explicit optionals or initialization guarantees remain the preferred baseline.' },
|
|
652
654
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAdHocLoggingUsage, ruleId: 'heuristics.ios.logging.adhoc-print.ast', code: 'HEURISTICS_IOS_LOGGING_ADHOC_PRINT_AST', message: 'AST heuristic detected print/debugPrint/dump/NSLog/os_log usage in iOS production code.' },
|
|
653
655
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveLoggingUsage, ruleId: 'heuristics.ios.logging.sensitive-data.ast', code: 'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST', message: 'AST heuristic detected sensitive data in an iOS logging call.' },
|
|
654
656
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAlamofireUsage, ruleId: 'heuristics.ios.networking.alamofire.ast', code: 'HEURISTICS_IOS_NETWORKING_ALAMOFIRE_AST', message: 'AST heuristic detected Alamofire usage in iOS production code; URLSession remains the preferred baseline for new code.' },
|
|
@@ -238,6 +238,44 @@ export const iosRules: RuleSet = [
|
|
|
238
238
|
code: 'HEURISTICS_IOS_ARCHITECTURE_CUSTOM_SINGLETON_AST',
|
|
239
239
|
},
|
|
240
240
|
},
|
|
241
|
+
{
|
|
242
|
+
id: 'heuristics.ios.architecture.massive-view-controller.ast',
|
|
243
|
+
description: 'Detects UIViewController classes with direct infrastructure/data access.',
|
|
244
|
+
severity: 'WARN',
|
|
245
|
+
platform: 'ios',
|
|
246
|
+
locked: true,
|
|
247
|
+
when: {
|
|
248
|
+
kind: 'Heuristic',
|
|
249
|
+
where: {
|
|
250
|
+
ruleId: 'heuristics.ios.architecture.massive-view-controller.ast',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
then: {
|
|
254
|
+
kind: 'Finding',
|
|
255
|
+
message:
|
|
256
|
+
'AST heuristic detected a UIViewController with direct infrastructure/data access; move data access behind application/domain boundaries.',
|
|
257
|
+
code: 'HEURISTICS_IOS_ARCHITECTURE_MASSIVE_VIEW_CONTROLLER_AST',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'heuristics.ios.safety.non-iboutlet-iuo.ast',
|
|
262
|
+
description: 'Detects implicitly unwrapped optionals outside IBOutlet wiring.',
|
|
263
|
+
severity: 'WARN',
|
|
264
|
+
platform: 'ios',
|
|
265
|
+
locked: true,
|
|
266
|
+
when: {
|
|
267
|
+
kind: 'Heuristic',
|
|
268
|
+
where: {
|
|
269
|
+
ruleId: 'heuristics.ios.safety.non-iboutlet-iuo.ast',
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
then: {
|
|
273
|
+
kind: 'Finding',
|
|
274
|
+
message:
|
|
275
|
+
'AST heuristic detected an implicitly unwrapped optional outside IBOutlet wiring; explicit optionals or initialization guarantees remain the preferred baseline.',
|
|
276
|
+
code: 'HEURISTICS_IOS_SAFETY_NON_IBOUTLET_IUO_AST',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
241
279
|
{
|
|
242
280
|
id: 'heuristics.ios.logging.adhoc-print.ast',
|
|
243
281
|
description: 'Detects print/debugPrint/dump/NSLog/os_log usage in iOS production code.',
|
|
@@ -66,6 +66,22 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
66
66
|
'ios.architecture.custom-singleton',
|
|
67
67
|
['heuristics.ios.architecture.custom-singleton.ast']
|
|
68
68
|
),
|
|
69
|
+
'skills.ios.guideline.ios.massive-view-controllers-viewcontrollers-que-mezclan-presentacio-n-nav': heuristicDetector(
|
|
70
|
+
'ios.architecture.massive-view-controller',
|
|
71
|
+
['heuristics.ios.architecture.massive-view-controller.ast']
|
|
72
|
+
),
|
|
73
|
+
'skills.ios.guideline.ios.mvc-evitar-massive-view-controller-no-escalable': heuristicDetector(
|
|
74
|
+
'ios.architecture.massive-view-controller',
|
|
75
|
+
['heuristics.ios.architecture.massive-view-controller.ast']
|
|
76
|
+
),
|
|
77
|
+
'skills.ios.guideline.ios.implicitly-unwrapped-solo-para-iboutlets-y-casos-muy-especi-ficos': heuristicDetector(
|
|
78
|
+
'ios.safety.non-iboutlet-iuo',
|
|
79
|
+
['heuristics.ios.safety.non-iboutlet-iuo.ast']
|
|
80
|
+
),
|
|
81
|
+
'skills.ios.guideline.ios.singletons-dificultan-testing': heuristicDetector(
|
|
82
|
+
'ios.architecture.custom-singleton',
|
|
83
|
+
['heuristics.ios.architecture.custom-singleton.ast']
|
|
84
|
+
),
|
|
69
85
|
'skills.ios.guideline.ios.prohibido-print-y-logs-ad-hoc': heuristicDetector(
|
|
70
86
|
'ios.logging.adhoc-print',
|
|
71
87
|
['heuristics.ios.logging.adhoc-print.ast']
|
|
@@ -444,6 +444,23 @@ const normalizeKnownRuleTarget = (
|
|
|
444
444
|
) {
|
|
445
445
|
return 'skills.ios.guideline.ios.no-singleton-usar-inyeccio-n-de-dependencias-no-compartir-instancias-g';
|
|
446
446
|
}
|
|
447
|
+
if (
|
|
448
|
+
includes('massive view controller') ||
|
|
449
|
+
includes('massive view controllers') ||
|
|
450
|
+
includes('viewcontrollers que mezclan') ||
|
|
451
|
+
includes('viewcontroller que mezclan') ||
|
|
452
|
+
(includes('mvc') && includes('evitar'))
|
|
453
|
+
) {
|
|
454
|
+
return 'skills.ios.guideline.ios.massive-view-controllers-viewcontrollers-que-mezclan-presentacio-n-nav';
|
|
455
|
+
}
|
|
456
|
+
if (
|
|
457
|
+
includes('implicitly unwrapped') ||
|
|
458
|
+
includes('implicit unwrapped') ||
|
|
459
|
+
includes('iboutlet') ||
|
|
460
|
+
includes('iboutlets')
|
|
461
|
+
) {
|
|
462
|
+
return 'skills.ios.guideline.ios.implicitly-unwrapped-solo-para-iboutlets-y-casos-muy-especi-ficos';
|
|
463
|
+
}
|
|
447
464
|
if (
|
|
448
465
|
includes('mixing legacy xctest style') ||
|
|
449
466
|
includes('mixed xctest and swift testing') ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.211",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/skills.lock.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0",
|
|
3
3
|
"compilerVersion": "1.0.0",
|
|
4
|
-
"generatedAt": "2026-05-13T12:
|
|
4
|
+
"generatedAt": "2026-05-13T12:56:51.940Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -5764,7 +5764,7 @@
|
|
|
5764
5764
|
"name": "ios-guidelines",
|
|
5765
5765
|
"version": "1.0.0",
|
|
5766
5766
|
"source": "file:vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5767
|
-
"hash": "
|
|
5767
|
+
"hash": "1395678f8abd2b8eddb9d4270fd205517ed4ce4e3796def5f7846592990ec778",
|
|
5768
5768
|
"rules": [
|
|
5769
5769
|
{
|
|
5770
5770
|
"id": "skills.ios.guideline.ios.accessibility-identifiers-para-localizar-elementos",
|
|
@@ -6736,7 +6736,7 @@
|
|
|
6736
6736
|
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
6737
6737
|
"confidence": "MEDIUM",
|
|
6738
6738
|
"locked": true,
|
|
6739
|
-
"evaluationMode": "
|
|
6739
|
+
"evaluationMode": "AUTO",
|
|
6740
6740
|
"origin": "core"
|
|
6741
6741
|
},
|
|
6742
6742
|
{
|
|
@@ -6957,14 +6957,14 @@
|
|
|
6957
6957
|
},
|
|
6958
6958
|
{
|
|
6959
6959
|
"id": "skills.ios.guideline.ios.massive-view-controllers-viewcontrollers-que-mezclan-presentacio-n-nav",
|
|
6960
|
-
"description": "
|
|
6960
|
+
"description": "MVC (evitar) - Massive View Controller, no escalable",
|
|
6961
6961
|
"severity": "ERROR",
|
|
6962
6962
|
"platform": "ios",
|
|
6963
6963
|
"sourceSkill": "ios-guidelines",
|
|
6964
6964
|
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
6965
|
-
"confidence": "
|
|
6965
|
+
"confidence": "MEDIUM",
|
|
6966
6966
|
"locked": true,
|
|
6967
|
-
"evaluationMode": "
|
|
6967
|
+
"evaluationMode": "AUTO",
|
|
6968
6968
|
"origin": "core"
|
|
6969
6969
|
},
|
|
6970
6970
|
{
|
|
@@ -6991,18 +6991,6 @@
|
|
|
6991
6991
|
"evaluationMode": "DECLARATIVE",
|
|
6992
6992
|
"origin": "core"
|
|
6993
6993
|
},
|
|
6994
|
-
{
|
|
6995
|
-
"id": "skills.ios.guideline.ios.mvc-evitar-massive-view-controller-no-escalable",
|
|
6996
|
-
"description": "MVC (evitar) - Massive View Controller, no escalable",
|
|
6997
|
-
"severity": "ERROR",
|
|
6998
|
-
"platform": "ios",
|
|
6999
|
-
"sourceSkill": "ios-guidelines",
|
|
7000
|
-
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
7001
|
-
"confidence": "MEDIUM",
|
|
7002
|
-
"locked": true,
|
|
7003
|
-
"evaluationMode": "DECLARATIVE",
|
|
7004
|
-
"origin": "core"
|
|
7005
|
-
},
|
|
7006
6994
|
{
|
|
7007
6995
|
"id": "skills.ios.guideline.ios.mvvm-c-coordinator-para-navegacio-n",
|
|
7008
6996
|
"description": "MVVM-C - + Coordinator para navegación",
|