pumuki 6.3.296 → 6.3.298
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/AGENTS.md +55 -0
- package/CHANGELOG.md +8 -0
- package/VERSION +1 -1
- package/core/facts/detectors/text/ios.test.ts +407 -0
- package/core/facts/detectors/text/ios.ts +328 -0
- package/core/facts/extractHeuristicFacts.ts +17 -0
- package/core/rules/presets/heuristics/ios.test.ts +61 -1
- package/core/rules/presets/heuristics/ios.ts +223 -0
- package/integrations/config/skillsDetectorRegistry.ts +55 -2
- package/integrations/gate/evaluateAiGate.ts +37 -7
- package/package.json +1 -1
- package/scripts/framework-menu-system-notifications-macos-dialog-payload.ts +55 -7
- package/skills.lock.json +1 -1
|
@@ -387,6 +387,63 @@ export const iosRules: RuleSet = [
|
|
|
387
387
|
code: 'HEURISTICS_IOS_MEMORY_UNOWNED_SELF_CAPTURE_AST',
|
|
388
388
|
},
|
|
389
389
|
},
|
|
390
|
+
{
|
|
391
|
+
id: 'heuristics.ios.memory.manual-management.ast',
|
|
392
|
+
description: 'Detects manual memory management that bypasses Swift ARC.',
|
|
393
|
+
severity: 'WARN',
|
|
394
|
+
platform: 'ios',
|
|
395
|
+
locked: true,
|
|
396
|
+
when: {
|
|
397
|
+
kind: 'Heuristic',
|
|
398
|
+
where: {
|
|
399
|
+
ruleId: 'heuristics.ios.memory.manual-management.ast',
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
then: {
|
|
403
|
+
kind: 'Finding',
|
|
404
|
+
message:
|
|
405
|
+
'AST heuristic detected manual memory management that bypasses Swift ARC.',
|
|
406
|
+
code: 'HEURISTICS_IOS_MEMORY_MANUAL_MANAGEMENT_AST',
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
id: 'heuristics.ios.testing.makesut-without-memory-tracking.ast',
|
|
411
|
+
description: 'Detects iOS test makeSUT helpers that do not register SUTs for memory-leak tracking.',
|
|
412
|
+
severity: 'WARN',
|
|
413
|
+
platform: 'ios',
|
|
414
|
+
locked: true,
|
|
415
|
+
when: {
|
|
416
|
+
kind: 'Heuristic',
|
|
417
|
+
where: {
|
|
418
|
+
ruleId: 'heuristics.ios.testing.makesut-without-memory-tracking.ast',
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
then: {
|
|
422
|
+
kind: 'Finding',
|
|
423
|
+
message:
|
|
424
|
+
'AST heuristic detected makeSUT() in an iOS test without trackForMemoryLeaks(sut).',
|
|
425
|
+
code: 'HEURISTICS_IOS_TESTING_MAKESUT_WITHOUT_MEMORY_TRACKING_AST',
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: 'heuristics.ios.testing.direct-sut-instantiation-without-makesut.ast',
|
|
430
|
+
description: 'Detects direct SUT construction in iOS tests without a makeSUT factory.',
|
|
431
|
+
severity: 'WARN',
|
|
432
|
+
platform: 'ios',
|
|
433
|
+
locked: true,
|
|
434
|
+
when: {
|
|
435
|
+
kind: 'Heuristic',
|
|
436
|
+
where: {
|
|
437
|
+
ruleId: 'heuristics.ios.testing.direct-sut-instantiation-without-makesut.ast',
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
then: {
|
|
441
|
+
kind: 'Finding',
|
|
442
|
+
message:
|
|
443
|
+
'AST heuristic detected direct SUT instantiation in an iOS test without makeSUT().',
|
|
444
|
+
code: 'HEURISTICS_IOS_TESTING_DIRECT_SUT_INSTANTIATION_WITHOUT_MAKESUT_AST',
|
|
445
|
+
},
|
|
446
|
+
},
|
|
390
447
|
{
|
|
391
448
|
id: 'heuristics.ios.maintainability.nested-if-pyramid.ast',
|
|
392
449
|
description: 'Detects deeply nested if pyramids in iOS production code.',
|
|
@@ -520,6 +577,25 @@ export const iosRules: RuleSet = [
|
|
|
520
577
|
code: 'HEURISTICS_IOS_MAINTAINABILITY_MAGIC_NUMBER_LAYOUT_AST',
|
|
521
578
|
},
|
|
522
579
|
},
|
|
580
|
+
{
|
|
581
|
+
id: 'heuristics.ios.uikit.manual-frame-layout.ast',
|
|
582
|
+
description: 'Detects UIKit manual frame CGRect layout in iOS presentation code.',
|
|
583
|
+
severity: 'WARN',
|
|
584
|
+
platform: 'ios',
|
|
585
|
+
locked: true,
|
|
586
|
+
when: {
|
|
587
|
+
kind: 'Heuristic',
|
|
588
|
+
where: {
|
|
589
|
+
ruleId: 'heuristics.ios.uikit.manual-frame-layout.ast',
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
then: {
|
|
593
|
+
kind: 'Finding',
|
|
594
|
+
message:
|
|
595
|
+
'AST heuristic detected UIKit manual frame layout; use Auto Layout constraints or SwiftUI relative layout.',
|
|
596
|
+
code: 'HEURISTICS_IOS_UIKIT_MANUAL_FRAME_LAYOUT_AST',
|
|
597
|
+
},
|
|
598
|
+
},
|
|
523
599
|
{
|
|
524
600
|
id: 'heuristics.ios.safety.non-iboutlet-iuo.ast',
|
|
525
601
|
description: 'Detects implicitly unwrapped optionals outside IBOutlet wiring.',
|
|
@@ -703,6 +779,79 @@ export const iosRules: RuleSet = [
|
|
|
703
779
|
code: 'HEURISTICS_IOS_DEPENDENCIES_SWIFTPM_BRANCH_DEPENDENCY_AST',
|
|
704
780
|
},
|
|
705
781
|
},
|
|
782
|
+
{
|
|
783
|
+
id: 'heuristics.ios.dependencies.swift-tools-version-below-6-2.ast',
|
|
784
|
+
description: 'Detects iOS Package.swift manifests below Swift tools 6.2.',
|
|
785
|
+
severity: 'WARN',
|
|
786
|
+
platform: 'ios',
|
|
787
|
+
locked: true,
|
|
788
|
+
when: {
|
|
789
|
+
kind: 'Heuristic',
|
|
790
|
+
where: {
|
|
791
|
+
ruleId: 'heuristics.ios.dependencies.swift-tools-version-below-6-2.ast',
|
|
792
|
+
},
|
|
793
|
+
},
|
|
794
|
+
then: {
|
|
795
|
+
kind: 'Finding',
|
|
796
|
+
message:
|
|
797
|
+
'AST heuristic detected Package.swift using swift-tools-version below 6.2.',
|
|
798
|
+
code: 'HEURISTICS_IOS_DEPENDENCIES_SWIFT_TOOLS_VERSION_BELOW_6_2_AST',
|
|
799
|
+
},
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
id: 'heuristics.ios.concurrency.strict-concurrency-below-complete.ast',
|
|
803
|
+
description: 'Detects Xcode iOS targets with strict concurrency checking below complete.',
|
|
804
|
+
severity: 'WARN',
|
|
805
|
+
platform: 'ios',
|
|
806
|
+
locked: true,
|
|
807
|
+
when: {
|
|
808
|
+
kind: 'Heuristic',
|
|
809
|
+
where: {
|
|
810
|
+
ruleId: 'heuristics.ios.concurrency.strict-concurrency-below-complete.ast',
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
then: {
|
|
814
|
+
kind: 'Finding',
|
|
815
|
+
message: 'AST heuristic detected SWIFT_STRICT_CONCURRENCY below complete in an iOS Xcode project.',
|
|
816
|
+
code: 'HEURISTICS_IOS_CONCURRENCY_STRICT_CONCURRENCY_BELOW_COMPLETE_AST',
|
|
817
|
+
},
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
id: 'heuristics.ios.naming.non-pascal-case-type.ast',
|
|
821
|
+
description: 'Detects Swift type declarations that are not PascalCase.',
|
|
822
|
+
severity: 'WARN',
|
|
823
|
+
platform: 'ios',
|
|
824
|
+
locked: true,
|
|
825
|
+
when: {
|
|
826
|
+
kind: 'Heuristic',
|
|
827
|
+
where: {
|
|
828
|
+
ruleId: 'heuristics.ios.naming.non-pascal-case-type.ast',
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
then: {
|
|
832
|
+
kind: 'Finding',
|
|
833
|
+
message: 'AST heuristic detected Swift type declaration without PascalCase.',
|
|
834
|
+
code: 'HEURISTICS_IOS_NAMING_NON_PASCAL_CASE_TYPE_AST',
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
id: 'heuristics.ios.uikit.cell-without-reuse.ast',
|
|
839
|
+
description: 'Detects UITableView/UICollectionView cell providers that create cells without dequeue reuse.',
|
|
840
|
+
severity: 'WARN',
|
|
841
|
+
platform: 'ios',
|
|
842
|
+
locked: true,
|
|
843
|
+
when: {
|
|
844
|
+
kind: 'Heuristic',
|
|
845
|
+
where: {
|
|
846
|
+
ruleId: 'heuristics.ios.uikit.cell-without-reuse.ast',
|
|
847
|
+
},
|
|
848
|
+
},
|
|
849
|
+
then: {
|
|
850
|
+
kind: 'Finding',
|
|
851
|
+
message: 'AST heuristic detected UIKit cell provider creating cells without dequeueReusableCell.',
|
|
852
|
+
code: 'HEURISTICS_IOS_UIKIT_CELL_WITHOUT_REUSE_AST',
|
|
853
|
+
},
|
|
854
|
+
},
|
|
706
855
|
{
|
|
707
856
|
id: 'heuristics.ios.security.userdefaults-sensitive-data.ast',
|
|
708
857
|
description: 'Detects sensitive data stored in UserDefaults/AppStorage; Keychain is the preferred baseline for secrets.',
|
|
@@ -1475,6 +1624,62 @@ export const iosRules: RuleSet = [
|
|
|
1475
1624
|
code: 'HEURISTICS_IOS_SWIFTUI_IMAGE_DATA_DECODING_AST',
|
|
1476
1625
|
},
|
|
1477
1626
|
},
|
|
1627
|
+
{
|
|
1628
|
+
id: 'heuristics.ios.swiftui.manual-rendering-without-imagerenderer.ast',
|
|
1629
|
+
description: 'Detects manual SwiftUI view rendering where ImageRenderer should be used.',
|
|
1630
|
+
severity: 'WARN',
|
|
1631
|
+
platform: 'ios',
|
|
1632
|
+
locked: true,
|
|
1633
|
+
when: {
|
|
1634
|
+
kind: 'Heuristic',
|
|
1635
|
+
where: {
|
|
1636
|
+
ruleId: 'heuristics.ios.swiftui.manual-rendering-without-imagerenderer.ast',
|
|
1637
|
+
},
|
|
1638
|
+
},
|
|
1639
|
+
then: {
|
|
1640
|
+
kind: 'Finding',
|
|
1641
|
+
message:
|
|
1642
|
+
'AST heuristic detected manual SwiftUI view rendering without ImageRenderer.',
|
|
1643
|
+
code: 'HEURISTICS_IOS_SWIFTUI_MANUAL_RENDERING_WITHOUT_IMAGERENDERER_AST',
|
|
1644
|
+
},
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
id: 'heuristics.ios.swiftui.large-viewbuilder-function.ast',
|
|
1648
|
+
description: 'Detects large @ViewBuilder helper functions in SwiftUI presentation code.',
|
|
1649
|
+
severity: 'WARN',
|
|
1650
|
+
platform: 'ios',
|
|
1651
|
+
locked: true,
|
|
1652
|
+
when: {
|
|
1653
|
+
kind: 'Heuristic',
|
|
1654
|
+
where: {
|
|
1655
|
+
ruleId: 'heuristics.ios.swiftui.large-viewbuilder-function.ast',
|
|
1656
|
+
},
|
|
1657
|
+
},
|
|
1658
|
+
then: {
|
|
1659
|
+
kind: 'Finding',
|
|
1660
|
+
message:
|
|
1661
|
+
'AST heuristic detected a large @ViewBuilder helper function; keep @ViewBuilder functions small and extract complex sections into subviews.',
|
|
1662
|
+
code: 'HEURISTICS_IOS_SWIFTUI_LARGE_VIEWBUILDER_FUNCTION_AST',
|
|
1663
|
+
},
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
id: 'heuristics.ios.accessibility.animation-without-reduce-motion.ast',
|
|
1667
|
+
description: 'Detects SwiftUI animations that do not account for reduce motion.',
|
|
1668
|
+
severity: 'WARN',
|
|
1669
|
+
platform: 'ios',
|
|
1670
|
+
locked: true,
|
|
1671
|
+
when: {
|
|
1672
|
+
kind: 'Heuristic',
|
|
1673
|
+
where: {
|
|
1674
|
+
ruleId: 'heuristics.ios.accessibility.animation-without-reduce-motion.ast',
|
|
1675
|
+
},
|
|
1676
|
+
},
|
|
1677
|
+
then: {
|
|
1678
|
+
kind: 'Finding',
|
|
1679
|
+
message: 'AST heuristic detected SwiftUI animation without reduce motion handling.',
|
|
1680
|
+
code: 'HEURISTICS_IOS_ACCESSIBILITY_ANIMATION_WITHOUT_REDUCE_MOTION_AST',
|
|
1681
|
+
},
|
|
1682
|
+
},
|
|
1478
1683
|
{
|
|
1479
1684
|
id: 'heuristics.ios.swiftui.inline-action-logic.ast',
|
|
1480
1685
|
description: 'Detects inline control-flow logic inside SwiftUI action handlers.',
|
|
@@ -1603,6 +1808,24 @@ export const iosRules: RuleSet = [
|
|
|
1603
1808
|
code: 'HEURISTICS_IOS_ON_TAP_GESTURE_AST',
|
|
1604
1809
|
},
|
|
1605
1810
|
},
|
|
1811
|
+
{
|
|
1812
|
+
id: 'heuristics.ios.accessibility.on-tap-without-button-trait.ast',
|
|
1813
|
+
description: 'Detects tappable SwiftUI elements without button accessibility traits.',
|
|
1814
|
+
severity: 'WARN',
|
|
1815
|
+
platform: 'ios',
|
|
1816
|
+
locked: true,
|
|
1817
|
+
when: {
|
|
1818
|
+
kind: 'Heuristic',
|
|
1819
|
+
where: {
|
|
1820
|
+
ruleId: 'heuristics.ios.accessibility.on-tap-without-button-trait.ast',
|
|
1821
|
+
},
|
|
1822
|
+
},
|
|
1823
|
+
then: {
|
|
1824
|
+
kind: 'Finding',
|
|
1825
|
+
message: 'AST heuristic detected onTapGesture without button accessibility trait.',
|
|
1826
|
+
code: 'HEURISTICS_IOS_ACCESSIBILITY_ON_TAP_WITHOUT_BUTTON_TRAIT_AST',
|
|
1827
|
+
},
|
|
1828
|
+
},
|
|
1606
1829
|
{
|
|
1607
1830
|
id: 'heuristics.ios.string-format.ast',
|
|
1608
1831
|
description: 'Detects String(format:) usage in iOS production code.',
|
|
@@ -110,6 +110,23 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
110
110
|
'ios.memory.unowned-self-capture',
|
|
111
111
|
['heuristics.ios.memory.unowned-self-capture.ast']
|
|
112
112
|
),
|
|
113
|
+
'skills.ios.guideline.ios.arc-automatic-reference-counting': heuristicDetector(
|
|
114
|
+
'ios.memory.manual-management',
|
|
115
|
+
['heuristics.ios.memory.manual-management.ast']
|
|
116
|
+
),
|
|
117
|
+
'skills.ios.guideline.ios.trackformemoryleaks-helper-para-detectar-memory-leaks-en-tests': heuristicDetector(
|
|
118
|
+
'ios.testing.makesut-memory-tracking',
|
|
119
|
+
['heuristics.ios.testing.makesut-without-memory-tracking.ast']
|
|
120
|
+
),
|
|
121
|
+
'skills.ios.guideline.ios.makesut-pattern-factory-para-system-under-test': heuristicDetector(
|
|
122
|
+
'ios.testing.makesut-factory',
|
|
123
|
+
['heuristics.ios.testing.direct-sut-instantiation-without-makesut.ast']
|
|
124
|
+
),
|
|
125
|
+
'skills.ios.guideline.ios-swift-testing.preserve-repository-specific-test-contracts-such-as-makesut-and-memory':
|
|
126
|
+
heuristicDetector('ios.testing.repository-specific-memory-contract', [
|
|
127
|
+
'heuristics.ios.testing.direct-sut-instantiation-without-makesut.ast',
|
|
128
|
+
'heuristics.ios.testing.makesut-without-memory-tracking.ast',
|
|
129
|
+
]),
|
|
113
130
|
'skills.ios.guideline.ios.guard-clauses-evitar-pyramid-of-doom-early-returns': heuristicDetector(
|
|
114
131
|
'ios.maintainability.nested-if-pyramid',
|
|
115
132
|
['heuristics.ios.maintainability.nested-if-pyramid.ast']
|
|
@@ -167,6 +184,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
167
184
|
heuristicDetector('ios.maintainability.magic-number-layout', [
|
|
168
185
|
'heuristics.ios.maintainability.magic-number-layout.ast',
|
|
169
186
|
]),
|
|
187
|
+
'skills.ios.guideline.ios.auto-layout-nslayoutconstraint': heuristicDetector(
|
|
188
|
+
'ios.uikit.manual-frame-layout',
|
|
189
|
+
['heuristics.ios.uikit.manual-frame-layout.ast']
|
|
190
|
+
),
|
|
170
191
|
'skills.ios.guideline.ios.prohibido-print-y-logs-ad-hoc': heuristicDetector(
|
|
171
192
|
'ios.logging.adhoc-print',
|
|
172
193
|
['heuristics.ios.logging.adhoc-print.ast']
|
|
@@ -340,6 +361,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
340
361
|
heuristicDetector('ios.accessibility.missing-accessibility-identifier', [
|
|
341
362
|
'heuristics.ios.accessibility.missing-accessibility-identifier.ast',
|
|
342
363
|
]),
|
|
364
|
+
'skills.ios.guideline.ios.traits-accessibilityaddtraits-isbutton': heuristicDetector(
|
|
365
|
+
'ios.accessibility.on-tap-without-button-trait',
|
|
366
|
+
['heuristics.ios.accessibility.on-tap-without-button-trait.ast']
|
|
367
|
+
),
|
|
343
368
|
'skills.ios.guideline.ios.image-optimization-resize-compress-cache': heuristicDetector(
|
|
344
369
|
'ios.swiftui.image-data-decoding',
|
|
345
370
|
['heuristics.ios.swiftui.image-data-decoding.ast']
|
|
@@ -376,6 +401,22 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
376
401
|
heuristicDetector('ios.dependencies.swiftpm-branch-dependency', [
|
|
377
402
|
'heuristics.ios.dependencies.swiftpm-branch-dependency.ast',
|
|
378
403
|
]),
|
|
404
|
+
'skills.ios.guideline.ios.swift-6-2-usar-la-versio-n-actual-con-approachable-concurrency':
|
|
405
|
+
heuristicDetector('ios.dependencies.swift-tools-version', [
|
|
406
|
+
'heuristics.ios.dependencies.swift-tools-version-below-6-2.ast',
|
|
407
|
+
]),
|
|
408
|
+
'skills.ios.guideline.ios.strict-concurrency-checking-activar-en-complete':
|
|
409
|
+
heuristicDetector('ios.concurrency.strict-concurrency-below-complete', [
|
|
410
|
+
'heuristics.ios.concurrency.strict-concurrency-below-complete.ast',
|
|
411
|
+
]),
|
|
412
|
+
'skills.ios.guideline.ios.file-naming-pascalcase-para-tipos': heuristicDetector(
|
|
413
|
+
'ios.naming.non-pascal-case-type',
|
|
414
|
+
['heuristics.ios.naming.non-pascal-case-type.ast']
|
|
415
|
+
),
|
|
416
|
+
'skills.ios.guideline.ios.reuse-cells-uitableview-uicollectionview': heuristicDetector(
|
|
417
|
+
'ios.uikit.cell-without-reuse',
|
|
418
|
+
['heuristics.ios.uikit.cell-without-reuse.ast']
|
|
419
|
+
),
|
|
379
420
|
'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
|
|
380
421
|
'heuristics.ios.unchecked-sendable.ast',
|
|
381
422
|
]),
|
|
@@ -592,8 +633,8 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
592
633
|
'heuristics.ios.swiftui.closure-based-viewbuilder-content.ast',
|
|
593
634
|
]),
|
|
594
635
|
'skills.ios.guideline.ios-swiftui-expert.use-viewbuilder-functions-only-for-small-simple-sections':
|
|
595
|
-
heuristicDetector('ios.swiftui.viewbuilder-
|
|
596
|
-
'heuristics.ios.swiftui.
|
|
636
|
+
heuristicDetector('ios.swiftui.large-viewbuilder-function', [
|
|
637
|
+
'heuristics.ios.swiftui.large-viewbuilder-function.ast',
|
|
597
638
|
]),
|
|
598
639
|
'skills.ios.guideline.ios-swiftui-expert.pass-only-needed-values-to-views-avoid-large-config-or-context-objects':
|
|
599
640
|
heuristicDetector('ios.swiftui.large-config-context-prop', [
|
|
@@ -632,6 +673,18 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
632
673
|
heuristicDetector('ios.swiftui.image-data-decoding', [
|
|
633
674
|
'heuristics.ios.swiftui.image-data-decoding.ast',
|
|
634
675
|
]),
|
|
676
|
+
'skills.ios.guideline.ios-swiftui-expert.use-imagerenderer-for-rendering-swiftui-views':
|
|
677
|
+
heuristicDetector('ios.swiftui.imagerenderer', [
|
|
678
|
+
'heuristics.ios.swiftui.manual-rendering-without-imagerenderer.ast',
|
|
679
|
+
]),
|
|
680
|
+
'skills.ios.guideline.ios.reduce-motion-respetar-preferencia-del-sistema':
|
|
681
|
+
heuristicDetector('ios.accessibility.reduce-motion', [
|
|
682
|
+
'heuristics.ios.accessibility.animation-without-reduce-motion.ast',
|
|
683
|
+
]),
|
|
684
|
+
'skills.ios.guideline.ios.reduce-motion-respetar-preferencias-del-usuario':
|
|
685
|
+
heuristicDetector('ios.accessibility.reduce-motion', [
|
|
686
|
+
'heuristics.ios.accessibility.animation-without-reduce-motion.ast',
|
|
687
|
+
]),
|
|
635
688
|
'skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic':
|
|
636
689
|
heuristicDetector('ios.swiftui.inline-action-logic', [
|
|
637
690
|
'heuristics.ios.swiftui.inline-action-logic.ast',
|
|
@@ -542,6 +542,14 @@ const hasWorktreeCodePlatforms = (params: {
|
|
|
542
542
|
);
|
|
543
543
|
};
|
|
544
544
|
|
|
545
|
+
const hasEffectiveChangedCodePlatforms = (params: {
|
|
546
|
+
changedPaths: ReadonlyArray<string>;
|
|
547
|
+
requiredPlatforms: ReadonlyArray<PreWriteSkillsPlatform>;
|
|
548
|
+
}): boolean =>
|
|
549
|
+
params.changedPaths.some((filePath) =>
|
|
550
|
+
params.requiredPlatforms.some((platform) => isPlatformPath(platform, filePath))
|
|
551
|
+
);
|
|
552
|
+
|
|
545
553
|
const toLockRequiredPlatforms = (
|
|
546
554
|
requiredLock: SkillsLockV1 | undefined
|
|
547
555
|
): ReadonlyArray<PreWriteSkillsPlatform> => {
|
|
@@ -792,6 +800,25 @@ const toSkillsContractAssessment = (params: {
|
|
|
792
800
|
skillsEnforcement: SkillsEnforcementResolution;
|
|
793
801
|
}): AiGateSkillsContractAssessment => {
|
|
794
802
|
const requiredPlatforms = toLockRequiredPlatforms(params.requiredLock);
|
|
803
|
+
const effectiveChangedPaths = collectPreWriteEffectiveChangedPaths(params.repoRoot);
|
|
804
|
+
const hasEffectiveChangedPaths = effectiveChangedPaths.length > 0;
|
|
805
|
+
const hasEffectiveCodePlatforms = hasEffectiveChangedCodePlatforms({
|
|
806
|
+
changedPaths: effectiveChangedPaths,
|
|
807
|
+
requiredPlatforms,
|
|
808
|
+
});
|
|
809
|
+
const isNoCodeEffectiveScope =
|
|
810
|
+
requiredPlatforms.length > 0 && hasEffectiveChangedPaths && !hasEffectiveCodePlatforms;
|
|
811
|
+
|
|
812
|
+
if (isNoCodeEffectiveScope) {
|
|
813
|
+
return {
|
|
814
|
+
stage: params.stage,
|
|
815
|
+
enforced: false,
|
|
816
|
+
status: 'NOT_APPLICABLE',
|
|
817
|
+
detected_platforms: [],
|
|
818
|
+
requirements: [],
|
|
819
|
+
violations: [],
|
|
820
|
+
};
|
|
821
|
+
}
|
|
795
822
|
|
|
796
823
|
if (params.evidenceResult.kind !== 'valid') {
|
|
797
824
|
return {
|
|
@@ -841,7 +868,7 @@ const toSkillsContractAssessment = (params: {
|
|
|
841
868
|
const explicitlyDetectedPlatforms = toDetectedSkillsPlatforms(params.evidenceResult.evidence.platforms);
|
|
842
869
|
const inferredPlatforms = toCoverageInferredPlatforms(coverage);
|
|
843
870
|
const repoTreeDetectedPlatforms =
|
|
844
|
-
params.stage !== 'PRE_WRITE' && requiredPlatforms.length > 0
|
|
871
|
+
params.stage !== 'PRE_WRITE' && requiredPlatforms.length > 0 && !hasEffectiveChangedPaths
|
|
845
872
|
? toRepoTreeDetectedPlatforms({
|
|
846
873
|
repoRoot: params.repoRoot,
|
|
847
874
|
platforms: requiredPlatforms,
|
|
@@ -871,13 +898,16 @@ const toSkillsContractAssessment = (params: {
|
|
|
871
898
|
|
|
872
899
|
if (requiredPlatforms.length > 0 && detectedPlatforms.length === 0) {
|
|
873
900
|
if (
|
|
874
|
-
|
|
875
|
-
&& (
|
|
901
|
+
(
|
|
876
902
|
pendingChanges === 0
|
|
877
|
-
|| !
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
903
|
+
|| !hasEffectiveCodePlatforms
|
|
904
|
+
|| (
|
|
905
|
+
params.stage === 'PRE_WRITE'
|
|
906
|
+
&& !hasWorktreeCodePlatforms({
|
|
907
|
+
repoRoot: params.repoRoot,
|
|
908
|
+
requiredPlatforms,
|
|
909
|
+
})
|
|
910
|
+
)
|
|
881
911
|
)
|
|
882
912
|
) {
|
|
883
913
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.298",
|
|
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": {
|
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
resolveBlockedRemediation,
|
|
5
5
|
resolveProjectLabel,
|
|
6
6
|
} from './framework-menu-system-notifications-payloads';
|
|
7
|
+
import {
|
|
8
|
+
normalizeNotificationText,
|
|
9
|
+
truncateNotificationText,
|
|
10
|
+
} from './framework-menu-system-notifications-text';
|
|
7
11
|
|
|
8
12
|
export type BlockedDialogPayload = {
|
|
9
13
|
title: string;
|
|
@@ -22,16 +26,58 @@ const buildBlockingCausesDetails = (
|
|
|
22
26
|
? 'Causas bloqueantes: 1'
|
|
23
27
|
: `Causas bloqueantes: ${total}`;
|
|
24
28
|
return causes
|
|
25
|
-
.
|
|
26
|
-
const rule = cause.ruleId ?? cause.code;
|
|
27
|
-
const file = cause.file ? ` · ${cause.file}` : '';
|
|
28
|
-
const fix = cause.remediation ? ` · Solución: ${cause.remediation}` : '';
|
|
29
|
-
return `${index + 1}. ${rule}${file}. ${cause.message}${fix}`;
|
|
30
|
-
})
|
|
29
|
+
.flatMap((cause, index) => formatBlockingCauseForDialog(cause, index))
|
|
31
30
|
.reduce((lines, line) => [...lines, line], [header])
|
|
32
31
|
.join('\n');
|
|
33
32
|
};
|
|
34
33
|
|
|
34
|
+
const extractLineFromCauseMessage = (message: string): string | null => {
|
|
35
|
+
const lineMatch = message.match(/\blines?=([0-9][0-9,\-\s]*)\b/i);
|
|
36
|
+
if (lineMatch?.[1]) {
|
|
37
|
+
return lineMatch[1].replace(/\s+/g, '');
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const stripTechnicalFieldsFromMessage = (message: string): string => {
|
|
43
|
+
return normalizeNotificationText(message)
|
|
44
|
+
.replace(/\bseverity=[A-Z]+\b/gi, '')
|
|
45
|
+
.replace(/\bcode=[A-Z0-9_]+\b/gi, '')
|
|
46
|
+
.replace(/\brule=[^\s]+/gi, '')
|
|
47
|
+
.replace(/\bfile=[^\s]+/gi, '')
|
|
48
|
+
.replace(/\blines?=[0-9][0-9,\-\s]*/gi, '')
|
|
49
|
+
.replace(/\bmessage=/gi, '')
|
|
50
|
+
.replace(/\bremediation=/gi, '')
|
|
51
|
+
.replace(/\s+/g, ' ')
|
|
52
|
+
.trim();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const formatLocation = (cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]): string => {
|
|
56
|
+
if (!cause.file) {
|
|
57
|
+
return 'sin fichero';
|
|
58
|
+
}
|
|
59
|
+
const line = extractLineFromCauseMessage(cause.message);
|
|
60
|
+
return line ? `${cause.file}:${line}` : cause.file;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const formatBlockingCauseForDialog = (
|
|
64
|
+
cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number],
|
|
65
|
+
index: number
|
|
66
|
+
): readonly string[] => {
|
|
67
|
+
const rule = cause.ruleId ?? cause.code;
|
|
68
|
+
const problem = stripTechnicalFieldsFromMessage(cause.message) || cause.code;
|
|
69
|
+
const remediation = cause.remediation
|
|
70
|
+
? normalizeNotificationText(cause.remediation)
|
|
71
|
+
: 'Corrige la violación indicada y vuelve a intentar el commit.';
|
|
72
|
+
|
|
73
|
+
return [
|
|
74
|
+
`${index + 1}. Regla: ${truncateNotificationText(rule, 96)}`,
|
|
75
|
+
` Fichero: ${truncateNotificationText(formatLocation(cause), 120)}`,
|
|
76
|
+
` Viola: ${truncateNotificationText(problem, 160)}`,
|
|
77
|
+
` Solución: ${truncateNotificationText(remediation, 180)}`,
|
|
78
|
+
];
|
|
79
|
+
};
|
|
80
|
+
|
|
35
81
|
export const buildBlockedDialogPayload = (params: {
|
|
36
82
|
event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>;
|
|
37
83
|
repoRoot: string;
|
|
@@ -40,7 +86,9 @@ export const buildBlockedDialogPayload = (params: {
|
|
|
40
86
|
const causeCode = params.event.causeCode ?? 'GATE_BLOCKED';
|
|
41
87
|
const cause = buildBlockingCausesDetails(params.event.blockingCauses)
|
|
42
88
|
?? resolveBlockedCauseSummary(params.event, causeCode);
|
|
43
|
-
const remediation =
|
|
89
|
+
const remediation = params.event.blockingCauses && params.event.blockingCauses.length > 0
|
|
90
|
+
? 'Corrige las violaciones listadas y vuelve a intentar el commit.'
|
|
91
|
+
: resolveBlockedRemediation(params.event, causeCode);
|
|
44
92
|
const projectLabel = resolveProjectLabel({
|
|
45
93
|
repoRoot: params.repoRoot,
|
|
46
94
|
projectLabel: params.env.PUMUKI_PROJECT_LABEL,
|