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.
@@ -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-composition-scope', [
596
- 'heuristics.ios.swiftui.closure-based-viewbuilder-content.ast',
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
- params.stage === 'PRE_WRITE'
875
- && (
901
+ (
876
902
  pendingChanges === 0
877
- || !hasWorktreeCodePlatforms({
878
- repoRoot: params.repoRoot,
879
- requiredPlatforms,
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.296",
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
- .map((cause, index) => {
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 = resolveBlockedRemediation(params.event, causeCode);
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,
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-14T09:20:03.683Z",
4
+ "generatedAt": "2026-05-19T17:54:54.241Z",
5
5
  "bundles": [
6
6
  {
7
7
  "name": "android-guidelines",