pumuki 6.3.201 → 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 +42 -0
- package/core/facts/detectors/text/ios.ts +31 -0
- package/core/facts/extractHeuristicFacts.ts +2 -0
- package/core/rules/presets/heuristics/ios.test.ts +11 -1
- package/core/rules/presets/heuristics/ios.ts +36 -0
- package/docs/codex-skills/ios-enterprise-rules.md +10 -0
- package/integrations/config/skillsDetectorRegistry.ts +8 -0
- package/integrations/config/skillsMarkdownRules.ts +12 -0
- package/package.json +1 -1
- package/skills.lock.json +4 -4
|
@@ -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,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
hasSwiftOperationQueueUsage,
|
|
44
45
|
hasSwiftContainsUserFilterUsage,
|
|
45
46
|
hasSwiftPassedValueStateWrapperUsage,
|
|
47
|
+
hasSwiftPhysicalTextAlignmentUsage,
|
|
46
48
|
hasSwiftPreconcurrencyUsage,
|
|
47
49
|
hasSwiftSheetIsPresentedUsage,
|
|
48
50
|
hasSwiftScrollViewShowsIndicatorsUsage,
|
|
@@ -335,6 +337,46 @@ let text = "UIFont.systemFont(ofSize: 16)"
|
|
|
335
337
|
assert.equal(hasSwiftFixedFontSizeUsage(ignored), false);
|
|
336
338
|
});
|
|
337
339
|
|
|
340
|
+
test('detector iOS de localización detecta alineación física sin confundir leading/trailing', () => {
|
|
341
|
+
const source = `
|
|
342
|
+
Text("Name").multilineTextAlignment(.left)
|
|
343
|
+
Text("Price").frame(maxWidth: .infinity, alignment: .right)
|
|
344
|
+
let textAlignment = TextAlignment.right
|
|
345
|
+
label.textAlignment = NSTextAlignment.left
|
|
346
|
+
`;
|
|
347
|
+
const ignored = `
|
|
348
|
+
Text("Name").multilineTextAlignment(.leading)
|
|
349
|
+
Text("Price").frame(maxWidth: .infinity, alignment: .trailing)
|
|
350
|
+
let sample = "TextAlignment.right"
|
|
351
|
+
// Text("Name").multilineTextAlignment(.left)
|
|
352
|
+
`;
|
|
353
|
+
|
|
354
|
+
assert.equal(hasSwiftPhysicalTextAlignmentUsage(source), true);
|
|
355
|
+
assert.equal(hasSwiftPhysicalTextAlignmentUsage(ignored), false);
|
|
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
|
+
|
|
338
380
|
test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
|
|
339
381
|
const source = `
|
|
340
382
|
final class LegacyBox: @unchecked Sendable {}
|
|
@@ -577,6 +577,37 @@ export const hasSwiftFixedFontSizeUsage = (source: string): boolean => {
|
|
|
577
577
|
});
|
|
578
578
|
};
|
|
579
579
|
|
|
580
|
+
export const hasSwiftPhysicalTextAlignmentUsage = (source: string): boolean => {
|
|
581
|
+
const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
|
|
582
|
+
return withoutBlockComments.split(/\r?\n/).some((line) => {
|
|
583
|
+
if (/^\s*\/\//.test(line)) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
const sanitized = stripSwiftLineForSemanticScan(line);
|
|
587
|
+
return (
|
|
588
|
+
/\.\s*multilineTextAlignment\s*\(\s*\.\s*(?:left|right)\s*\)/.test(sanitized) ||
|
|
589
|
+
/\.\s*frame\s*\([^)]*alignment\s*:\s*\.\s*(?:left|right)\b/.test(sanitized) ||
|
|
590
|
+
/\bTextAlignment\s*\.\s*(?:left|right)\b/.test(sanitized) ||
|
|
591
|
+
/\bNSTextAlignment\s*\.\s*(?:left|right)\b/.test(sanitized)
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
};
|
|
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
|
+
|
|
580
611
|
export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
|
|
581
612
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
582
613
|
if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
|
|
@@ -657,6 +657,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
657
657
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedUiStringUsage, ruleId: 'heuristics.ios.localization.hardcoded-ui-string.ast', code: 'HEURISTICS_IOS_LOCALIZATION_HARDCODED_UI_STRING_AST', message: 'AST heuristic detected hardcoded user-facing SwiftUI text; String(localized:) and String Catalogs remain the preferred baseline.' },
|
|
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
|
+
{ 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.' },
|
|
660
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.' },
|
|
661
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.' },
|
|
662
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, [
|
|
@@ -29,6 +29,8 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
29
29
|
'heuristics.ios.localization.hardcoded-ui-string.ast',
|
|
30
30
|
'heuristics.ios.assets.loose-resource.ast',
|
|
31
31
|
'heuristics.ios.accessibility.fixed-font-size.ast',
|
|
32
|
+
'heuristics.ios.localization.physical-text-alignment.ast',
|
|
33
|
+
'heuristics.ios.performance.blocking-sleep.ast',
|
|
32
34
|
'heuristics.ios.unchecked-sendable.ast',
|
|
33
35
|
'heuristics.ios.preconcurrency.ast',
|
|
34
36
|
'heuristics.ios.nonisolated-unsafe.ast',
|
|
@@ -121,6 +123,14 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
121
123
|
byId.get('heuristics.ios.accessibility.fixed-font-size.ast')?.then.code,
|
|
122
124
|
'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST'
|
|
123
125
|
);
|
|
126
|
+
assert.equal(
|
|
127
|
+
byId.get('heuristics.ios.localization.physical-text-alignment.ast')?.then.code,
|
|
128
|
+
'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST'
|
|
129
|
+
);
|
|
130
|
+
assert.equal(
|
|
131
|
+
byId.get('heuristics.ios.performance.blocking-sleep.ast')?.then.code,
|
|
132
|
+
'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST'
|
|
133
|
+
);
|
|
124
134
|
assert.equal(
|
|
125
135
|
byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
|
|
126
136
|
'HEURISTICS_IOS_PRECONCURRENCY_AST'
|
|
@@ -397,6 +397,42 @@ export const iosRules: RuleSet = [
|
|
|
397
397
|
code: 'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST',
|
|
398
398
|
},
|
|
399
399
|
},
|
|
400
|
+
{
|
|
401
|
+
id: 'heuristics.ios.localization.physical-text-alignment.ast',
|
|
402
|
+
description: 'Detects physical left/right text alignment where leading/trailing are the preferred RTL-safe iOS baseline.',
|
|
403
|
+
severity: 'WARN',
|
|
404
|
+
platform: 'ios',
|
|
405
|
+
locked: true,
|
|
406
|
+
when: {
|
|
407
|
+
kind: 'Heuristic',
|
|
408
|
+
where: {
|
|
409
|
+
ruleId: 'heuristics.ios.localization.physical-text-alignment.ast',
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
then: {
|
|
413
|
+
kind: 'Finding',
|
|
414
|
+
message: 'AST heuristic detected physical left/right text alignment in iOS production code; leading/trailing remain the preferred RTL-safe baseline.',
|
|
415
|
+
code: 'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST',
|
|
416
|
+
},
|
|
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
|
+
},
|
|
400
436
|
{
|
|
401
437
|
id: 'heuristics.ios.unchecked-sendable.ast',
|
|
402
438
|
description: 'Detects @unchecked Sendable usage in iOS production code.',
|
|
@@ -628,6 +628,16 @@ struct APIEndpoint: Sendable {
|
|
|
628
628
|
- `skills.ios.guideline.ios.dynamic-type-fuentes-escalables-y-layouts-adaptativos` se mapea a `heuristics.ios.accessibility.fixed-font-size.ast`.
|
|
629
629
|
- En `PROJECT MODE: brownfield`, este hallazgo detecta tamaños de fuente fijos (`.font(.system(size:))`, `Font.system(size:)`, `UIFont.systemFont(ofSize:)`) como señal de adopción hacia Dynamic Type y estilos semánticos. No marca `.font(.headline)`, `.font(.body)` ni otros estilos semánticos.
|
|
630
630
|
|
|
631
|
+
### Enforcement AST inicial de RTL iOS
|
|
632
|
+
|
|
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
|
+
- 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
|
+
|
|
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
|
+
|
|
631
641
|
### Combine (Reactive):
|
|
632
642
|
✅ **Publishers** - AsyncSequence para async, Combine para streams complejos
|
|
633
643
|
✅ **@Published** - En ViewModels para binding con Views
|
|
@@ -123,6 +123,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
123
123
|
'ios.accessibility.fixed-font-size',
|
|
124
124
|
['heuristics.ios.accessibility.fixed-font-size.ast']
|
|
125
125
|
),
|
|
126
|
+
'skills.ios.guideline.ios.rtl-support-right-to-left-para-a-rabe-hebreo': heuristicDetector(
|
|
127
|
+
'ios.localization.physical-text-alignment',
|
|
128
|
+
['heuristics.ios.localization.physical-text-alignment.ast']
|
|
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
|
+
),
|
|
126
134
|
'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
|
|
127
135
|
'heuristics.ios.unchecked-sendable.ast',
|
|
128
136
|
]),
|
|
@@ -404,6 +404,18 @@ const normalizeKnownRuleTarget = (
|
|
|
404
404
|
if (includes('dynamic type')) {
|
|
405
405
|
return 'skills.ios.guideline.ios.dynamic-type-font-scaling-automa-tico';
|
|
406
406
|
}
|
|
407
|
+
if (includes('rtl support') || includes('right to left')) {
|
|
408
|
+
return 'skills.ios.guideline.ios.rtl-support-right-to-left-para-a-rabe-hebreo';
|
|
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
|
+
}
|
|
407
419
|
if (
|
|
408
420
|
includes('mixing legacy xctest style') ||
|
|
409
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
|
{
|
|
@@ -7624,7 +7624,7 @@
|
|
|
7624
7624
|
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
7625
7625
|
"confidence": "MEDIUM",
|
|
7626
7626
|
"locked": true,
|
|
7627
|
-
"evaluationMode": "
|
|
7627
|
+
"evaluationMode": "AUTO",
|
|
7628
7628
|
"origin": "core"
|
|
7629
7629
|
},
|
|
7630
7630
|
{
|