pumuki 6.3.154 → 6.3.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/facts/extractHeuristicFacts.ts +2 -0
- package/core/rules/presets/heuristics/ios.test.ts +11 -1
- package/core/rules/presets/heuristics/ios.ts +36 -0
- package/integrations/config/skillsCompilerTemplates.ts +18 -0
- package/integrations/config/skillsDetectorRegistry.ts +8 -0
- package/integrations/git/gitAtomicity.ts +64 -0
- package/integrations/git/runPlatformGate.ts +44 -1
- package/package.json +1 -1
- package/skills.lock.json +28 -2
|
@@ -713,6 +713,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
713
713
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPassedValueStateWrapperUsage, ruleId: 'heuristics.ios.passed-value-state-wrapper.ast', code: 'HEURISTICS_IOS_PASSED_VALUE_STATE_WRAPPER_AST', message: 'AST heuristic detected a passed value stored as @State/@StateObject via init wrapper ownership.' },
|
|
714
714
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStateWrapperWithoutPrivateUsage, ruleId: 'heuristics.ios.swiftui.state-wrapper-private.ast', code: 'HEURISTICS_IOS_SWIFTUI_STATE_WRAPPER_PRIVATE_AST', message: 'AST heuristic detected @State/@StateObject usage in a SwiftUI View without private visibility.' },
|
|
715
715
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachIndicesUsage, ruleId: 'heuristics.ios.foreach-indices.ast', code: 'HEURISTICS_IOS_FOREACH_INDICES_AST', message: 'AST heuristic detected ForEach(...indices...) usage where stable element identity may be preferred.' },
|
|
716
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInlineFilteringInForEachUsage, ruleId: 'heuristics.ios.swiftui.inline-filtering-in-foreach.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FILTERING_IN_FOREACH_AST', message: 'AST heuristic detected inline filtering inside ForEach; prefilter and cache before rendering.' },
|
|
717
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftExplicitColorStaticMemberUsage, ruleId: 'heuristics.ios.swiftui.explicit-color-static-member.ast', code: 'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST', message: 'AST heuristic detected explicit Color static member lookup where contextual .color style is preferred.' },
|
|
716
718
|
{ platform: 'ios', pathCheck: isIOSApplicationOrPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftContainsUserFilterUsage, ruleId: 'heuristics.ios.contains-user-filter.ast', code: 'HEURISTICS_IOS_CONTAINS_USER_FILTER_AST', message: 'AST heuristic detected contains() in a user-facing filter where localizedStandardContains() may be preferred.' },
|
|
717
719
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftGeometryReaderUsage, ruleId: 'heuristics.ios.geometryreader.ast', code: 'HEURISTICS_IOS_GEOMETRYREADER_AST', message: 'AST heuristic detected GeometryReader usage that may be replaceable with modern layout APIs.' },
|
|
718
720
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftFontWeightBoldUsage, ruleId: 'heuristics.ios.font-weight-bold.ast', code: 'HEURISTICS_IOS_FONT_WEIGHT_BOLD_AST', message: 'AST heuristic detected fontWeight(.bold) usage where bold() may be preferred.' },
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { iosRules } from './ios';
|
|
4
4
|
|
|
5
5
|
test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
6
|
-
assert.equal(iosRules.length,
|
|
6
|
+
assert.equal(iosRules.length, 44);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -25,6 +25,8 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
25
25
|
'heuristics.ios.legacy-swiftui-observable-wrapper.ast',
|
|
26
26
|
'heuristics.ios.passed-value-state-wrapper.ast',
|
|
27
27
|
'heuristics.ios.foreach-indices.ast',
|
|
28
|
+
'heuristics.ios.swiftui.inline-filtering-in-foreach.ast',
|
|
29
|
+
'heuristics.ios.swiftui.explicit-color-static-member.ast',
|
|
28
30
|
'heuristics.ios.contains-user-filter.ast',
|
|
29
31
|
'heuristics.ios.geometryreader.ast',
|
|
30
32
|
'heuristics.ios.font-weight-bold.ast',
|
|
@@ -84,6 +86,14 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
84
86
|
byId.get('heuristics.ios.foreach-indices.ast')?.then.code,
|
|
85
87
|
'HEURISTICS_IOS_FOREACH_INDICES_AST'
|
|
86
88
|
);
|
|
89
|
+
assert.equal(
|
|
90
|
+
byId.get('heuristics.ios.swiftui.inline-filtering-in-foreach.ast')?.then.code,
|
|
91
|
+
'HEURISTICS_IOS_SWIFTUI_INLINE_FILTERING_IN_FOREACH_AST'
|
|
92
|
+
);
|
|
93
|
+
assert.equal(
|
|
94
|
+
byId.get('heuristics.ios.swiftui.explicit-color-static-member.ast')?.then.code,
|
|
95
|
+
'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST'
|
|
96
|
+
);
|
|
87
97
|
assert.equal(
|
|
88
98
|
byId.get('heuristics.ios.contains-user-filter.ast')?.then.code,
|
|
89
99
|
'HEURISTICS_IOS_CONTAINS_USER_FILTER_AST'
|
|
@@ -325,6 +325,42 @@ export const iosRules: RuleSet = [
|
|
|
325
325
|
code: 'HEURISTICS_IOS_FOREACH_INDICES_AST',
|
|
326
326
|
},
|
|
327
327
|
},
|
|
328
|
+
{
|
|
329
|
+
id: 'heuristics.ios.swiftui.inline-filtering-in-foreach.ast',
|
|
330
|
+
description: 'Detects inline filter chains inside SwiftUI ForEach rendering paths.',
|
|
331
|
+
severity: 'WARN',
|
|
332
|
+
platform: 'ios',
|
|
333
|
+
locked: true,
|
|
334
|
+
when: {
|
|
335
|
+
kind: 'Heuristic',
|
|
336
|
+
where: {
|
|
337
|
+
ruleId: 'heuristics.ios.swiftui.inline-filtering-in-foreach.ast',
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
then: {
|
|
341
|
+
kind: 'Finding',
|
|
342
|
+
message: 'AST heuristic detected inline filtering inside ForEach; prefilter and cache before rendering.',
|
|
343
|
+
code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FILTERING_IN_FOREACH_AST',
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'heuristics.ios.swiftui.explicit-color-static-member.ast',
|
|
348
|
+
description: 'Detects explicit Color.* static member lookup in SwiftUI presentation code.',
|
|
349
|
+
severity: 'WARN',
|
|
350
|
+
platform: 'ios',
|
|
351
|
+
locked: true,
|
|
352
|
+
when: {
|
|
353
|
+
kind: 'Heuristic',
|
|
354
|
+
where: {
|
|
355
|
+
ruleId: 'heuristics.ios.swiftui.explicit-color-static-member.ast',
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
then: {
|
|
359
|
+
kind: 'Finding',
|
|
360
|
+
message: 'AST heuristic detected explicit Color static member lookup where contextual .color style is preferred.',
|
|
361
|
+
code: 'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST',
|
|
362
|
+
},
|
|
363
|
+
},
|
|
328
364
|
{
|
|
329
365
|
id: 'heuristics.ios.contains-user-filter.ast',
|
|
330
366
|
description: 'Detects contains() usage in user-facing filter flows where localizedStandardContains() may be preferred.',
|
|
@@ -257,6 +257,24 @@ export const skillsCompilerTemplates: Record<string, SkillsCompilerTemplate> = {
|
|
|
257
257
|
stage: 'PRE_PUSH',
|
|
258
258
|
locked: true,
|
|
259
259
|
},
|
|
260
|
+
{
|
|
261
|
+
id: 'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache',
|
|
262
|
+
description: 'Avoid inline filtering in ForEach; prefilter and cache before rendering.',
|
|
263
|
+
severity: 'ERROR',
|
|
264
|
+
platform: 'ios',
|
|
265
|
+
confidence: 'HIGH',
|
|
266
|
+
stage: 'PRE_PUSH',
|
|
267
|
+
locked: true,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: 'skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue',
|
|
271
|
+
description: 'Prefer SwiftUI contextual static member lookup such as .blue instead of Color.blue.',
|
|
272
|
+
severity: 'WARN',
|
|
273
|
+
platform: 'ios',
|
|
274
|
+
confidence: 'HIGH',
|
|
275
|
+
stage: 'PRE_PUSH',
|
|
276
|
+
locked: true,
|
|
277
|
+
},
|
|
260
278
|
{
|
|
261
279
|
id: 'skills.ios.no-contains-user-filter',
|
|
262
280
|
description:
|
|
@@ -94,6 +94,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
94
94
|
'skills.ios.no-foreach-indices': heuristicDetector('ios.foreach-indices', [
|
|
95
95
|
'heuristics.ios.foreach-indices.ast',
|
|
96
96
|
]),
|
|
97
|
+
'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache':
|
|
98
|
+
heuristicDetector('ios.swiftui.inline-filtering-in-foreach', [
|
|
99
|
+
'heuristics.ios.swiftui.inline-filtering-in-foreach.ast',
|
|
100
|
+
]),
|
|
101
|
+
'skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue':
|
|
102
|
+
heuristicDetector('ios.swiftui.explicit-color-static-member', [
|
|
103
|
+
'heuristics.ios.swiftui.explicit-color-static-member.ast',
|
|
104
|
+
]),
|
|
97
105
|
'skills.ios.no-contains-user-filter': heuristicDetector('ios.contains-user-filter', [
|
|
98
106
|
'heuristics.ios.contains-user-filter.ast',
|
|
99
107
|
]),
|
|
@@ -205,6 +205,60 @@ const collectScopePaths = (
|
|
|
205
205
|
return normalized;
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
+
const isSkillsContractCarrierPath = (path: string): boolean => {
|
|
209
|
+
const normalized = path.replace(/\\/g, '/').trim().toLowerCase();
|
|
210
|
+
return (
|
|
211
|
+
normalized === 'agents.md' ||
|
|
212
|
+
normalized === 'skills.lock.json' ||
|
|
213
|
+
normalized === 'skills.sources.json' ||
|
|
214
|
+
normalized.startsWith('vendor/skills/') ||
|
|
215
|
+
normalized.startsWith('docs/codex-skills/') ||
|
|
216
|
+
normalized === '.pumuki/policy-as-code.json'
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const isSkillsEnforcementImplementationPath = (path: string): boolean => {
|
|
221
|
+
const normalized = path.replace(/\\/g, '/').trim().toLowerCase();
|
|
222
|
+
return (
|
|
223
|
+
normalized.endsWith('.feature') ||
|
|
224
|
+
normalized.startsWith('core/facts/') ||
|
|
225
|
+
normalized.startsWith('core/rules/presets/heuristics/') ||
|
|
226
|
+
normalized.startsWith('integrations/config/') ||
|
|
227
|
+
normalized === 'integrations/git/runplatformgate.ts' ||
|
|
228
|
+
normalized === 'integrations/git/__tests__/runplatformgate.test.ts' ||
|
|
229
|
+
normalized === 'integrations/git/gitatomicity.ts' ||
|
|
230
|
+
normalized === 'integrations/git/__tests__/gitatomicity.test.ts' ||
|
|
231
|
+
isSkillsContractCarrierPath(normalized)
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const isSkillsEnforcementRemediationDiff = (
|
|
236
|
+
paths: ReadonlyArray<string>
|
|
237
|
+
): boolean => {
|
|
238
|
+
if (paths.length === 0) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const normalizedPaths = paths.map((path) => path.replace(/\\/g, '/').trim().toLowerCase());
|
|
243
|
+
const touchesDetectorSurface = normalizedPaths.some((path) =>
|
|
244
|
+
path.startsWith('core/facts/') ||
|
|
245
|
+
path.startsWith('core/rules/presets/heuristics/') ||
|
|
246
|
+
path.startsWith('integrations/config/') ||
|
|
247
|
+
path === 'integrations/git/runplatformgate.ts' ||
|
|
248
|
+
path === 'integrations/git/__tests__/runplatformgate.test.ts' ||
|
|
249
|
+
path === 'integrations/git/gitatomicity.ts' ||
|
|
250
|
+
path === 'integrations/git/__tests__/gitatomicity.test.ts'
|
|
251
|
+
);
|
|
252
|
+
const touchesLockOrScenario = normalizedPaths.some((path) =>
|
|
253
|
+
path === 'skills.lock.json' || path.endsWith('.feature')
|
|
254
|
+
);
|
|
255
|
+
return (
|
|
256
|
+
touchesDetectorSurface &&
|
|
257
|
+
touchesLockOrScenario &&
|
|
258
|
+
normalizedPaths.every((path) => isSkillsEnforcementImplementationPath(path))
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
|
|
208
262
|
const buildAtomicSlicesRemediation = (params: {
|
|
209
263
|
git: IGitService;
|
|
210
264
|
repoRoot: string;
|
|
@@ -449,6 +503,9 @@ const buildPrePushCommitPathLimitViolations = (params: {
|
|
|
449
503
|
repoRoot: params.repoRoot,
|
|
450
504
|
commitHash: commitRecord.hash,
|
|
451
505
|
}).filter((path) => !isManagedEvidencePath(path));
|
|
506
|
+
if (isSkillsEnforcementRemediationDiff(changedPaths)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
452
509
|
violations.push(
|
|
453
510
|
...buildPathLimitViolations({
|
|
454
511
|
changedPaths,
|
|
@@ -493,6 +550,13 @@ export const evaluateGitAtomicity = (params: {
|
|
|
493
550
|
repoRoot,
|
|
494
551
|
stage: params.stage,
|
|
495
552
|
});
|
|
553
|
+
if (params.stage === 'PRE_COMMIT' && isSkillsEnforcementRemediationDiff(changedPaths)) {
|
|
554
|
+
return {
|
|
555
|
+
enabled: true,
|
|
556
|
+
allowed: true,
|
|
557
|
+
violations: [],
|
|
558
|
+
};
|
|
559
|
+
}
|
|
496
560
|
|
|
497
561
|
const prePushCommitViolations =
|
|
498
562
|
params.stage === 'PRE_PUSH'
|
|
@@ -436,6 +436,44 @@ const isSkillsContractCarrierPath = (path: string): boolean => {
|
|
|
436
436
|
);
|
|
437
437
|
};
|
|
438
438
|
|
|
439
|
+
const isSkillsEnforcementImplementationPath = (path: string): boolean => {
|
|
440
|
+
const normalized = toNormalizedPath(path).toLowerCase();
|
|
441
|
+
return (
|
|
442
|
+
normalized.endsWith('.feature') ||
|
|
443
|
+
normalized.startsWith('core/facts/') ||
|
|
444
|
+
normalized.startsWith('core/rules/presets/heuristics/') ||
|
|
445
|
+
normalized.startsWith('integrations/config/') ||
|
|
446
|
+
normalized === 'integrations/git/runplatformgate.ts' ||
|
|
447
|
+
normalized === 'integrations/git/__tests__/runplatformgate.test.ts' ||
|
|
448
|
+
isSkillsContractCarrierPath(normalized)
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const isSkillsEnforcementRemediationDiff = (
|
|
453
|
+
paths: ReadonlyArray<string>
|
|
454
|
+
): boolean => {
|
|
455
|
+
if (paths.length === 0) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const normalizedPaths = paths.map((path) => toNormalizedPath(path));
|
|
460
|
+
const touchesDetectorSurface = normalizedPaths.some((path) =>
|
|
461
|
+
path.startsWith('core/facts/') ||
|
|
462
|
+
path.startsWith('core/rules/presets/heuristics/') ||
|
|
463
|
+
path.startsWith('integrations/config/') ||
|
|
464
|
+
path === 'integrations/git/runplatformgate.ts' ||
|
|
465
|
+
path === 'integrations/git/__tests__/runplatformgate.test.ts'
|
|
466
|
+
);
|
|
467
|
+
const touchesLockOrScenario = normalizedPaths.some((path) =>
|
|
468
|
+
path === 'skills.lock.json' || path.endsWith('.feature')
|
|
469
|
+
);
|
|
470
|
+
return (
|
|
471
|
+
touchesDetectorSurface &&
|
|
472
|
+
touchesLockOrScenario &&
|
|
473
|
+
normalizedPaths.every((path) => isSkillsEnforcementImplementationPath(path))
|
|
474
|
+
);
|
|
475
|
+
};
|
|
476
|
+
|
|
439
477
|
const collectStagedPaths = (git: IGitService, repoRoot: string): ReadonlyArray<string> => {
|
|
440
478
|
try {
|
|
441
479
|
return git.runGit(['diff', '--cached', '--name-only'], repoRoot)
|
|
@@ -1433,7 +1471,12 @@ export async function runPlatformGate(params: {
|
|
|
1433
1471
|
].sort(),
|
|
1434
1472
|
})
|
|
1435
1473
|
: undefined;
|
|
1436
|
-
const
|
|
1474
|
+
const skillsEnforcementRemediationDiff = isSkillsEnforcementRemediationDiff(stagedPaths);
|
|
1475
|
+
const remediationProgressAllowsGlobalGap =
|
|
1476
|
+
remediationProgressFinding !== undefined ||
|
|
1477
|
+
(skillsEnforcementRemediationDiff &&
|
|
1478
|
+
!hasNativeBlockingFinding &&
|
|
1479
|
+
!hasTddBddBlockingFinding);
|
|
1437
1480
|
const effectiveTddBddFindings = remediationProgressAllowsGlobalGap
|
|
1438
1481
|
? tddBddEvaluation.findings.map((finding) =>
|
|
1439
1482
|
finding.code === 'TDD_BDD_EVIDENCE_STALE'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.156",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/skills.lock.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0",
|
|
3
3
|
"compilerVersion": "1.0.0",
|
|
4
|
-
"generatedAt": "2026-05-05T20:
|
|
4
|
+
"generatedAt": "2026-05-05T20:56:13.453Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -8288,7 +8288,7 @@
|
|
|
8288
8288
|
"name": "ios-swiftui-expert-guidelines",
|
|
8289
8289
|
"version": "1.0.0",
|
|
8290
8290
|
"source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8291
|
-
"hash": "
|
|
8291
|
+
"hash": "d73de37053af4a98ae690284715bb6d4b73d10a64d137670633e3b0a98705012",
|
|
8292
8292
|
"rules": [
|
|
8293
8293
|
{
|
|
8294
8294
|
"id": "skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear",
|
|
@@ -8305,6 +8305,19 @@
|
|
|
8305
8305
|
},
|
|
8306
8306
|
{
|
|
8307
8307
|
"id": "skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache",
|
|
8308
|
+
"description": "Avoid inline filtering in ForEach; prefilter and cache before rendering.",
|
|
8309
|
+
"severity": "ERROR",
|
|
8310
|
+
"platform": "ios",
|
|
8311
|
+
"confidence": "HIGH",
|
|
8312
|
+
"stage": "PRE_PUSH",
|
|
8313
|
+
"locked": true,
|
|
8314
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8315
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8316
|
+
"evaluationMode": "AUTO",
|
|
8317
|
+
"origin": "core"
|
|
8318
|
+
},
|
|
8319
|
+
{
|
|
8320
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache-2",
|
|
8308
8321
|
"description": "Avoid inline filtering in ForEach (prefilter and cache)",
|
|
8309
8322
|
"severity": "ERROR",
|
|
8310
8323
|
"platform": "ios",
|
|
@@ -8365,6 +8378,19 @@
|
|
|
8365
8378
|
},
|
|
8366
8379
|
{
|
|
8367
8380
|
"id": "skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue",
|
|
8381
|
+
"description": "Prefer SwiftUI contextual static member lookup such as .blue instead of Color.blue.",
|
|
8382
|
+
"severity": "WARN",
|
|
8383
|
+
"platform": "ios",
|
|
8384
|
+
"confidence": "HIGH",
|
|
8385
|
+
"stage": "PRE_PUSH",
|
|
8386
|
+
"locked": true,
|
|
8387
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8388
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8389
|
+
"evaluationMode": "AUTO",
|
|
8390
|
+
"origin": "core"
|
|
8391
|
+
},
|
|
8392
|
+
{
|
|
8393
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.prefer-static-member-lookup-blue-vs-color-blue-2",
|
|
8368
8394
|
"description": "Prefer static member lookup (.blue vs Color.blue)",
|
|
8369
8395
|
"severity": "WARN",
|
|
8370
8396
|
"platform": "ios",
|