pumuki 6.3.202 → 6.3.203
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 +23 -0
- package/core/facts/detectors/text/ios.ts +15 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +18 -0
- package/docs/codex-skills/ios-enterprise-rules.md +5 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/integrations/config/skillsMarkdownRules.ts +9 -0
- package/package.json +1 -1
- package/skills.lock.json +3 -3
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
hasSwiftLegacyOnChangeUsage,
|
|
28
28
|
hasSwiftLegacyExpectationDescriptionUsage,
|
|
29
29
|
hasSwiftLegacySwiftUiObservableWrapperUsage,
|
|
30
|
+
hasSwiftMainThreadBlockingSleepUsage,
|
|
30
31
|
hasSwiftMixedTestingFrameworksUsage,
|
|
31
32
|
hasSwiftLegacyXCTestImportUsage,
|
|
32
33
|
hasSwiftModernizableXCTestSuiteUsage,
|
|
@@ -354,6 +355,28 @@ let sample = "TextAlignment.right"
|
|
|
354
355
|
assert.equal(hasSwiftPhysicalTextAlignmentUsage(ignored), false);
|
|
355
356
|
});
|
|
356
357
|
|
|
358
|
+
test('detector iOS de performance detecta sleeps bloqueantes sin confundir Task.sleep', () => {
|
|
359
|
+
const source = `
|
|
360
|
+
final class SplashDelay {
|
|
361
|
+
func wait() {
|
|
362
|
+
Thread.sleep(forTimeInterval: 0.25)
|
|
363
|
+
sleep(1)
|
|
364
|
+
usleep(100)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
`;
|
|
368
|
+
const ignored = `
|
|
369
|
+
func wait() async throws {
|
|
370
|
+
try await Task.sleep(for: .seconds(1))
|
|
371
|
+
let text = "Thread.sleep(forTimeInterval: 1)"
|
|
372
|
+
// sleep(1)
|
|
373
|
+
}
|
|
374
|
+
`;
|
|
375
|
+
|
|
376
|
+
assert.equal(hasSwiftMainThreadBlockingSleepUsage(source), true);
|
|
377
|
+
assert.equal(hasSwiftMainThreadBlockingSleepUsage(ignored), false);
|
|
378
|
+
});
|
|
379
|
+
|
|
357
380
|
test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
|
|
358
381
|
const source = `
|
|
359
382
|
final class LegacyBox: @unchecked Sendable {}
|
|
@@ -593,6 +593,21 @@ export const hasSwiftPhysicalTextAlignmentUsage = (source: string): boolean => {
|
|
|
593
593
|
});
|
|
594
594
|
};
|
|
595
595
|
|
|
596
|
+
export const hasSwiftMainThreadBlockingSleepUsage = (source: string): boolean => {
|
|
597
|
+
const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
|
|
598
|
+
return withoutBlockComments.split(/\r?\n/).some((line) => {
|
|
599
|
+
if (/^\s*\/\//.test(line)) {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
const sanitized = stripSwiftLineForSemanticScan(line);
|
|
603
|
+
return (
|
|
604
|
+
/\bThread\s*\.\s*sleep\s*\(/.test(sanitized) ||
|
|
605
|
+
/(^|[^\w.])sleep\s*\(/.test(sanitized) ||
|
|
606
|
+
/(^|[^\w.])usleep\s*\(/.test(sanitized)
|
|
607
|
+
);
|
|
608
|
+
});
|
|
609
|
+
};
|
|
610
|
+
|
|
596
611
|
export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
|
|
597
612
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
598
613
|
if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
|
|
@@ -658,6 +658,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
658
658
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLooseAssetResourceUsage, ruleId: 'heuristics.ios.assets.loose-resource.ast', code: 'HEURISTICS_IOS_ASSETS_LOOSE_RESOURCE_AST', message: 'AST heuristic detected loose image resource loading in iOS production code; Asset Catalogs remain the preferred baseline.' },
|
|
659
659
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftFixedFontSizeUsage, ruleId: 'heuristics.ios.accessibility.fixed-font-size.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST', message: 'AST heuristic detected fixed font sizing in iOS production code; Dynamic Type semantic text styles remain the preferred baseline.' },
|
|
660
660
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPhysicalTextAlignmentUsage, ruleId: 'heuristics.ios.localization.physical-text-alignment.ast', code: 'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST', message: 'AST heuristic detected physical left/right text alignment in iOS production code; leading/trailing remain the preferred RTL-safe baseline.' },
|
|
661
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMainThreadBlockingSleepUsage, ruleId: 'heuristics.ios.performance.blocking-sleep.ast', code: 'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST', message: 'AST heuristic detected blocking sleep usage in iOS production code; async clocks, suspension or cancellable scheduling remain the preferred baseline.' },
|
|
661
662
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUncheckedSendableUsage, ruleId: 'heuristics.ios.unchecked-sendable.ast', code: 'HEURISTICS_IOS_UNCHECKED_SENDABLE_AST', message: 'AST heuristic detected @unchecked Sendable usage.' },
|
|
662
663
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPreconcurrencyUsage, ruleId: 'heuristics.ios.preconcurrency.ast', code: 'HEURISTICS_IOS_PRECONCURRENCY_AST', message: 'AST heuristic detected @preconcurrency usage.' },
|
|
663
664
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonisolatedUnsafeUsage, ruleId: 'heuristics.ios.nonisolated-unsafe.ast', code: 'HEURISTICS_IOS_NONISOLATED_UNSAFE_AST', message: 'AST heuristic detected nonisolated(unsafe) usage.' },
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { iosRules } from './ios';
|
|
4
4
|
|
|
5
5
|
test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
6
|
-
assert.equal(iosRules.length,
|
|
6
|
+
assert.equal(iosRules.length, 57);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -30,6 +30,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
30
30
|
'heuristics.ios.assets.loose-resource.ast',
|
|
31
31
|
'heuristics.ios.accessibility.fixed-font-size.ast',
|
|
32
32
|
'heuristics.ios.localization.physical-text-alignment.ast',
|
|
33
|
+
'heuristics.ios.performance.blocking-sleep.ast',
|
|
33
34
|
'heuristics.ios.unchecked-sendable.ast',
|
|
34
35
|
'heuristics.ios.preconcurrency.ast',
|
|
35
36
|
'heuristics.ios.nonisolated-unsafe.ast',
|
|
@@ -126,6 +127,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
126
127
|
byId.get('heuristics.ios.localization.physical-text-alignment.ast')?.then.code,
|
|
127
128
|
'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST'
|
|
128
129
|
);
|
|
130
|
+
assert.equal(
|
|
131
|
+
byId.get('heuristics.ios.performance.blocking-sleep.ast')?.then.code,
|
|
132
|
+
'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST'
|
|
133
|
+
);
|
|
129
134
|
assert.equal(
|
|
130
135
|
byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
|
|
131
136
|
'HEURISTICS_IOS_PRECONCURRENCY_AST'
|
|
@@ -415,6 +415,24 @@ export const iosRules: RuleSet = [
|
|
|
415
415
|
code: 'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST',
|
|
416
416
|
},
|
|
417
417
|
},
|
|
418
|
+
{
|
|
419
|
+
id: 'heuristics.ios.performance.blocking-sleep.ast',
|
|
420
|
+
description: 'Detects blocking sleep calls where cancellable async scheduling is the preferred iOS baseline.',
|
|
421
|
+
severity: 'WARN',
|
|
422
|
+
platform: 'ios',
|
|
423
|
+
locked: true,
|
|
424
|
+
when: {
|
|
425
|
+
kind: 'Heuristic',
|
|
426
|
+
where: {
|
|
427
|
+
ruleId: 'heuristics.ios.performance.blocking-sleep.ast',
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
then: {
|
|
431
|
+
kind: 'Finding',
|
|
432
|
+
message: 'AST heuristic detected blocking sleep usage in iOS production code; async clocks, suspension or cancellable scheduling remain the preferred baseline.',
|
|
433
|
+
code: 'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST',
|
|
434
|
+
},
|
|
435
|
+
},
|
|
418
436
|
{
|
|
419
437
|
id: 'heuristics.ios.unchecked-sendable.ast',
|
|
420
438
|
description: 'Detects @unchecked Sendable usage in iOS production code.',
|
|
@@ -633,6 +633,11 @@ struct APIEndpoint: Sendable {
|
|
|
633
633
|
- `skills.ios.guideline.ios.rtl-support-right-to-left-para-a-rabe-hebreo` se mapea a `heuristics.ios.localization.physical-text-alignment.ast`.
|
|
634
634
|
- En `PROJECT MODE: brownfield`, este hallazgo detecta alineaciones físicas `.left`/`.right` en texto y frames (`multilineTextAlignment`, `frame(alignment:)`, `TextAlignment`, `NSTextAlignment`) como señal de adopción hacia `.leading`/`.trailing`. No marca `.leading` ni `.trailing`.
|
|
635
635
|
|
|
636
|
+
### Enforcement AST inicial de bloqueo de thread iOS
|
|
637
|
+
|
|
638
|
+
- `skills.ios.guideline.ios.background-threads-no-bloquear-main-thread` se mapea a `heuristics.ios.performance.blocking-sleep.ast`.
|
|
639
|
+
- En `PROJECT MODE: brownfield`, este hallazgo detecta sleeps bloqueantes (`Thread.sleep`, `sleep`, `usleep`) en Swift production como señal de adopción hacia scheduling cancellable, clocks o suspensión asíncrona. No marca `Task.sleep`.
|
|
640
|
+
|
|
636
641
|
### Combine (Reactive):
|
|
637
642
|
✅ **Publishers** - AsyncSequence para async, Combine para streams complejos
|
|
638
643
|
✅ **@Published** - En ViewModels para binding con Views
|
|
@@ -127,6 +127,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
127
127
|
'ios.localization.physical-text-alignment',
|
|
128
128
|
['heuristics.ios.localization.physical-text-alignment.ast']
|
|
129
129
|
),
|
|
130
|
+
'skills.ios.guideline.ios.background-threads-no-bloquear-main-thread': heuristicDetector(
|
|
131
|
+
'ios.performance.blocking-sleep',
|
|
132
|
+
['heuristics.ios.performance.blocking-sleep.ast']
|
|
133
|
+
),
|
|
130
134
|
'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
|
|
131
135
|
'heuristics.ios.unchecked-sendable.ast',
|
|
132
136
|
]),
|
|
@@ -407,6 +407,15 @@ const normalizeKnownRuleTarget = (
|
|
|
407
407
|
if (includes('rtl support') || includes('right to left')) {
|
|
408
408
|
return 'skills.ios.guideline.ios.rtl-support-right-to-left-para-a-rabe-hebreo';
|
|
409
409
|
}
|
|
410
|
+
if (
|
|
411
|
+
includes('background threads') ||
|
|
412
|
+
includes('bloquear main thread') ||
|
|
413
|
+
includes('no bloquear main thread') ||
|
|
414
|
+
includes('thread.sleep') ||
|
|
415
|
+
includes('blocking sleep')
|
|
416
|
+
) {
|
|
417
|
+
return 'skills.ios.guideline.ios.background-threads-no-bloquear-main-thread';
|
|
418
|
+
}
|
|
410
419
|
if (
|
|
411
420
|
includes('mixing legacy xctest style') ||
|
|
412
421
|
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.203",
|
|
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-13T11:
|
|
4
|
+
"generatedAt": "2026-05-13T11:57:22.186Z",
|
|
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": "54d70e010210d043da474f55a1031af4f0d897b30082dd522be5d85c44ba27ab",
|
|
5768
5768
|
"rules": [
|
|
5769
5769
|
{
|
|
5770
5770
|
"id": "skills.ios.guideline.ios.accessibility-identifiers-para-localizar-elementos",
|
|
@@ -5967,7 +5967,7 @@
|
|
|
5967
5967
|
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5968
5968
|
"confidence": "MEDIUM",
|
|
5969
5969
|
"locked": true,
|
|
5970
|
-
"evaluationMode": "
|
|
5970
|
+
"evaluationMode": "AUTO",
|
|
5971
5971
|
"origin": "core"
|
|
5972
5972
|
},
|
|
5973
5973
|
{
|