pumuki 6.3.247 → 6.3.249

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/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [6.3.249] - 2026-05-14
10
+
11
+ ### Fixed
12
+
13
+ - **Zero-violation gate contract:** runtime findings are blocking regardless of severity; `pumuki audit` no longer degrades findings to non-blocking warnings just because the runner returned exit 0.
14
+ - **AST-actionable findings contract:** scoped `skills.*` and `heuristics.*` matches without line, range or semantic node attribution are no longer emitted as advisory findings. A runtime finding must be actionable by AST/location evidence or it is not a gate finding.
15
+
16
+ ## [6.3.248] - 2026-05-14
17
+
18
+ ### Fixed
19
+
20
+ - **PRE_WRITE iOS critical skills parity:** `skills.ios.critical-test-quality` vuelve a materializarse como cobertura crítica de `PRE_WRITE`; el modo advisory informa sin bloquear y `PUMUKI_SKILLS_ENFORCEMENT=strict` bloquea cuando la regla crítica falta.
21
+ - **Notification opt-out parity:** `PUMUKI_SYSTEM_NOTIFICATIONS=0` y `PUMUKI_NOTIFICATIONS=0` desactivan también el canal de notificaciones del sistema, evitando diálogos macOS en smokes/CI no interactivos.
22
+ - **Release validation alignment:** package smoke, metadata, framework-menu, policy y `rules_coverage.contract=AUTO_RUNTIME_RULES_FOR_STAGE` quedan alineados con la política zero-violation actual.
23
+
9
24
  ## [6.3.247] - 2026-05-14
10
25
 
11
26
  ### Added
@@ -7,6 +7,7 @@ import {
7
7
  findSwiftConcreteDependencyDipMatch,
8
8
  findSwiftPresentationSrpMatch,
9
9
  hasSwiftAnyViewUsage,
10
+ hasSwiftAsyncWithoutAwaitUsage,
10
11
  hasSwiftCallbackStyleSignature,
11
12
  hasSwiftCornerRadiusUsage,
12
13
  hasSwiftDispatchGroupUsage,
@@ -14,6 +15,7 @@ import {
14
15
  hasSwiftDispatchSemaphoreUsage,
15
16
  hasSwiftAdHocLoggingUsage,
16
17
  hasSwiftAlamofireUsage,
18
+ hasSwiftEmptyCatchUsage,
17
19
  hasSwiftForEachIndicesUsage,
18
20
  hasSwiftForEachSelfIdentityUsage,
19
21
  hasSwiftForceCastUsage,
@@ -136,6 +138,27 @@ test('hasSwiftAnyViewUsage ignora comentarios, strings y coincidencias parciales
136
138
  assert.equal(hasSwiftAnyViewUsage(source), false);
137
139
  });
138
140
 
141
+ test('hasSwiftEmptyCatchUsage detecta catch vacio e ignora comentarios y strings', () => {
142
+ const source = `
143
+ do {
144
+ try repository.save()
145
+ } catch {
146
+ // TODO: report error
147
+ }
148
+ `;
149
+ const safe = `
150
+ let sample = "catch {}"
151
+ do {
152
+ try repository.save()
153
+ } catch {
154
+ logger.error("Save failed")
155
+ }
156
+ `;
157
+
158
+ assert.equal(hasSwiftEmptyCatchUsage(source), true);
159
+ assert.equal(hasSwiftEmptyCatchUsage(safe), false);
160
+ });
161
+
139
162
  test('hasSwiftNonLazyScrollForEachUsage detecta ScrollView con stack no lazy y preserva LazyVStack', () => {
140
163
  const source = `
141
164
  struct FeedView: View {
@@ -1019,6 +1042,34 @@ func wait() async throws {
1019
1042
  assert.equal(hasSwiftMainThreadBlockingSleepUsage(ignored), false);
1020
1043
  });
1021
1044
 
1045
+ test('detector iOS de concurrencia detecta async privado sin await y evita boundaries publicos', () => {
1046
+ const source = `
1047
+ final class ProfileLoader {
1048
+ private func buildSnapshot() async throws -> ProfileSnapshot {
1049
+ ProfileSnapshot.empty
1050
+ }
1051
+ }
1052
+ `;
1053
+ const ignored = `
1054
+ protocol RemoteLoader {
1055
+ func load() async throws -> ProfileSnapshot
1056
+ }
1057
+
1058
+ final class ProfileLoader: RemoteLoader {
1059
+ func load() async throws -> ProfileSnapshot {
1060
+ ProfileSnapshot.empty
1061
+ }
1062
+
1063
+ private func refresh() async throws -> ProfileSnapshot {
1064
+ try await api.load()
1065
+ }
1066
+ }
1067
+ `;
1068
+
1069
+ assert.equal(hasSwiftAsyncWithoutAwaitUsage(source), true);
1070
+ assert.equal(hasSwiftAsyncWithoutAwaitUsage(ignored), false);
1071
+ });
1072
+
1022
1073
  test('detector iOS de accesibilidad detecta botones icon-only sin label explicita', () => {
1023
1074
  const source = `
1024
1075
  struct ToolbarView: View {
@@ -476,6 +476,53 @@ export const hasSwiftTaskDetachedUsage = (source: string): boolean => {
476
476
  });
477
477
  };
478
478
 
479
+ const findMatchingSwiftBrace = (source: string, openBraceIndex: number): number => {
480
+ let depth = 0;
481
+ for (let index = openBraceIndex; index < source.length; index += 1) {
482
+ const current = source[index];
483
+ if (current === '{') {
484
+ depth += 1;
485
+ } else if (current === '}') {
486
+ depth -= 1;
487
+ if (depth === 0) {
488
+ return index;
489
+ }
490
+ }
491
+ }
492
+ return -1;
493
+ };
494
+
495
+ export const hasSwiftAsyncWithoutAwaitUsage = (source: string): boolean => {
496
+ const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
497
+ const privateAsyncFunctionPattern =
498
+ /\bprivate\s+(?:static\s+|class\s+)?func\s+[A-Za-z_][A-Za-z0-9_]*[^{};]*\basync\b[^{};]*\{/g;
499
+
500
+ for (const match of sanitized.matchAll(privateAsyncFunctionPattern)) {
501
+ const header = match[0] ?? '';
502
+ if (/\boverride\b|\bprotocol\b/.test(header)) {
503
+ continue;
504
+ }
505
+
506
+ const openBraceIndex = (match.index ?? 0) + header.length - 1;
507
+ const closeBraceIndex = findMatchingSwiftBrace(sanitized, openBraceIndex);
508
+ if (closeBraceIndex < 0) {
509
+ continue;
510
+ }
511
+
512
+ const body = sanitized.slice(openBraceIndex + 1, closeBraceIndex);
513
+ if (!/\bawait\b/.test(body)) {
514
+ return true;
515
+ }
516
+ }
517
+
518
+ return false;
519
+ };
520
+
521
+ export const hasSwiftEmptyCatchUsage = (source: string): boolean => {
522
+ const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
523
+ return /\bcatch(?:\s+(?:let|var)\s+[A-Za-z_][A-Za-z0-9_]*)?\s*\{\s*\}/.test(sanitized);
524
+ };
525
+
479
526
  export const hasSwiftOnAppearTaskUsage = (source: string): boolean => {
480
527
  const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
481
528
  return /\.onAppear\s*\{[\s\S]{0,500}?\bTask\s*(?:\([^)]*\))?\s*\{/.test(sanitized);
@@ -646,6 +646,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
646
646
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftDispatchSemaphoreUsage, ruleId: 'heuristics.ios.dispatchsemaphore.ast', code: 'HEURISTICS_IOS_DISPATCHSEMAPHORE_AST', message: 'AST heuristic detected DispatchSemaphore usage.' },
647
647
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOperationQueueUsage, ruleId: 'heuristics.ios.operation-queue.ast', code: 'HEURISTICS_IOS_OPERATION_QUEUE_AST', message: 'AST heuristic detected OperationQueue usage.' },
648
648
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftTaskDetachedUsage, ruleId: 'heuristics.ios.task-detached.ast', code: 'HEURISTICS_IOS_TASK_DETACHED_AST', message: 'AST heuristic detected Task.detached usage.' },
649
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAsyncWithoutAwaitUsage, ruleId: 'heuristics.ios.concurrency.async-without-await.ast', code: 'HEURISTICS_IOS_CONCURRENCY_ASYNC_WITHOUT_AWAIT_AST', message: 'AST heuristic detected a private async function without await; remove async unless a protocol/override boundary requires it.' },
650
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftEmptyCatchUsage, ruleId: 'heuristics.ios.error.empty-catch.ast', code: 'HEURISTICS_IOS_ERROR_EMPTY_CATCH_AST', message: 'AST heuristic detected an empty Swift catch block; handle, log, or propagate the error.' },
649
651
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnAppearTaskUsage, ruleId: 'heuristics.ios.swiftui.onappear-task.ast', code: 'HEURISTICS_IOS_SWIFTUI_ONAPPEAR_TASK_AST', message: 'AST heuristic detected Task launched from SwiftUI onAppear; .task/.task(id:) provides lifecycle-aware cancellation.' },
650
652
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnChangeTaskUsage, ruleId: 'heuristics.ios.swiftui.onchange-task.ast', code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST', message: 'AST heuristic detected Task launched from SwiftUI onChange; .task(id:) provides lifecycle-aware cancellation for value-dependent async work.' },
651
653
  { 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.' },
@@ -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, 86);
6
+ assert.equal(iosRules.length, 88);
7
7
 
8
8
  const ids = iosRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
@@ -17,6 +17,8 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
17
17
  'heuristics.ios.dispatchsemaphore.ast',
18
18
  'heuristics.ios.operation-queue.ast',
19
19
  'heuristics.ios.task-detached.ast',
20
+ 'heuristics.ios.concurrency.async-without-await.ast',
21
+ 'heuristics.ios.error.empty-catch.ast',
20
22
  'heuristics.ios.swiftui.onappear-task.ast',
21
23
  'heuristics.ios.swiftui.onchange-task.ast',
22
24
  'heuristics.ios.memory.strong-delegate.ast',
@@ -104,6 +106,14 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
104
106
  byId.get('heuristics.ios.task-detached.ast')?.then.code,
105
107
  'HEURISTICS_IOS_TASK_DETACHED_AST'
106
108
  );
109
+ assert.equal(
110
+ byId.get('heuristics.ios.concurrency.async-without-await.ast')?.then.code,
111
+ 'HEURISTICS_IOS_CONCURRENCY_ASYNC_WITHOUT_AWAIT_AST'
112
+ );
113
+ assert.equal(
114
+ byId.get('heuristics.ios.error.empty-catch.ast')?.then.code,
115
+ 'HEURISTICS_IOS_ERROR_EMPTY_CATCH_AST'
116
+ );
107
117
  assert.equal(
108
118
  byId.get('heuristics.ios.swiftui.onchange-task.ast')?.then.code,
109
119
  'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST'
@@ -181,6 +181,43 @@ export const iosRules: RuleSet = [
181
181
  code: 'HEURISTICS_IOS_TASK_DETACHED_AST',
182
182
  },
183
183
  },
184
+ {
185
+ id: 'heuristics.ios.concurrency.async-without-await.ast',
186
+ description: 'Detects private async functions that do not await in iOS production code.',
187
+ severity: 'WARN',
188
+ platform: 'ios',
189
+ locked: true,
190
+ when: {
191
+ kind: 'Heuristic',
192
+ where: {
193
+ ruleId: 'heuristics.ios.concurrency.async-without-await.ast',
194
+ },
195
+ },
196
+ then: {
197
+ kind: 'Finding',
198
+ message:
199
+ 'AST heuristic detected a private async function without await; remove async unless a protocol/override boundary requires it.',
200
+ code: 'HEURISTICS_IOS_CONCURRENCY_ASYNC_WITHOUT_AWAIT_AST',
201
+ },
202
+ },
203
+ {
204
+ id: 'heuristics.ios.error.empty-catch.ast',
205
+ description: 'Detects Swift catch blocks that silently swallow errors.',
206
+ severity: 'WARN',
207
+ platform: 'ios',
208
+ locked: true,
209
+ when: {
210
+ kind: 'Heuristic',
211
+ where: {
212
+ ruleId: 'heuristics.ios.error.empty-catch.ast',
213
+ },
214
+ },
215
+ then: {
216
+ kind: 'Finding',
217
+ message: 'AST heuristic detected an empty Swift catch block.',
218
+ code: 'HEURISTICS_IOS_ERROR_EMPTY_CATCH_AST',
219
+ },
220
+ },
184
221
  {
185
222
  id: 'heuristics.ios.swiftui.onappear-task.ast',
186
223
  description: 'Detects Task launches from SwiftUI onAppear where .task can provide lifecycle cancellation.',
@@ -6,6 +6,14 @@ This file keeps only the operational highlights and rollout notes that matter wh
6
6
 
7
7
  ## 2026-04 (CLI stability and macOS notifications)
8
8
 
9
+ ### 2026-05-14 (v6.3.249)
10
+
11
+ - **Normalización iOS mode-aware:** la línea activa conserva reglas iOS automatizables con evidencia concreta y deja como declarativas las reglas greenfield/brownfield que requieren contexto de adopción, baseline o migración.
12
+ - **Package smoke estable para fixtures Git:** los commits y pushes internos de preparación del consumer smoke no disparan hooks del paquete bajo prueba; el gate real sigue validándose en los pasos explícitos del smoke.
13
+ - **Smokes no interactivos sin diálogos macOS:** `PUMUKI_SYSTEM_NOTIFICATIONS=0` y `PUMUKI_NOTIFICATIONS=0` vuelven a apagar el canal de sistema, evitando bloqueos por Swift dialog en validaciones de release.
14
+ - **Zero-violation real:** cualquier finding runtime emitido por regla activa bloquea; los matches scoped sin línea/rango/nodo AST dejan de publicarse como findings advisory.
15
+ - **Rollout recomendado:** publicar `pumuki@6.3.249` tras el test suite global verde; `validation:package-smoke`, metadata local y `PRE_WRITE` strict/advisory quedan alineados para esta versión.
16
+
9
17
  ### 2026-04-25 (v6.3.116)
10
18
 
11
19
  - **Inventario local real de dependencias:** `status` y `doctor` conservan `trackedNodeModules*` como señal estricta de seguridad Git y añaden `dependencyInventory` como fuente de verdad de instalación local.
@@ -108,6 +108,16 @@ export const skillsCompilerTemplates: Record<string, SkillsCompilerTemplate> = {
108
108
  stage: 'PRE_PUSH',
109
109
  locked: true,
110
110
  },
111
+ {
112
+ id: 'skills.ios.no-async-without-await',
113
+ description:
114
+ 'Avoid private async functions without await; remove async unless a protocol or override boundary requires it.',
115
+ severity: 'WARN',
116
+ platform: 'ios',
117
+ confidence: 'MEDIUM',
118
+ stage: 'PRE_PUSH',
119
+ locked: true,
120
+ },
111
121
  {
112
122
  id: 'skills.ios.no-unchecked-sendable',
113
123
  description: 'Avoid @unchecked Sendable in production iOS code without strict safety invariant.',
@@ -47,6 +47,9 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
47
47
  'skills.ios.no-task-detached': heuristicDetector('ios.task-detached', [
48
48
  'heuristics.ios.task-detached.ast',
49
49
  ]),
50
+ 'skills.ios.no-async-without-await': heuristicDetector('ios.concurrency.async-without-await', [
51
+ 'heuristics.ios.concurrency.async-without-await.ast',
52
+ ]),
50
53
  'skills.ios.guideline.ios.delegation-pattern-weak-delegates-para-evitar-retain-cycles': heuristicDetector(
51
54
  'ios.memory.strong-delegate',
52
55
  ['heuristics.ios.memory.strong-delegate.ast']
@@ -58,6 +61,13 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
58
61
  'heuristics.ios.memory.strong-self-escaping-closure.ast',
59
62
  ]
60
63
  ),
64
+ 'skills.ios.guideline.ios.retain-cycles-memory-leaks': heuristicDetector(
65
+ 'ios.memory.retain-cycles',
66
+ [
67
+ 'heuristics.ios.memory.strong-delegate.ast',
68
+ 'heuristics.ios.memory.strong-self-escaping-closure.ast',
69
+ ]
70
+ ),
61
71
  'skills.ios.guideline.ios.no-singleton-usar-inyeccio-n-de-dependencias-no-compartir-instancias-g': heuristicDetector(
62
72
  'ios.architecture.custom-singleton',
63
73
  ['heuristics.ios.architecture.custom-singleton.ast']
@@ -98,6 +108,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
98
108
  'ios.logging.adhoc-print',
99
109
  ['heuristics.ios.logging.adhoc-print.ast']
100
110
  ),
111
+ 'skills.ios.guideline.ios.catch-vaci-os-prohibido-silenciar-errores-ast-common-error-emptycatch': heuristicDetector(
112
+ 'ios.error.empty-catch',
113
+ ['heuristics.ios.error.empty-catch.ast']
114
+ ),
101
115
  'skills.ios.guideline.ios.no-loggear-pii-tokens-emails-ids-sensibles': heuristicDetector(
102
116
  'ios.logging.sensitive-data',
103
117
  ['heuristics.ios.logging.sensitive-data.ast']
@@ -110,10 +124,6 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
110
124
  'ios.localization.unlocalized-dateformatter',
111
125
  ['heuristics.ios.localization.unlocalized-dateformatter.ast']
112
126
  ),
113
- 'skills.ios.guideline.ios.alamofire-prohibido-usar-urlsession-nativo': heuristicDetector(
114
- 'ios.networking.alamofire',
115
- ['heuristics.ios.networking.alamofire.ast']
116
- ),
117
127
  'skills.ios.guideline.ios.codable-decodificacio-n-automa-tica-de-json-nunca-jsonserialization': heuristicDetector(
118
128
  'ios.json.jsonserialization',
119
129
  ['heuristics.ios.json.jsonserialization.ast']
@@ -122,14 +132,6 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
122
132
  'ios.json.jsonserialization',
123
133
  ['heuristics.ios.json.jsonserialization.ast']
124
134
  ),
125
- 'skills.ios.guideline.ios.cocoapods-prohibido': heuristicDetector(
126
- 'ios.dependencies.cocoapods',
127
- ['heuristics.ios.dependencies.cocoapods.ast']
128
- ),
129
- 'skills.ios.guideline.ios.carthage-prohibido': heuristicDetector(
130
- 'ios.dependencies.carthage',
131
- ['heuristics.ios.dependencies.carthage.ast']
132
- ),
133
135
  'skills.ios.guideline.ios.keychain-passwords-tokens-no-userdefaults': heuristicDetector(
134
136
  'ios.security.userdefaults-sensitive-data',
135
137
  ['heuristics.ios.security.userdefaults-sensitive-data.ast']
@@ -146,18 +148,6 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
146
148
  'ios.security.insecure-transport',
147
149
  ['heuristics.ios.security.insecure-transport.ast']
148
150
  ),
149
- 'skills.ios.guideline.ios.localizable-strings-deprecado-usar-string-catalogs': heuristicDetector(
150
- 'ios.localization.localizable-strings',
151
- ['heuristics.ios.localization.localizable-strings.ast']
152
- ),
153
- 'skills.ios.guideline.ios.string-catalogs-xcstrings': heuristicDetector(
154
- 'ios.localization.localizable-strings',
155
- ['heuristics.ios.localization.localizable-strings.ast']
156
- ),
157
- 'skills.ios.guideline.ios.string-catalogs-xcstrings-sistema-moderno-de-localizacio-n-xcode-15': heuristicDetector(
158
- 'ios.localization.localizable-strings',
159
- ['heuristics.ios.localization.localizable-strings.ast']
160
- ),
161
151
  'skills.ios.guideline.ios.cero-strings-hardcodeadas-en-ui': heuristicDetector(
162
152
  'ios.localization.hardcoded-ui-string',
163
153
  ['heuristics.ios.localization.hardcoded-ui-string.ast']
@@ -233,6 +233,13 @@ const normalizeKnownRuleTarget = (
233
233
  if (includes('task detached') || includes('task.detached')) {
234
234
  return 'skills.ios.no-task-detached';
235
235
  }
236
+ if (
237
+ includes('remove async if not required') ||
238
+ includes('async without await') ||
239
+ includes('async_without_await')
240
+ ) {
241
+ return 'skills.ios.no-async-without-await';
242
+ }
236
243
  if (includes('unchecked sendable')) {
237
244
  return 'skills.ios.no-unchecked-sendable';
238
245
  }
@@ -491,6 +498,12 @@ const normalizeKnownRuleTarget = (
491
498
  if (includes('uiscreen main bounds') || includes('uiscreen.main.bounds')) {
492
499
  return 'skills.ios.no-uiscreen-main-bounds';
493
500
  }
501
+ if (
502
+ (includes('task id') && includes('value dependent')) ||
503
+ (includes('task id') && includes('value-dependent'))
504
+ ) {
505
+ return 'skills.ios.guideline.ios-swiftui-expert.use-task-id-for-value-dependent-tasks';
506
+ }
494
507
  if (
495
508
  includes('task/.task(id') ||
496
509
  includes('trabajos async con cancelacion automatica') ||
@@ -500,15 +513,127 @@ const normalizeKnownRuleTarget = (
500
513
  ) {
501
514
  return 'skills.ios.guideline.ios-swiftui-expert.use-task-modifier-for-automatic-cancellation-of-async-work';
502
515
  }
516
+ if (includes('jsonserialization')) {
517
+ if (includes('decodificacion automatica') || includes('decoding')) {
518
+ return 'skills.ios.guideline.ios.codable-decodificacio-n-automa-tica-de-json-nunca-jsonserialization';
519
+ }
520
+ if (includes('codable')) {
521
+ return 'skills.ios.guideline.ios.codable-para-serializacio-n-json-nunca-jsonserialization';
522
+ }
523
+ }
524
+ if (
525
+ (includes('keychain') && includes('userdefaults')) ||
526
+ (includes('passwords') && includes('tokens') && includes('userdefaults'))
527
+ ) {
528
+ return 'skills.ios.guideline.ios.keychain-passwords-tokens-no-userdefaults';
529
+ }
530
+ if (includes('userdefaults') && includes('no datos sensibles')) {
531
+ return 'skills.ios.guideline.ios.userdefaults-settings-simples-no-datos-sensibles';
532
+ }
533
+ if (
534
+ includes('print y logs ad hoc') ||
535
+ includes('print() y logs ad hoc') ||
536
+ includes('logs ad hoc')
537
+ ) {
538
+ return 'skills.ios.guideline.ios.prohibido-print-y-logs-ad-hoc';
539
+ }
540
+ if (
541
+ includes('catch vac') ||
542
+ includes('catch vaci') ||
543
+ includes('emptycatch') ||
544
+ includes('empty catch')
545
+ ) {
546
+ return 'skills.ios.guideline.ios.catch-vaci-os-prohibido-silenciar-errores-ast-common-error-emptycatch';
547
+ }
548
+ if (includes('no loggear pii') || includes('tokens emails ids sensibles')) {
549
+ return 'skills.ios.guideline.ios.no-loggear-pii-tokens-emails-ids-sensibles';
550
+ }
551
+ if (includes('obfuscation') && includes('strings sensibles')) {
552
+ return 'skills.ios.guideline.ios.obfuscation-strings-sensibles-en-co-digo';
553
+ }
554
+ if (includes('dateformatter') && includes('fechas localizadas')) {
555
+ return 'skills.ios.guideline.ios.dateformatter-fechas-localizadas';
556
+ }
557
+ if (includes('strings hardcodeadas') && includes('ui')) {
558
+ return 'skills.ios.guideline.ios.cero-strings-hardcodeadas-en-ui';
559
+ }
560
+ if (includes('assets en asset catalogs')) {
561
+ return 'skills.ios.guideline.ios.assets-en-asset-catalogs-con-soporte-para-todos-los-taman-os';
562
+ }
563
+ if (includes('dynamic type') && includes('font scaling')) {
564
+ return 'skills.ios.guideline.ios.dynamic-type-font-scaling-automa-tico';
565
+ }
566
+ if (includes('dynamic type') && includes('fuentes escalables')) {
567
+ return 'skills.ios.guideline.ios.dynamic-type-fuentes-escalables-y-layouts-adaptativos';
568
+ }
569
+ if (includes('rtl support') || includes('right to left')) {
570
+ return 'skills.ios.guideline.ios.rtl-support-right-to-left-para-a-rabe-hebreo';
571
+ }
572
+ if (includes('background threads') && includes('no bloquear main thread')) {
573
+ return 'skills.ios.guideline.ios.background-threads-no-bloquear-main-thread';
574
+ }
575
+ if (includes('accessibility labels') || includes('accessibilitylabel')) {
576
+ return 'skills.ios.guideline.ios.accessibility-labels-accessibilitylabel';
577
+ }
578
+ if (includes('delegation pattern') && includes('weak delegates')) {
579
+ return 'skills.ios.guideline.ios.delegation-pattern-weak-delegates-para-evitar-retain-cycles';
580
+ }
581
+ if (includes('retain cycles') && includes('memory leaks')) {
582
+ return 'skills.ios.guideline.ios.retain-cycles-memory-leaks';
583
+ }
584
+ if (includes('retain cycles') && includes('closures')) {
585
+ return 'skills.ios.guideline.ios.evitar-retain-cycles-especialmente-en-closures-delegates';
586
+ }
587
+ if (includes('no singleton') || includes('no singletons')) {
588
+ return 'skills.ios.guideline.ios.no-singleton-usar-inyeccio-n-de-dependencias-no-compartir-instancias-g';
589
+ }
590
+ if (includes('massive view controllers')) {
591
+ return 'skills.ios.guideline.ios.massive-view-controllers-viewcontrollers-que-mezclan-presentacio-n-nav';
592
+ }
593
+ if (includes('implicitly unwrapped')) {
594
+ return 'skills.ios.guideline.ios.implicitly-unwrapped-solo-para-iboutlets-y-casos-muy-especi-ficos';
595
+ }
596
+ if (includes('magic numbers')) {
597
+ return 'skills.ios.guideline.ios.magic-numbers-usar-constantes-con-nombres';
598
+ }
599
+ if (includes('swinject')) {
600
+ return 'skills.ios.guideline.ios.swinject-prohibido-di-manual-o-environment';
601
+ }
602
+ if (
603
+ includes('navigationstack navigationpath') ||
604
+ includes('navigationstack + navigationpath')
605
+ ) {
606
+ return 'skills.ios.no-navigation-view';
607
+ }
608
+ if (
609
+ includes('onchange') &&
610
+ (includes('2 parametros') || includes('2 para metros') || includes('sin parametros'))
611
+ ) {
612
+ return 'skills.ios.no-legacy-onchange';
613
+ }
614
+ if (
615
+ includes('lazyvstack lazyhstack') ||
616
+ (includes('lazy loading') && includes('lazyvstack'))
617
+ ) {
618
+ return 'skills.ios.guideline.ios-swiftui-expert.use-lazyvstack-lazyhstack-for-large-lists';
619
+ }
620
+ if (includes('singletons') && includes('dificultan testing')) {
621
+ return 'skills.ios.guideline.ios.singletons-dificultan-testing';
622
+ }
503
623
  if (
504
624
  includes('swift testing over xctest') ||
505
625
  includes('prefer import testing') ||
506
626
  includes('prefer test functions over test methods') ||
507
627
  includes('test functions over test methods') ||
508
628
  includes('xctest-only unit tests') ||
629
+ includes('xctest only unit tests') ||
509
630
  includes('new xctest-only unit tests') ||
631
+ includes('new xctest only unit tests') ||
510
632
  includes('xctest only for ui') ||
511
- includes('xctest only for ui performance')
633
+ includes('xctest only for ui performance') ||
634
+ (includes('xctest solo') && includes('legacy')) ||
635
+ (includes('xctest') && includes('solo para proyectos legacy')) ||
636
+ (includes('xctest') && includes('ui tests'))
512
637
  ) {
513
638
  return 'skills.ios.prefer-swift-testing';
514
639
  }
@@ -539,13 +664,6 @@ const normalizeKnownRuleTarget = (
539
664
  ) {
540
665
  return 'skills.ios.guideline.ios.app-transport-security-ats-https-por-defecto';
541
666
  }
542
- if (
543
- includes('localizable strings') ||
544
- includes('string catalogs') ||
545
- includes('xcstrings')
546
- ) {
547
- return 'skills.ios.guideline.ios.localizable-strings-deprecado-usar-string-catalogs';
548
- }
549
667
  if (includes('strings hardcodeadas') || includes('string localized')) {
550
668
  return 'skills.ios.guideline.ios.cero-strings-hardcodeadas-en-ui';
551
669
  }
@@ -646,9 +764,16 @@ const normalizeKnownRuleTarget = (
646
764
  }
647
765
  if (
648
766
  includes('nsmanagedobject across boundaries') ||
767
+ includes('nsmanagedobjectid or mapped dto') ||
768
+ includes('mapped dto domain models') ||
649
769
  includes('passing nsmanagedobject') ||
770
+ includes('pass nsmanagedobjectid when a different context') ||
650
771
  includes('nsmanagedobject through service') ||
651
- includes('nsmanagedobject in shared function and property boundaries')
772
+ includes('nsmanagedobject in shared function and property boundaries') ||
773
+ includes('managed objects as context scoped references') ||
774
+ includes('not as portable domain entities') ||
775
+ includes('managed objects into domain models before crossing module boundaries') ||
776
+ includes('map managed objects into domain models')
652
777
  ) {
653
778
  return 'skills.ios.no-nsmanagedobject-boundary';
654
779
  }
@@ -670,6 +795,8 @@ const normalizeKnownRuleTarget = (
670
795
  }
671
796
  if (
672
797
  includes('core data orchestration inside infrastructure') ||
798
+ includes('make context ownership explicit') ||
799
+ includes('merge boundaries controlled') ||
673
800
  includes('instead of presentation code') ||
674
801
  includes('core data apis in application or presentation code') ||
675
802
  includes('avoid core data apis in application or presentation code') ||
@@ -151,7 +151,7 @@ const PLATFORM_REQUIRED_SKILLS_BUNDLES: Readonly<Record<PreWriteSkillsPlatform,
151
151
  frontend: ['frontend-guidelines'],
152
152
  };
153
153
  const PREWRITE_CRITICAL_SKILLS_RULES: Readonly<Record<PreWriteSkillsPlatform, ReadonlyArray<string>>> = {
154
- ios: [],
154
+ ios: ['skills.ios.critical-test-quality'],
155
155
  android: [],
156
156
  backend: [],
157
157
  frontend: [],
@@ -644,7 +644,8 @@ const collectPreWritePlatformSkillsViolations = (params: {
644
644
 
645
645
  if (missingCriticalRulesByPlatform.length > 0) {
646
646
  violations.push(
647
- toCriticalSkillsViolation(
647
+ toSkillsViolation(
648
+ params.skillsEnforcement,
648
649
  'EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING',
649
650
  `Detected platforms missing critical skill-rule enforcement in PRE_WRITE: ${missingCriticalRulesByPlatform.join(' | ')}.`
650
651
  )
@@ -101,20 +101,6 @@ const hasActionableFindingLocation = (finding: Finding): boolean => {
101
101
  );
102
102
  };
103
103
 
104
- const toNonActionableScopedAdvisoryFinding = (finding: Finding): Finding => ({
105
- ...finding,
106
- blocking: false,
107
- message:
108
- `${finding.message} ` +
109
- '(Advisory: Pumuki no pudo atribuir este hallazgo a una linea, rango o nodo accionable en el scope actual.)',
110
- why:
111
- finding.why ??
112
- 'El gate esta limitado a un scope acotado y este finding solo pudo atribuirse al archivo completo.',
113
- expected_fix:
114
- finding.expected_fix ??
115
- 'Reintentar cuando el detector aporte lineas, rango, simbolo o nodo; mientras tanto no bloquea el slice acotado.',
116
- });
117
-
118
104
  const normalizeScopedRuleEngineFindings = (params: {
119
105
  findings: ReadonlyArray<Finding>;
120
106
  scope: GateScope;
@@ -123,15 +109,15 @@ const normalizeScopedRuleEngineFindings = (params: {
123
109
  return params.findings;
124
110
  }
125
111
 
126
- return params.findings.map((finding) => {
112
+ return params.findings.filter((finding) => {
127
113
  if (
128
114
  !finding.filePath ||
129
115
  hasActionableFindingLocation(finding) ||
130
116
  (!finding.ruleId.startsWith('skills.') && !finding.ruleId.startsWith('heuristics.'))
131
117
  ) {
132
- return finding;
118
+ return true;
133
119
  }
134
- return toNonActionableScopedAdvisoryFinding(finding);
120
+ return false;
135
121
  });
136
122
  };
137
123
 
@@ -283,22 +283,6 @@ const toLifecycleAuditFinding = (finding: SnapshotFinding): LifecycleAuditFindin
283
283
  blocking: isFindingBlocking(finding),
284
284
  });
285
285
 
286
- const toGateAllowedAuditAdvisoryFinding = (
287
- finding: LifecycleAuditFinding
288
- ): LifecycleAuditFinding => {
289
- if (!finding.blocking) {
290
- return finding;
291
- }
292
- return {
293
- ...finding,
294
- severity: 'WARN',
295
- blocking: false,
296
- message:
297
- `${finding.message} ` +
298
- '(Advisory: current audit gate exited 0, so this finding is not blocking for this run.)',
299
- };
300
- };
301
-
302
286
  const buildBlockedWithoutFindingsFallback = (params: {
303
287
  stage: LifecycleAuditStage;
304
288
  gateExitCode: number;
@@ -540,13 +524,14 @@ export const runLifecycleAudit = async (params: {
540
524
  ? findings.map(toRangeNoSupportedCodeAuditAdvisoryFinding)
541
525
  : stagedWithoutSupportedCode
542
526
  ? findings.map(toStagedNoSupportedCodeAuditAdvisoryFinding)
543
- : gateAllowed
544
- ? findings.map(toGateAllowedAuditAdvisoryFinding)
545
527
  : findings;
528
+ const hasBlockingFinding = effectiveFindings.some((finding) => finding.blocking);
546
529
  const gateExitCode =
547
530
  scopedGlobalEnforcementOnly || rangePrePushWithoutSupportedCodeSddOnly || stagedWithoutSupportedCode
548
531
  ? 0
549
- : originalGateExitCode;
532
+ : hasBlockingFinding
533
+ ? 1
534
+ : originalGateExitCode;
550
535
  const effectiveSnapshotOutcome =
551
536
  gateExitCode === 0 && snapshotOutcome === 'BLOCK' ? 'PASS' : snapshotOutcome;
552
537
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.247",
3
+ "version": "6.3.249",
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": {
@@ -156,6 +156,11 @@ const runMenuAuditGate = async (
156
156
  await defaultDependencies.runPlatformGate({
157
157
  ...gateParams,
158
158
  auditMode: 'engine',
159
+ sddDecisionOverride: {
160
+ allowed: true,
161
+ code: 'ALLOWED',
162
+ message: 'Framework menu audit gate runs without implicit SDD/OpenSpec bootstrap.',
163
+ },
159
164
  dependencies: {
160
165
  printGateFindings: () => {},
161
166
  evaluatePlatformGateFindings: (params) =>
@@ -5,12 +5,24 @@ import type {
5
5
  } from './framework-menu-system-notifications-types';
6
6
  import { isTruthyEnvValue } from './framework-menu-system-notifications-env';
7
7
 
8
+ const isDisabledEnvValue = (value?: string): boolean => {
9
+ if (!value) {
10
+ return false;
11
+ }
12
+ const normalized = value.trim().toLowerCase();
13
+ return normalized === '0' || normalized === 'false' || normalized === 'no' || normalized === 'off';
14
+ };
15
+
8
16
  export const resolveSystemNotificationGate = (params: {
9
17
  config: SystemNotificationsConfig;
10
18
  nowMs: number;
11
19
  env?: NodeJS.ProcessEnv;
12
20
  }): SystemNotificationEmitResult | null => {
13
- if (isTruthyEnvValue(params.env?.PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS)) {
21
+ if (
22
+ isTruthyEnvValue(params.env?.PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS) ||
23
+ isDisabledEnvValue(params.env?.PUMUKI_SYSTEM_NOTIFICATIONS) ||
24
+ isDisabledEnvValue(params.env?.PUMUKI_NOTIFICATIONS)
25
+ ) {
14
26
  return { delivered: false, reason: 'disabled' };
15
27
  }
16
28
  if (!params.config.enabled) {
@@ -13,7 +13,11 @@ export const commitBaseline = (
13
13
  ): void => {
14
14
  writeBaselineFile(workspace.consumerRepo);
15
15
  runGitStep(workspace, ['add', '.'], 'git add baseline');
16
- runGitStep(workspace, ['commit', '-m', 'chore: baseline'], 'git commit baseline');
16
+ runGitStep(
17
+ workspace,
18
+ ['commit', '--no-verify', '-m', 'chore: baseline'],
19
+ 'git commit baseline'
20
+ );
17
21
  };
18
22
 
19
23
  export const writeAndCommitRangePayloadForBlockMode = (
@@ -28,7 +32,7 @@ export const writeAndCommitRangePayloadForBlockMode = (
28
32
  runGitStep(workspace, ['add', '.'], 'git add range payload');
29
33
  runGitStep(
30
34
  workspace,
31
- ['commit', '-m', 'test: range payload for package smoke'],
35
+ ['commit', '--no-verify', '-m', 'test: range payload for package smoke'],
32
36
  'git commit range payload'
33
37
  );
34
38
  };
@@ -39,7 +39,7 @@ export const configureRemoteAndFeatureBranch = (
39
39
  workspace.tmpRoot
40
40
  );
41
41
  runGitStep(workspace, ['remote', 'add', 'origin', workspace.bareRemote], 'git remote add origin');
42
- runGitStep(workspace, ['push', '-u', 'origin', 'main'], 'git push origin main');
42
+ runGitStep(workspace, ['push', '--no-verify', '-u', 'origin', 'main'], 'git push origin main');
43
43
  runGitStep(workspace, ['checkout', '-b', 'feature/package-smoke'], 'git checkout feature branch');
44
44
  runGitStep(
45
45
  workspace,
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-14T08:03:51.620Z",
4
+ "generatedAt": "2026-05-14T09:20:03.683Z",
5
5
  "bundles": [
6
6
  {
7
7
  "name": "android-guidelines",
@@ -5387,7 +5387,7 @@
5387
5387
  "name": "ios-concurrency-guidelines",
5388
5388
  "version": "1.0.0",
5389
5389
  "source": "file:vendor/skills/swift-concurrency/SKILL.md",
5390
- "hash": "6a1613d60c8ac35be8c39b15b98807e0ee7c81fa33a29e3262d225daa4598252",
5390
+ "hash": "38629be89a118780cf411e11520b34fe1aee005a38703ef4793ff37eecd10dfa",
5391
5391
  "rules": [
5392
5392
  {
5393
5393
  "id": "skills.ios.guideline.ios-concurrency.async-sequences-md-asyncsequence-asyncstream-when-to-use-vs-regular-as",
@@ -5401,18 +5401,6 @@
5401
5401
  "evaluationMode": "DECLARATIVE",
5402
5402
  "origin": "core"
5403
5403
  },
5404
- {
5405
- "id": "skills.ios.guideline.ios-concurrency.remove-async-if-not-required-if-required-by-protocol-override-concurre",
5406
- "description": "Remove async if not required; if required by protocol/override/@concurrent, prefer narrow suppression over adding fake awaits. See references/linting.md.",
5407
- "severity": "ERROR",
5408
- "platform": "ios",
5409
- "sourceSkill": "ios-concurrency-guidelines",
5410
- "sourcePath": "vendor/skills/swift-concurrency/SKILL.md",
5411
- "confidence": "HIGH",
5412
- "locked": true,
5413
- "evaluationMode": "DECLARATIVE",
5414
- "origin": "core"
5415
- },
5416
5404
  {
5417
5405
  "id": "skills.ios.guideline.ios-concurrency.then-use-references-actors-md-global-actors-nonisolated-isolated-param",
5418
5406
  "description": "Then: use references/actors.md (global actors, nonisolated, isolated parameters) and references/threading.md (default isolation)",
@@ -5522,6 +5510,19 @@
5522
5510
  "evaluationMode": "AUTO",
5523
5511
  "origin": "core"
5524
5512
  },
5513
+ {
5514
+ "id": "skills.ios.no-async-without-await",
5515
+ "description": "Avoid private async functions without await; remove async unless a protocol or override boundary requires it.",
5516
+ "severity": "WARN",
5517
+ "platform": "ios",
5518
+ "confidence": "MEDIUM",
5519
+ "stage": "PRE_PUSH",
5520
+ "locked": true,
5521
+ "sourceSkill": "ios-concurrency-guidelines",
5522
+ "sourcePath": "vendor/skills/swift-concurrency/SKILL.md",
5523
+ "evaluationMode": "AUTO",
5524
+ "origin": "core"
5525
+ },
5525
5526
  {
5526
5527
  "id": "skills.ios.no-dispatchgroup",
5527
5528
  "description": "Avoid DispatchGroup in production iOS code; prefer TaskGroup.",
@@ -5644,56 +5645,8 @@
5644
5645
  "name": "ios-core-data-guidelines",
5645
5646
  "version": "1.0.0",
5646
5647
  "source": "file:vendor/skills/core-data-expert/SKILL.md",
5647
- "hash": "be63caab019ec200e0248cc670f431b27d4fa8023c3267d0577785e50ba14db4",
5648
+ "hash": "a84487522605588f76e31278324b5153f2a437ba2ac56f972f24e1a7226c4365",
5648
5649
  "rules": [
5649
- {
5650
- "id": "skills.ios.guideline.ios-core-data.make-context-ownership-explicit-and-keep-merge-boundaries-controlled",
5651
- "description": "Make context ownership explicit and keep merge boundaries controlled.",
5652
- "severity": "WARN",
5653
- "platform": "ios",
5654
- "sourceSkill": "ios-core-data-guidelines",
5655
- "sourcePath": "vendor/skills/core-data-expert/SKILL.md",
5656
- "confidence": "MEDIUM",
5657
- "locked": true,
5658
- "evaluationMode": "DECLARATIVE",
5659
- "origin": "core"
5660
- },
5661
- {
5662
- "id": "skills.ios.guideline.ios-core-data.pass-nsmanagedobjectid-when-a-different-context-must-resolve-the-entit",
5663
- "description": "Pass NSManagedObjectID when a different context must resolve the entity later.",
5664
- "severity": "ERROR",
5665
- "platform": "ios",
5666
- "sourceSkill": "ios-core-data-guidelines",
5667
- "sourcePath": "vendor/skills/core-data-expert/SKILL.md",
5668
- "confidence": "HIGH",
5669
- "locked": true,
5670
- "evaluationMode": "DECLARATIVE",
5671
- "origin": "core"
5672
- },
5673
- {
5674
- "id": "skills.ios.guideline.ios-core-data.treat-managed-objects-as-context-scoped-references-not-as-portable-dom",
5675
- "description": "Treat managed objects as context-scoped references, not as portable domain entities.",
5676
- "severity": "WARN",
5677
- "platform": "ios",
5678
- "sourceSkill": "ios-core-data-guidelines",
5679
- "sourcePath": "vendor/skills/core-data-expert/SKILL.md",
5680
- "confidence": "MEDIUM",
5681
- "locked": true,
5682
- "evaluationMode": "DECLARATIVE",
5683
- "origin": "core"
5684
- },
5685
- {
5686
- "id": "skills.ios.guideline.ios-core-data.use-repositories-that-map-managed-objects-into-domain-models-before-cr",
5687
- "description": "Use repositories that map managed objects into domain models before crossing module boundaries.",
5688
- "severity": "WARN",
5689
- "platform": "ios",
5690
- "sourceSkill": "ios-core-data-guidelines",
5691
- "sourcePath": "vendor/skills/core-data-expert/SKILL.md",
5692
- "confidence": "MEDIUM",
5693
- "locked": true,
5694
- "evaluationMode": "DECLARATIVE",
5695
- "origin": "core"
5696
- },
5697
5650
  {
5698
5651
  "id": "skills.ios.no-core-data-layer-leak",
5699
5652
  "description": "Keep Core Data orchestration inside infrastructure or repository layers; avoid Core Data APIs in application or presentation code.",
@@ -5764,8 +5717,20 @@
5764
5717
  "name": "ios-guidelines",
5765
5718
  "version": "1.0.0",
5766
5719
  "source": "file:vendor/skills/ios-enterprise-rules/SKILL.md",
5767
- "hash": "d787ee4a7d258686f6053ba89c5eff680d6d991ff54e590dd49b7aaa9a7e39f9",
5720
+ "hash": "3eb1d2a79500bce846244c510c96e83ef1b0046695eb4ff7dc506dbd94d10154",
5768
5721
  "rules": [
5722
+ {
5723
+ "id": "skills.ios.guideline.ios-swiftui-expert.use-lazyvstack-lazyhstack-for-large-lists",
5724
+ "description": "LazyVStack/LazyHStack - Para listas grandes",
5725
+ "severity": "WARN",
5726
+ "platform": "ios",
5727
+ "sourceSkill": "ios-guidelines",
5728
+ "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
5729
+ "confidence": "MEDIUM",
5730
+ "locked": true,
5731
+ "evaluationMode": "AUTO",
5732
+ "origin": "core"
5733
+ },
5769
5734
  {
5770
5735
  "id": "skills.ios.guideline.ios-swiftui-expert.use-navigationdestination-for-for-type-safe-navigation",
5771
5736
  "description": "navigationDestination(for:) - Destinos tipados",
@@ -5970,6 +5935,18 @@
5970
5935
  "evaluationMode": "DECLARATIVE",
5971
5936
  "origin": "core"
5972
5937
  },
5938
+ {
5939
+ "id": "skills.ios.guideline.ios.automatic-plural-handling-en-string-catalogs",
5940
+ "description": "Automatic plural handling - En String Catalogs",
5941
+ "severity": "WARN",
5942
+ "platform": "ios",
5943
+ "sourceSkill": "ios-guidelines",
5944
+ "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
5945
+ "confidence": "MEDIUM",
5946
+ "locked": true,
5947
+ "evaluationMode": "DECLARATIVE",
5948
+ "origin": "core"
5949
+ },
5973
5950
  {
5974
5951
  "id": "skills.ios.guideline.ios.avoid-over-use-async-await-ma-s-simple-para-single-values",
5975
5952
  "description": "Avoid over-use - async/await más simple para single values",
@@ -6087,7 +6064,7 @@
6087
6064
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6088
6065
  "confidence": "HIGH",
6089
6066
  "locked": true,
6090
- "evaluationMode": "DECLARATIVE",
6067
+ "evaluationMode": "AUTO",
6091
6068
  "origin": "core"
6092
6069
  },
6093
6070
  {
@@ -6138,28 +6115,16 @@
6138
6115
  "evaluationMode": "DECLARATIVE",
6139
6116
  "origin": "core"
6140
6117
  },
6141
- {
6142
- "id": "skills.ios.guideline.ios.codable-decodificacio-n-automa-tica-de-json-nunca-jsonserialization",
6143
- "description": "Codable - Decodificación automática de JSON (nunca JSONSerialization)",
6144
- "severity": "ERROR",
6145
- "platform": "ios",
6146
- "sourceSkill": "ios-guidelines",
6147
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6148
- "confidence": "MEDIUM",
6149
- "locked": true,
6150
- "evaluationMode": "DECLARATIVE",
6151
- "origin": "core"
6152
- },
6153
6118
  {
6154
6119
  "id": "skills.ios.guideline.ios.codable-para-serializacio-n-json-nunca-jsonserialization",
6155
- "description": "Codable para serialización JSON (nunca JSONSerialization)",
6120
+ "description": "Codable - Decodificación automática de JSON (nunca JSONSerialization)",
6156
6121
  "severity": "ERROR",
6157
6122
  "platform": "ios",
6158
6123
  "sourceSkill": "ios-guidelines",
6159
6124
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6160
6125
  "confidence": "MEDIUM",
6161
6126
  "locked": true,
6162
- "evaluationMode": "DECLARATIVE",
6127
+ "evaluationMode": "AUTO",
6163
6128
  "origin": "core"
6164
6129
  },
6165
6130
  {
@@ -6392,7 +6357,7 @@
6392
6357
  },
6393
6358
  {
6394
6359
  "id": "skills.ios.guideline.ios.dynamic-type-font-scaling-automa-tico",
6395
- "description": "Dynamic Type - fuentes escalables y layouts adaptativos",
6360
+ "description": "Dynamic Type - Font scaling automático",
6396
6361
  "severity": "WARN",
6397
6362
  "platform": "ios",
6398
6363
  "sourceSkill": "ios-guidelines",
@@ -6403,15 +6368,15 @@
6403
6368
  "origin": "core"
6404
6369
  },
6405
6370
  {
6406
- "id": "skills.ios.guideline.ios.en-produccio-n-ni-un-mocks-ni-un-spies-todo-real-de-apis-y-persistenci",
6407
- "description": "En producción ni un mocks ni un spies - Todo real de APIs y persistencia (Core Data, UserDefaults, Keychain)",
6371
+ "id": "skills.ios.guideline.ios.dynamic-type-fuentes-escalables-y-layouts-adaptativos",
6372
+ "description": "Dynamic Type - fuentes escalables y layouts adaptativos",
6408
6373
  "severity": "WARN",
6409
6374
  "platform": "ios",
6410
6375
  "sourceSkill": "ios-guidelines",
6411
6376
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6412
6377
  "confidence": "MEDIUM",
6413
6378
  "locked": true,
6414
- "evaluationMode": "DECLARATIVE",
6379
+ "evaluationMode": "AUTO",
6415
6380
  "origin": "core"
6416
6381
  },
6417
6382
  {
@@ -6811,28 +6776,16 @@
6811
6776
  "evaluationMode": "DECLARATIVE",
6812
6777
  "origin": "core"
6813
6778
  },
6814
- {
6815
- "id": "skills.ios.guideline.ios.jsonserialization-prohibido-usar-codable",
6816
- "description": "JSONSerialization - Prohibido, usar Codable",
6817
- "severity": "ERROR",
6818
- "platform": "ios",
6819
- "sourceSkill": "ios-guidelines",
6820
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6821
- "confidence": "HIGH",
6822
- "locked": true,
6823
- "evaluationMode": "DECLARATIVE",
6824
- "origin": "core"
6825
- },
6826
6779
  {
6827
6780
  "id": "skills.ios.guideline.ios.keychain-passwords-tokens-no-userdefaults",
6828
- "description": "Keychain - Passwords, tokens (NO UserDefaults)",
6781
+ "description": "En producción ni un mocks ni un spies - Todo real de APIs y persistencia (Core Data, UserDefaults, Keychain)",
6829
6782
  "severity": "WARN",
6830
6783
  "platform": "ios",
6831
6784
  "sourceSkill": "ios-guidelines",
6832
6785
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6833
6786
  "confidence": "MEDIUM",
6834
6787
  "locked": true,
6835
- "evaluationMode": "DECLARATIVE",
6788
+ "evaluationMode": "AUTO",
6836
6789
  "origin": "core"
6837
6790
  },
6838
6791
  {
@@ -6859,30 +6812,6 @@
6859
6812
  "evaluationMode": "DECLARATIVE",
6860
6813
  "origin": "core"
6861
6814
  },
6862
- {
6863
- "id": "skills.ios.guideline.ios.lazy-loading-lazyvstack-on-demand-data",
6864
- "description": "Lazy loading - LazyVStack, on-demand data",
6865
- "severity": "WARN",
6866
- "platform": "ios",
6867
- "sourceSkill": "ios-guidelines",
6868
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6869
- "confidence": "MEDIUM",
6870
- "locked": true,
6871
- "evaluationMode": "DECLARATIVE",
6872
- "origin": "core"
6873
- },
6874
- {
6875
- "id": "skills.ios.guideline.ios.lazyvstack-lazyhstack-para-listas-grandes",
6876
- "description": "LazyVStack/LazyHStack - Para listas grandes",
6877
- "severity": "WARN",
6878
- "platform": "ios",
6879
- "sourceSkill": "ios-guidelines",
6880
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6881
- "confidence": "MEDIUM",
6882
- "locked": true,
6883
- "evaluationMode": "DECLARATIVE",
6884
- "origin": "core"
6885
- },
6886
6815
  {
6887
6816
  "id": "skills.ios.guideline.ios.libreri-as-de-terceros-usar-apis-nativas",
6888
6817
  "description": "Librerías de terceros - Usar APIs nativas",
@@ -6909,14 +6838,14 @@
6909
6838
  },
6910
6839
  {
6911
6840
  "id": "skills.ios.guideline.ios.localizable-strings-deprecado-usar-string-catalogs",
6912
- "description": "String Catalogs (.xcstrings)",
6913
- "severity": "WARN",
6841
+ "description": "Localizable.strings - Deprecado, usar String Catalogs",
6842
+ "severity": "ERROR",
6914
6843
  "platform": "ios",
6915
6844
  "sourceSkill": "ios-guidelines",
6916
6845
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6917
- "confidence": "MEDIUM",
6846
+ "confidence": "HIGH",
6918
6847
  "locked": true,
6919
- "evaluationMode": "AUTO",
6848
+ "evaluationMode": "DECLARATIVE",
6920
6849
  "origin": "core"
6921
6850
  },
6922
6851
  {
@@ -7063,30 +6992,6 @@
7063
6992
  "evaluationMode": "DECLARATIVE",
7064
6993
  "origin": "core"
7065
6994
  },
7066
- {
7067
- "id": "skills.ios.guideline.ios.navigationstack-navigationpath-con-rutas-tipadas",
7068
- "description": "NavigationStack + NavigationPath con rutas tipadas",
7069
- "severity": "WARN",
7070
- "platform": "ios",
7071
- "sourceSkill": "ios-guidelines",
7072
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7073
- "confidence": "MEDIUM",
7074
- "locked": true,
7075
- "evaluationMode": "DECLARATIVE",
7076
- "origin": "core"
7077
- },
7078
- {
7079
- "id": "skills.ios.guideline.ios.navigationstack-navigationpath-para-navegacio-n-moderna",
7080
- "description": "NavigationStack + NavigationPath - Para navegación moderna",
7081
- "severity": "WARN",
7082
- "platform": "ios",
7083
- "sourceSkill": "ios-guidelines",
7084
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7085
- "confidence": "MEDIUM",
7086
- "locked": true,
7087
- "evaluationMode": "DECLARATIVE",
7088
- "origin": "core"
7089
- },
7090
6995
  {
7091
6996
  "id": "skills.ios.guideline.ios.network-layer-abstrai-do-apiclient-protocol-en-domain",
7092
6997
  "description": "Network layer abstraído - APIClient protocol en Domain",
@@ -7132,7 +7037,7 @@
7132
7037
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7133
7038
  "confidence": "MEDIUM",
7134
7039
  "locked": true,
7135
- "evaluationMode": "DECLARATIVE",
7040
+ "evaluationMode": "AUTO",
7136
7041
  "origin": "core"
7137
7042
  },
7138
7043
  {
@@ -7219,18 +7124,6 @@
7219
7124
  "evaluationMode": "DECLARATIVE",
7220
7125
  "origin": "core"
7221
7126
  },
7222
- {
7223
- "id": "skills.ios.guideline.ios.onchange-of-con-variante-de-2-para-metros-o-sin-para-metros",
7224
- "description": "onChange(of:) con variante de 2 parámetros o sin parámetros",
7225
- "severity": "WARN",
7226
- "platform": "ios",
7227
- "sourceSkill": "ios-guidelines",
7228
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7229
- "confidence": "MEDIUM",
7230
- "locked": true,
7231
- "evaluationMode": "DECLARATIVE",
7232
- "origin": "core"
7233
- },
7234
7127
  {
7235
7128
  "id": "skills.ios.guideline.ios.opaque-types-some-view-some-publisher-cuando-sea-apropiado",
7236
7129
  "description": "Opaque types - some View, some Publisher cuando sea apropiado",
@@ -7348,7 +7241,7 @@
7348
7241
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7349
7242
  "confidence": "MEDIUM",
7350
7243
  "locked": true,
7351
- "evaluationMode": "DECLARATIVE",
7244
+ "evaluationMode": "AUTO",
7352
7245
  "origin": "core"
7353
7246
  },
7354
7247
  {
@@ -7540,7 +7433,7 @@
7540
7433
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7541
7434
  "confidence": "HIGH",
7542
7435
  "locked": true,
7543
- "evaluationMode": "DECLARATIVE",
7436
+ "evaluationMode": "AUTO",
7544
7437
  "origin": "core"
7545
7438
  },
7546
7439
  {
@@ -7696,7 +7589,7 @@
7696
7589
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7697
7590
  "confidence": "HIGH",
7698
7591
  "locked": true,
7699
- "evaluationMode": "DECLARATIVE",
7592
+ "evaluationMode": "AUTO",
7700
7593
  "origin": "core"
7701
7594
  },
7702
7595
  {
@@ -7819,6 +7712,30 @@
7819
7712
  "evaluationMode": "DECLARATIVE",
7820
7713
  "origin": "core"
7821
7714
  },
7715
+ {
7716
+ "id": "skills.ios.guideline.ios.string-catalogs-xcstrings",
7717
+ "description": "String Catalogs (.xcstrings)",
7718
+ "severity": "WARN",
7719
+ "platform": "ios",
7720
+ "sourceSkill": "ios-guidelines",
7721
+ "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7722
+ "confidence": "MEDIUM",
7723
+ "locked": true,
7724
+ "evaluationMode": "DECLARATIVE",
7725
+ "origin": "core"
7726
+ },
7727
+ {
7728
+ "id": "skills.ios.guideline.ios.string-catalogs-xcstrings-sistema-moderno-de-localizacio-n-xcode-15",
7729
+ "description": "String Catalogs (.xcstrings) - Sistema moderno de localización (Xcode 15+)",
7730
+ "severity": "WARN",
7731
+ "platform": "ios",
7732
+ "sourceSkill": "ios-guidelines",
7733
+ "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7734
+ "confidence": "MEDIUM",
7735
+ "locked": true,
7736
+ "evaluationMode": "DECLARATIVE",
7737
+ "origin": "core"
7738
+ },
7822
7739
  {
7823
7740
  "id": "skills.ios.guideline.ios.struct-por-defecto-class-solo-cuando-necesites-identity-o-herencia",
7824
7741
  "description": "struct por defecto - class solo cuando necesites identity o herencia",
@@ -8080,7 +7997,7 @@
8080
7997
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
8081
7998
  "confidence": "MEDIUM",
8082
7999
  "locked": true,
8083
- "evaluationMode": "DECLARATIVE",
8000
+ "evaluationMode": "AUTO",
8084
8001
  "origin": "core"
8085
8002
  },
8086
8003
  {
@@ -8215,18 +8132,6 @@
8215
8132
  "evaluationMode": "DECLARATIVE",
8216
8133
  "origin": "core"
8217
8134
  },
8218
- {
8219
- "id": "skills.ios.guideline.ios.xctest-solo-para-proyectos-legacy-o-ui-tests",
8220
- "description": "XCTest - Solo para proyectos legacy o UI tests",
8221
- "severity": "WARN",
8222
- "platform": "ios",
8223
- "sourceSkill": "ios-guidelines",
8224
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
8225
- "confidence": "MEDIUM",
8226
- "locked": true,
8227
- "evaluationMode": "DECLARATIVE",
8228
- "origin": "core"
8229
- },
8230
8135
  {
8231
8136
  "id": "skills.ios.guideline.ios.xcuitest-ui-testing-nativo",
8232
8137
  "description": "XCUITest - UI testing nativo",
@@ -8360,9 +8265,21 @@
8360
8265
  "evaluationMode": "AUTO",
8361
8266
  "origin": "core"
8362
8267
  },
8268
+ {
8269
+ "id": "skills.ios.no-legacy-onchange",
8270
+ "description": "onChange(of:) con variante de 2 parámetros o sin parámetros",
8271
+ "severity": "WARN",
8272
+ "platform": "ios",
8273
+ "sourceSkill": "ios-guidelines",
8274
+ "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
8275
+ "confidence": "MEDIUM",
8276
+ "locked": true,
8277
+ "evaluationMode": "AUTO",
8278
+ "origin": "core"
8279
+ },
8363
8280
  {
8364
8281
  "id": "skills.ios.no-navigation-view",
8365
- "description": "NavigationStack en lugar de NavigationView",
8282
+ "description": "NavigationStack + NavigationPath - Para navegación moderna",
8366
8283
  "severity": "WARN",
8367
8284
  "platform": "ios",
8368
8285
  "sourceSkill": "ios-guidelines",
@@ -8491,6 +8408,18 @@
8491
8408
  "locked": true,
8492
8409
  "evaluationMode": "AUTO",
8493
8410
  "origin": "core"
8411
+ },
8412
+ {
8413
+ "id": "skills.ios.prefer-swift-testing",
8414
+ "description": "XCTest - Solo para proyectos legacy o UI tests",
8415
+ "severity": "WARN",
8416
+ "platform": "ios",
8417
+ "sourceSkill": "ios-guidelines",
8418
+ "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
8419
+ "confidence": "MEDIUM",
8420
+ "locked": true,
8421
+ "evaluationMode": "AUTO",
8422
+ "origin": "core"
8494
8423
  }
8495
8424
  ]
8496
8425
  },
@@ -8498,7 +8427,7 @@
8498
8427
  "name": "ios-swift-testing-guidelines",
8499
8428
  "version": "1.0.0",
8500
8429
  "source": "file:vendor/skills/swift-testing-expert/SKILL.md",
8501
- "hash": "f75a4765dafca2b9d6d174ccd13ad601ab8b5b0ebe70719073831b563ca9dfda",
8430
+ "hash": "48249808a0c025bb5fb16ac2e5cec4d44ac4b0634273509ee2165ad60d0fe81f",
8502
8431
  "rules": [
8503
8432
  {
8504
8433
  "id": "skills.ios.guideline.ios-swift-testing.keep-tests-isolated-expressive-and-aligned-with-swift-concurrency",
@@ -8512,18 +8441,6 @@
8512
8441
  "evaluationMode": "DECLARATIVE",
8513
8442
  "origin": "core"
8514
8443
  },
8515
- {
8516
- "id": "skills.ios.guideline.ios-swift-testing.new-xctest-only-unit-tests-when-swift-testing-is-available",
8517
- "description": "New XCTest-only unit tests when Swift Testing is available.",
8518
- "severity": "ERROR",
8519
- "platform": "ios",
8520
- "sourceSkill": "ios-swift-testing-guidelines",
8521
- "sourcePath": "vendor/skills/swift-testing-expert/SKILL.md",
8522
- "confidence": "HIGH",
8523
- "locked": true,
8524
- "evaluationMode": "DECLARATIVE",
8525
- "origin": "core"
8526
- },
8527
8444
  {
8528
8445
  "id": "skills.ios.guideline.ios-swift-testing.preserve-repository-specific-test-contracts-such-as-makesut-and-memory",
8529
8446
  "description": "Preserve repository-specific test contracts such as makeSUT() and memory-leak tracking helpers when they are mandatory.",
@@ -8620,7 +8537,7 @@
8620
8537
  "name": "ios-swiftui-expert-guidelines",
8621
8538
  "version": "1.0.0",
8622
8539
  "source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
8623
- "hash": "5a759b80ddf1308e23cd04aef7e1628abf13dcd0e9d7649039da10a40f1de860",
8540
+ "hash": "d4914b86eb46b5cf0408a922c4f034789a672d95ac6c01922e94257044b8aa01",
8624
8541
  "rules": [
8625
8542
  {
8626
8543
  "id": "skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic",
@@ -8943,7 +8860,7 @@
8943
8860
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8944
8861
  "confidence": "MEDIUM",
8945
8862
  "locked": true,
8946
- "evaluationMode": "DECLARATIVE",
8863
+ "evaluationMode": "AUTO",
8947
8864
  "origin": "core"
8948
8865
  },
8949
8866
  {