pumuki 6.3.59 → 6.3.61
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/VERSION +1 -1
- package/assets/rule-packs/ios-swiftui-modernization-v1.json +77 -0
- package/bin/_run-ts-entry.js +4 -1
- package/core/facts/detectors/text/ios.test.ts +151 -0
- package/core/facts/detectors/text/ios.ts +75 -0
- package/core/facts/detectors/text/iosSwiftUiModernizationSnapshot.test.ts +31 -0
- package/core/facts/detectors/text/iosSwiftUiModernizationSnapshot.ts +110 -0
- package/core/facts/extractHeuristicFacts.ts +14 -1
- package/core/gate/GateStage.test.ts +6 -4
- package/core/gate/GateStage.ts +6 -1
- package/core/rules/presets/heuristics/ios.test.ts +22 -1
- package/core/rules/presets/heuristics/ios.ts +162 -0
- package/core/rules/presets/iosEnterpriseRuleSet.test.ts +49 -1
- package/core/rules/presets/iosEnterpriseRuleSet.ts +4 -11
- package/docs/README.md +5 -10
- package/docs/codex-skills/core-data-expert.md +23 -0
- package/docs/codex-skills/swift-testing-expert.md +25 -0
- package/docs/operations/RELEASE_NOTES.md +28 -33
- package/docs/product/API_REFERENCE.md +35 -16
- package/docs/product/CONFIGURATION.md +8 -1
- package/docs/product/HOW_IT_WORKS.md +9 -9
- package/docs/product/TESTING.md +28 -0
- package/docs/product/USAGE.md +60 -18
- package/docs/rule-packs/ios.md +2 -0
- package/docs/validation/README.md +31 -7
- package/integrations/config/compileSkillsLock.ts +1 -1
- package/integrations/config/coreSkillsLock.ts +20 -10
- package/integrations/config/skillsCompilerTemplates.ts +97 -0
- package/integrations/config/skillsCustomRules.ts +6 -4
- package/integrations/config/skillsDetectorRegistry.ts +30 -0
- package/integrations/config/skillsMarkdownRules.ts +56 -0
- package/integrations/config/skillsRuleSet.ts +1 -0
- package/integrations/evidence/buildEvidence.ts +18 -0
- package/integrations/evidence/repoState.ts +8 -20
- package/integrations/gate/stagePolicies.ts +50 -550
- package/integrations/git/brownfieldHotspots.ts +309 -0
- package/integrations/git/runPlatformGate.ts +81 -19
- package/integrations/lifecycle/cli.ts +293 -21
- package/integrations/lifecycle/doctor.ts +51 -10
- package/integrations/lifecycle/experimentalFeaturesSnapshot.ts +61 -0
- package/integrations/lifecycle/packageInfo.ts +22 -5
- package/integrations/lifecycle/policyValidationSnapshot.ts +6 -0
- package/integrations/lifecycle/saasIngestionContract.ts +1 -1
- package/integrations/lifecycle/status.ts +6 -0
- package/integrations/mcp/aiGateCheck.ts +7 -3
- package/integrations/mcp/autoExecuteAiStart.ts +7 -3
- package/integrations/mcp/enterpriseServer.ts +49 -2
- package/integrations/mcp/preFlightCheck.ts +7 -3
- package/integrations/policy/experimentalFeatures.ts +189 -0
- package/integrations/policy/heuristicsEnforcement.ts +65 -0
- package/integrations/policy/policyAsCode.ts +250 -0
- package/integrations/policy/policyProfiles.ts +435 -0
- package/integrations/policy/preWriteEnforcement.ts +19 -31
- package/integrations/policy/sddCompletenessEnforcement.ts +57 -0
- package/integrations/policy/skillsEnforcement.ts +1 -10
- package/integrations/policy/tddBddEnforcement.ts +103 -0
- package/integrations/sdd/policy.ts +125 -39
- package/integrations/sdd/types.ts +2 -0
- package/integrations/tdd/types.ts +1 -1
- package/integrations/telemetry/gateTelemetry.ts +4 -0
- package/package.json +38 -3
- package/scripts/adapter-readiness-markdown-next-actions-lib.ts +1 -1
- package/scripts/adapter-real-session-analysis-evaluation-lib.ts +4 -0
- package/scripts/adapter-real-session-analysis-messages-lib.ts +13 -0
- package/scripts/adapter-real-session-contract.ts +3 -0
- package/scripts/adapter-real-session-markdown-sections-context-lib.ts +8 -2
- package/scripts/adapter-real-session-status-parser-lib.ts +29 -1
- package/scripts/adapter-session-status-capabilities-lib.ts +77 -0
- package/scripts/adapter-session-status-command-lib.ts +18 -6
- package/scripts/adapter-session-status-contract.ts +20 -5
- package/scripts/adapter-session-status-markdown-lib.ts +30 -7
- package/scripts/adapter-session-status-verdict-lib.ts +15 -4
- package/scripts/build-adapter-session-status.ts +5 -3
- package/scripts/build-consumer-menu-matrix-baseline.ts +5 -0
- package/scripts/build-consumer-startup-triage-runner-lib.ts +2 -4
- package/scripts/check-tracking-single-active.sh +12 -12
- package/scripts/compile-skills-lock.ts +9 -2
- package/scripts/consumer-menu-matrix-baseline-builder-lib.ts +114 -0
- package/scripts/consumer-menu-matrix-baseline-cli-lib.ts +117 -0
- package/scripts/consumer-menu-matrix-baseline-report-lib.ts +263 -0
- package/scripts/consumer-startup-triage-command-builders-core-required-lib.ts +9 -2
- package/scripts/consumer-startup-triage-command-builders-support-bundle-lib.ts +5 -1
- package/scripts/consumer-startup-triage-command-builders-support-followup-lib.ts +9 -2
- package/scripts/consumer-startup-triage-command-builders-workflow-lint-lib.ts +9 -6
- package/scripts/consumer-startup-triage-contract.ts +2 -1
- package/scripts/consumer-startup-triage-markdown-lib.ts +1 -1
- package/scripts/consumer-startup-triage-script-paths-lib.ts +17 -0
- package/scripts/consumer-support-bundle-markdown-sections-support-payload-lib.ts +57 -3
- package/scripts/consumer-support-ticket-draft-lib.ts +2 -1
- package/scripts/consumer-support-ticket-draft-primary-sections-lib.ts +49 -4
- package/scripts/consumer-workflow-lint-markdown-lib.ts +8 -1
- package/scripts/enterprise-contract-suite-contract.ts +2 -2
- package/scripts/framework-menu-actions-diagnostics-adapter-lib.ts +17 -3
- package/scripts/framework-menu-actions-diagnostics-maintenance-lib.ts +1 -1
- package/scripts/framework-menu-actions-diagnostics-support-core-lib.ts +3 -3
- package/scripts/framework-menu-actions-diagnostics-support-triage-lib.ts +2 -2
- package/scripts/framework-menu-actions-gates-stage-lib.ts +3 -3
- package/scripts/framework-menu-actions-phase5-exec-lib.ts +2 -2
- package/scripts/framework-menu-actions-phase5-reports-lib.ts +4 -4
- package/scripts/framework-menu-advanced-view-help.ts +15 -15
- package/scripts/framework-menu-builders-maintenance.ts +10 -4
- package/scripts/framework-menu-consumer-actions-lib.ts +9 -9
- package/scripts/framework-menu-consumer-runtime-actions.ts +27 -7
- package/scripts/framework-menu-consumer-runtime-audit.ts +163 -17
- package/scripts/framework-menu-consumer-runtime-lib.ts +10 -0
- package/scripts/framework-menu-consumer-runtime-menu.ts +17 -8
- package/scripts/framework-menu-consumer-runtime-types.ts +20 -6
- package/scripts/framework-menu-evidence-summary-normalize.ts +7 -0
- package/scripts/framework-menu-evidence-summary-read.ts +28 -0
- package/scripts/framework-menu-evidence-summary-severity.ts +129 -0
- package/scripts/framework-menu-evidence-summary-types.ts +16 -0
- package/scripts/framework-menu-gate-lib.ts +44 -7
- package/scripts/framework-menu-layout-data.ts +15 -10
- package/scripts/framework-menu-legacy-audit-markdown-document.ts +6 -2
- package/scripts/framework-menu-legacy-audit-render-panel.ts +3 -2
- package/scripts/framework-menu-legacy-audit-render-report.ts +20 -58
- package/scripts/framework-menu-legacy-audit-render-sections.ts +5 -7
- package/scripts/framework-menu-runner-constants.ts +2 -1
- package/scripts/framework-menu-runners-adapter-real-session-lib.ts +35 -12
- package/scripts/framework-menu-runners-adapter-session-lib.ts +35 -12
- package/scripts/framework-menu-runners-validation-custom-rules-lib.ts +10 -2
- package/scripts/framework-menu-runners-validation-skills-lib.ts +10 -2
- package/scripts/framework-menu-skills-lib.ts +8 -1
- package/scripts/framework-menu-system-notifications-gate.ts +12 -0
- package/scripts/framework-menu-system-notifications-lib.ts +1 -0
- package/scripts/framework-menu.ts +23 -14
- package/scripts/lint-consumer-workflows.ts +21 -12
- package/scripts/package-install-smoke-consumer-npm-lib.ts +41 -5
- package/scripts/package-install-smoke-workspace-factory-lib.ts +1 -1
- package/scripts/package-manifest-lib.ts +14 -0
- package/scripts/phase5-blockers-markdown-next-actions-blocked-lib.ts +3 -3
- package/scripts/phase5-blockers-markdown-next-actions-ready-lib.ts +1 -1
- package/scripts/phase5-execution-closure-plan-consumer-triage-lib.ts +4 -5
- package/scripts/phase5-execution-closure-status-markdown-next-actions-lib.ts +1 -1
- package/scripts/phase5-external-handoff-markdown-next-actions-lib.ts +1 -1
- package/scripts/prepare-phase5-escalation-submission.sh +1 -1
- package/scripts/refresh-phase5-latest-escalation.sh +1 -1
- package/scripts/run-phase5-post-support-refresh.sh +1 -1
- package/scripts/run-phase8-after-billing-reactivation.sh +2 -2
- package/scripts/run-phase8-autopilot.sh +3 -3
- package/scripts/run-phase8-close-ready.sh +2 -4
- package/scripts/run-phase8-doctor.sh +3 -3
- package/scripts/run-phase8-next-step.sh +3 -3
- package/scripts/run-phase8-status-pack.sh +3 -3
- package/scripts/run-phase8-tick.sh +5 -5
- package/skills.lock.json +220 -87
- package/skills.sources.json +14 -0
- package/vendor/skills/MANIFEST.json +64 -0
- package/vendor/skills/android-enterprise-rules/SKILL.md +341 -0
- package/vendor/skills/backend-enterprise-rules/SKILL.md +262 -0
- package/vendor/skills/core-data-expert/SKILL.md +23 -0
- package/vendor/skills/enterprise-operating-system/SKILL.md +223 -0
- package/vendor/skills/frontend-enterprise-rules/SKILL.md +208 -0
- package/vendor/skills/ios-enterprise-rules/SKILL.md +916 -0
- package/vendor/skills/swift-concurrency/SKILL.md +246 -0
- package/vendor/skills/swift-concurrency/references/actors.md +640 -0
- package/vendor/skills/swift-concurrency/references/async-algorithms.md +819 -0
- package/vendor/skills/swift-concurrency/references/async-await-basics.md +249 -0
- package/vendor/skills/swift-concurrency/references/async-sequences.md +670 -0
- package/vendor/skills/swift-concurrency/references/core-data.md +533 -0
- package/vendor/skills/swift-concurrency/references/glossary.md +128 -0
- package/vendor/skills/swift-concurrency/references/linting.md +142 -0
- package/vendor/skills/swift-concurrency/references/memory-management.md +542 -0
- package/vendor/skills/swift-concurrency/references/migration.md +1073 -0
- package/vendor/skills/swift-concurrency/references/performance.md +574 -0
- package/vendor/skills/swift-concurrency/references/sendable.md +578 -0
- package/vendor/skills/swift-concurrency/references/tasks.md +604 -0
- package/vendor/skills/swift-concurrency/references/testing.md +565 -0
- package/vendor/skills/swift-concurrency/references/threading.md +452 -0
- package/vendor/skills/swift-testing-expert/SKILL.md +25 -0
- package/vendor/skills/swiftui-expert-skill/SKILL.md +263 -0
- package/vendor/skills/swiftui-expert-skill/references/image-optimization.md +286 -0
- package/vendor/skills/swiftui-expert-skill/references/layout-best-practices.md +312 -0
- package/vendor/skills/swiftui-expert-skill/references/liquid-glass.md +377 -0
- package/vendor/skills/swiftui-expert-skill/references/list-patterns.md +153 -0
- package/vendor/skills/swiftui-expert-skill/references/modern-apis.md +400 -0
- package/vendor/skills/swiftui-expert-skill/references/performance-patterns.md +377 -0
- package/vendor/skills/swiftui-expert-skill/references/scroll-patterns.md +305 -0
- package/vendor/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +292 -0
- package/vendor/skills/swiftui-expert-skill/references/state-management.md +447 -0
- package/vendor/skills/swiftui-expert-skill/references/text-formatting.md +285 -0
- package/vendor/skills/swiftui-expert-skill/references/view-structure.md +276 -0
- package/scripts/build-phase8-ready-handoff-summary.sh +0 -66
- package/scripts/check-refactor-progress-single-active.sh +0 -25
- package/scripts/close-phase5-escalation-submission.sh +0 -81
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.61
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"snapshotId": "ios-swiftui-modernization-v1",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"generatedAt": "2026-03-31T00:00:00.000Z",
|
|
5
|
+
"sourceSkill": "swiftui-expert-skill",
|
|
6
|
+
"sourceReferences": [
|
|
7
|
+
"vendor/skills/swiftui-expert-skill/references/modern-apis.md",
|
|
8
|
+
"vendor/skills/swiftui-expert-skill/references/text-formatting.md",
|
|
9
|
+
"vendor/skills/swiftui-expert-skill/references/scroll-patterns.md"
|
|
10
|
+
],
|
|
11
|
+
"entries": [
|
|
12
|
+
{
|
|
13
|
+
"id": "foreground-color",
|
|
14
|
+
"ruleId": "skills.ios.no-foreground-color",
|
|
15
|
+
"heuristicRuleId": "heuristics.ios.foreground-color.ast",
|
|
16
|
+
"category": "styling",
|
|
17
|
+
"legacyApi": ".foregroundColor(...)",
|
|
18
|
+
"modernApi": ".foregroundStyle(...)",
|
|
19
|
+
"rationale": "foregroundStyle supports hierarchical styles, gradients and materials.",
|
|
20
|
+
"confidence": "HIGH",
|
|
21
|
+
"minimumStage": "PRE_PUSH",
|
|
22
|
+
"minimumIos": "13.0",
|
|
23
|
+
"match": {
|
|
24
|
+
"kind": "regex",
|
|
25
|
+
"pattern": "\\.\\s*foregroundColor\\s*\\("
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "corner-radius",
|
|
30
|
+
"ruleId": "skills.ios.no-corner-radius",
|
|
31
|
+
"heuristicRuleId": "heuristics.ios.corner-radius.ast",
|
|
32
|
+
"category": "styling",
|
|
33
|
+
"legacyApi": ".cornerRadius(...)",
|
|
34
|
+
"modernApi": ".clipShape(.rect(cornerRadius: ...))",
|
|
35
|
+
"rationale": "cornerRadius is deprecated in modern SwiftUI and clipShape is explicit about the rendered shape.",
|
|
36
|
+
"confidence": "HIGH",
|
|
37
|
+
"minimumStage": "PRE_PUSH",
|
|
38
|
+
"minimumIos": "17.0",
|
|
39
|
+
"match": {
|
|
40
|
+
"kind": "regex",
|
|
41
|
+
"pattern": "\\.\\s*cornerRadius\\s*\\("
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "tab-item",
|
|
46
|
+
"ruleId": "skills.ios.no-tab-item",
|
|
47
|
+
"heuristicRuleId": "heuristics.ios.tab-item.ast",
|
|
48
|
+
"category": "tabs",
|
|
49
|
+
"legacyApi": ".tabItem { ... }",
|
|
50
|
+
"modernApi": "Tab { ... } label: { ... }",
|
|
51
|
+
"rationale": "The Tab API unlocks modern tab roles and avoids mixed syntax issues on iOS 18+.",
|
|
52
|
+
"confidence": "MEDIUM",
|
|
53
|
+
"minimumStage": "PRE_PUSH",
|
|
54
|
+
"minimumIos": "18.0",
|
|
55
|
+
"match": {
|
|
56
|
+
"kind": "regex",
|
|
57
|
+
"pattern": "\\.\\s*tabItem\\s*\\{"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "scrollview-shows-indicators",
|
|
62
|
+
"ruleId": "skills.ios.no-scrollview-shows-indicators",
|
|
63
|
+
"heuristicRuleId": "heuristics.ios.scrollview-shows-indicators.ast",
|
|
64
|
+
"category": "scrolling",
|
|
65
|
+
"legacyApi": "ScrollView(..., showsIndicators: false)",
|
|
66
|
+
"modernApi": ".scrollIndicators(.hidden)",
|
|
67
|
+
"rationale": "The modifier-based API keeps the initializer clean and matches modern SwiftUI scrolling patterns.",
|
|
68
|
+
"confidence": "HIGH",
|
|
69
|
+
"minimumStage": "PRE_PUSH",
|
|
70
|
+
"minimumIos": "16.0",
|
|
71
|
+
"match": {
|
|
72
|
+
"kind": "regex",
|
|
73
|
+
"pattern": "\\bScrollView\\s*\\([\\s\\S]{0,80}showsIndicators\\s*:\\s*false\\b"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
package/bin/_run-ts-entry.js
CHANGED
|
@@ -8,19 +8,28 @@ import {
|
|
|
8
8
|
findSwiftPresentationSrpMatch,
|
|
9
9
|
hasSwiftAnyViewUsage,
|
|
10
10
|
hasSwiftCallbackStyleSignature,
|
|
11
|
+
hasSwiftCornerRadiusUsage,
|
|
11
12
|
hasSwiftDispatchGroupUsage,
|
|
12
13
|
hasSwiftDispatchQueueUsage,
|
|
13
14
|
hasSwiftDispatchSemaphoreUsage,
|
|
14
15
|
hasSwiftForceCastUsage,
|
|
16
|
+
hasSwiftForegroundColorUsage,
|
|
15
17
|
hasSwiftForceTryUsage,
|
|
16
18
|
hasSwiftForceUnwrap,
|
|
19
|
+
hasSwiftLegacyXCTestImportUsage,
|
|
20
|
+
hasSwiftNSManagedObjectAsyncBoundaryUsage,
|
|
21
|
+
hasSwiftNSManagedObjectBoundaryUsage,
|
|
17
22
|
hasSwiftNavigationViewUsage,
|
|
18
23
|
hasSwiftObservableObjectUsage,
|
|
19
24
|
hasSwiftOnTapGestureUsage,
|
|
20
25
|
hasSwiftOperationQueueUsage,
|
|
26
|
+
hasSwiftScrollViewShowsIndicatorsUsage,
|
|
21
27
|
hasSwiftStringFormatUsage,
|
|
28
|
+
hasSwiftTabItemUsage,
|
|
22
29
|
hasSwiftTaskDetachedUsage,
|
|
23
30
|
hasSwiftUIScreenMainBoundsUsage,
|
|
31
|
+
hasSwiftXCTestAssertionUsage,
|
|
32
|
+
hasSwiftXCTUnwrapUsage,
|
|
24
33
|
hasSwiftUncheckedSendableUsage,
|
|
25
34
|
} from './ios';
|
|
26
35
|
|
|
@@ -111,6 +120,16 @@ test('hasSwiftCallbackStyleSignature ignora usos fuera de firmas callback', () =
|
|
|
111
120
|
assert.equal(hasSwiftCallbackStyleSignature(source), false);
|
|
112
121
|
});
|
|
113
122
|
|
|
123
|
+
test('hasSwiftCallbackStyleSignature ignora closures async modernos con @Sendable', () => {
|
|
124
|
+
const source = `
|
|
125
|
+
public init(publish: @escaping @Sendable ([AppRoute]) async -> Void) {
|
|
126
|
+
self.publish = publish
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
assert.equal(hasSwiftCallbackStyleSignature(source), false);
|
|
131
|
+
});
|
|
132
|
+
|
|
114
133
|
test('detecta primitivas GCD y OperationQueue en codigo ejecutable', () => {
|
|
115
134
|
const source = `
|
|
116
135
|
DispatchQueue.main.async { }
|
|
@@ -149,15 +168,29 @@ test('detectores SwiftUI modernos detectan patrones legacy relevantes', () => {
|
|
|
149
168
|
const source = `
|
|
150
169
|
final class LegacyViewModel: ObservableObject {}
|
|
151
170
|
NavigationView { Text("x") }
|
|
171
|
+
Text("Primary").foregroundColor(.blue)
|
|
172
|
+
Image("hero").cornerRadius(12)
|
|
173
|
+
TabView {
|
|
174
|
+
HomeView().tabItem {
|
|
175
|
+
Label("Home", systemImage: "house")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
152
178
|
Text("Tap").onTapGesture { }
|
|
153
179
|
let value = String(format: "%d", 1)
|
|
154
180
|
let width = UIScreen.main.bounds.width
|
|
181
|
+
ScrollView(.horizontal, showsIndicators: false) {
|
|
182
|
+
Text("feed")
|
|
183
|
+
}
|
|
155
184
|
`;
|
|
156
185
|
assert.equal(hasSwiftObservableObjectUsage(source), true);
|
|
157
186
|
assert.equal(hasSwiftNavigationViewUsage(source), true);
|
|
187
|
+
assert.equal(hasSwiftForegroundColorUsage(source), true);
|
|
188
|
+
assert.equal(hasSwiftCornerRadiusUsage(source), true);
|
|
189
|
+
assert.equal(hasSwiftTabItemUsage(source), true);
|
|
158
190
|
assert.equal(hasSwiftOnTapGestureUsage(source), true);
|
|
159
191
|
assert.equal(hasSwiftStringFormatUsage(source), true);
|
|
160
192
|
assert.equal(hasSwiftUIScreenMainBoundsUsage(source), true);
|
|
193
|
+
assert.equal(hasSwiftScrollViewShowsIndicatorsUsage(source), true);
|
|
161
194
|
});
|
|
162
195
|
|
|
163
196
|
test('detectores legacy ignoran strings y comentarios', () => {
|
|
@@ -167,11 +200,129 @@ let a = "Task.detached { }"
|
|
|
167
200
|
let b = "NavigationView { }"
|
|
168
201
|
let c = "String(format: \\\"%d\\\", 1)"
|
|
169
202
|
let d = "UIScreen.main.bounds.width"
|
|
203
|
+
let e = ".foregroundColor(.blue)"
|
|
204
|
+
let f = ".cornerRadius(12)"
|
|
205
|
+
let g = ".tabItem { Label(\\\"Home\\\", systemImage: \\\"house\\\") }"
|
|
206
|
+
let h = "ScrollView(showsIndicators: false) { }"
|
|
170
207
|
`;
|
|
171
208
|
assert.equal(hasSwiftTaskDetachedUsage(source), false);
|
|
172
209
|
assert.equal(hasSwiftNavigationViewUsage(source), false);
|
|
210
|
+
assert.equal(hasSwiftForegroundColorUsage(source), false);
|
|
211
|
+
assert.equal(hasSwiftCornerRadiusUsage(source), false);
|
|
212
|
+
assert.equal(hasSwiftTabItemUsage(source), false);
|
|
173
213
|
assert.equal(hasSwiftStringFormatUsage(source), false);
|
|
174
214
|
assert.equal(hasSwiftUIScreenMainBoundsUsage(source), false);
|
|
215
|
+
assert.equal(hasSwiftScrollViewShowsIndicatorsUsage(source), false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('detectores snapshot SwiftUI ignoran reemplazos modernos', () => {
|
|
219
|
+
const source = `
|
|
220
|
+
Text("Primary").foregroundStyle(.blue)
|
|
221
|
+
Image("hero").clipShape(.rect(cornerRadius: 12))
|
|
222
|
+
TabView {
|
|
223
|
+
Tab("Home", systemImage: "house") {
|
|
224
|
+
HomeView()
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
ScrollView {
|
|
228
|
+
Text("feed")
|
|
229
|
+
}
|
|
230
|
+
.scrollIndicators(.hidden)
|
|
231
|
+
`;
|
|
232
|
+
assert.equal(hasSwiftForegroundColorUsage(source), false);
|
|
233
|
+
assert.equal(hasSwiftCornerRadiusUsage(source), false);
|
|
234
|
+
assert.equal(hasSwiftTabItemUsage(source), false);
|
|
235
|
+
assert.equal(hasSwiftScrollViewShowsIndicatorsUsage(source), false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('hasSwiftLegacyXCTestImportUsage detecta XCTest unitario y excluye UI/performance', () => {
|
|
239
|
+
const unitTest = `
|
|
240
|
+
import XCTest
|
|
241
|
+
|
|
242
|
+
final class LoginTests: XCTestCase {}
|
|
243
|
+
`;
|
|
244
|
+
const uiTest = `
|
|
245
|
+
import XCTest
|
|
246
|
+
|
|
247
|
+
final class LoginUITests: XCTestCase {
|
|
248
|
+
func testLoginFlow() {
|
|
249
|
+
let app = XCUIApplication()
|
|
250
|
+
app.launch()
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
const performanceTest = `
|
|
255
|
+
import XCTest
|
|
256
|
+
|
|
257
|
+
final class SyncTests: XCTestCase {
|
|
258
|
+
func testPerformance() {
|
|
259
|
+
measure {
|
|
260
|
+
runSync()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
assert.equal(hasSwiftLegacyXCTestImportUsage(unitTest), true);
|
|
267
|
+
assert.equal(hasSwiftLegacyXCTestImportUsage(uiTest), false);
|
|
268
|
+
assert.equal(hasSwiftLegacyXCTestImportUsage(performanceTest), false);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('hasSwiftXCTestAssertionUsage detecta XCTAssert y XCTFail reales', () => {
|
|
272
|
+
const source = `
|
|
273
|
+
XCTAssertEqual(value, expected)
|
|
274
|
+
XCTFail("boom")
|
|
275
|
+
`;
|
|
276
|
+
const ignored = `
|
|
277
|
+
// XCTAssertEqual(value, expected)
|
|
278
|
+
let text = "XCTAssertEqual(value, expected)"
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
assert.equal(hasSwiftXCTestAssertionUsage(source), true);
|
|
282
|
+
assert.equal(hasSwiftXCTestAssertionUsage(ignored), false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('hasSwiftXCTUnwrapUsage detecta XCTUnwrap real y evita strings', () => {
|
|
286
|
+
const source = `
|
|
287
|
+
let value = try XCTUnwrap(optionalValue)
|
|
288
|
+
`;
|
|
289
|
+
const ignored = `
|
|
290
|
+
let text = "XCTUnwrap(optionalValue)"
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
assert.equal(hasSwiftXCTUnwrapUsage(source), true);
|
|
294
|
+
assert.equal(hasSwiftXCTUnwrapUsage(ignored), false);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('hasSwiftNSManagedObjectBoundaryUsage detecta boundaries con NSManagedObject y excluye IDs o subclases', () => {
|
|
298
|
+
const source = `
|
|
299
|
+
func persist(_ entity: NSManagedObject) {}
|
|
300
|
+
var selectedEntity: NSManagedObject?
|
|
301
|
+
`;
|
|
302
|
+
const ignored = `
|
|
303
|
+
final class TodoEntity: NSManagedObject {}
|
|
304
|
+
var selectedID: NSManagedObjectID?
|
|
305
|
+
let context: NSManagedObjectContext
|
|
306
|
+
`;
|
|
307
|
+
|
|
308
|
+
assert.equal(hasSwiftNSManagedObjectBoundaryUsage(source), true);
|
|
309
|
+
assert.equal(hasSwiftNSManagedObjectBoundaryUsage(ignored), false);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('hasSwiftNSManagedObjectAsyncBoundaryUsage detecta async APIs con NSManagedObject', () => {
|
|
313
|
+
const source = `
|
|
314
|
+
func fetchEntity() async throws -> NSManagedObject {
|
|
315
|
+
fatalError()
|
|
316
|
+
}
|
|
317
|
+
`;
|
|
318
|
+
const ignored = `
|
|
319
|
+
func fetchEntityID() async throws -> NSManagedObjectID {
|
|
320
|
+
fatalError()
|
|
321
|
+
}
|
|
322
|
+
`;
|
|
323
|
+
|
|
324
|
+
assert.equal(hasSwiftNSManagedObjectAsyncBoundaryUsage(source), true);
|
|
325
|
+
assert.equal(hasSwiftNSManagedObjectAsyncBoundaryUsage(ignored), false);
|
|
175
326
|
});
|
|
176
327
|
|
|
177
328
|
test('findSwiftPresentationSrpMatch devuelve payload semantico para SRP-iOS en presentation', () => {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
readIdentifierBackward,
|
|
7
7
|
scanCodeLikeSource,
|
|
8
8
|
} from './utils';
|
|
9
|
+
import { getIosSwiftUiModernizationEntry } from './iosSwiftUiModernizationSnapshot';
|
|
9
10
|
|
|
10
11
|
export type SwiftSemanticNodeMatch = {
|
|
11
12
|
kind: 'class' | 'property' | 'call' | 'member';
|
|
@@ -85,6 +86,26 @@ const collectSwiftRegexLines = (source: string, regex: RegExp): readonly number[
|
|
|
85
86
|
return matches;
|
|
86
87
|
};
|
|
87
88
|
|
|
89
|
+
const sanitizeSwiftSourceForMultilineRegex = (source: string): string => {
|
|
90
|
+
return source
|
|
91
|
+
.replace(/\/\*[\s\S]*?\*\//g, ' ')
|
|
92
|
+
.replace(/\/\/.*$/gm, '')
|
|
93
|
+
.replace(/"(?:\\.|[^"\\])*"/g, '""');
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const hasSwiftSanitizedRegexMatch = (source: string, regex: RegExp): boolean => {
|
|
97
|
+
regex.lastIndex = 0;
|
|
98
|
+
return regex.test(sanitizeSwiftSourceForMultilineRegex(source));
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const hasSwiftUiModernizationSnapshotMatch = (source: string, entryId: string): boolean => {
|
|
102
|
+
const entry = getIosSwiftUiModernizationEntry(entryId);
|
|
103
|
+
if (!entry) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return hasSwiftSanitizedRegexMatch(source, new RegExp(entry.match.pattern, 'g'));
|
|
107
|
+
};
|
|
108
|
+
|
|
88
109
|
const sortedUniqueLines = (lines: ReadonlyArray<number>): readonly number[] => {
|
|
89
110
|
return Array.from(new Set(lines.filter((line) => Number.isFinite(line)).map((line) => Math.trunc(line))))
|
|
90
111
|
.sort((left, right) => left - right);
|
|
@@ -370,6 +391,18 @@ export const hasSwiftNavigationViewUsage = (source: string): boolean => {
|
|
|
370
391
|
});
|
|
371
392
|
};
|
|
372
393
|
|
|
394
|
+
export const hasSwiftForegroundColorUsage = (source: string): boolean => {
|
|
395
|
+
return hasSwiftUiModernizationSnapshotMatch(source, 'foreground-color');
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
export const hasSwiftCornerRadiusUsage = (source: string): boolean => {
|
|
399
|
+
return hasSwiftUiModernizationSnapshotMatch(source, 'corner-radius');
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
export const hasSwiftTabItemUsage = (source: string): boolean => {
|
|
403
|
+
return hasSwiftUiModernizationSnapshotMatch(source, 'tab-item');
|
|
404
|
+
};
|
|
405
|
+
|
|
373
406
|
export const hasSwiftOnTapGestureUsage = (source: string): boolean => {
|
|
374
407
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
375
408
|
if (current !== 'o') {
|
|
@@ -427,6 +460,48 @@ export const hasSwiftUIScreenMainBoundsUsage = (source: string): boolean => {
|
|
|
427
460
|
});
|
|
428
461
|
};
|
|
429
462
|
|
|
463
|
+
export const hasSwiftScrollViewShowsIndicatorsUsage = (source: string): boolean => {
|
|
464
|
+
return hasSwiftUiModernizationSnapshotMatch(source, 'scrollview-shows-indicators');
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
export const hasSwiftLegacyXCTestImportUsage = (source: string): boolean => {
|
|
468
|
+
const hasXCTestImport = collectSwiftRegexLines(source, /^\s*import\s+XCTest\b/).length > 0;
|
|
469
|
+
if (!hasXCTestImport) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (hasSwiftSanitizedRegexMatch(source, /\bXCUIApplication\b|\bXCTMetric\b|\bmeasure\s*(?:\(|\{)/)) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return true;
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
export const hasSwiftXCTestAssertionUsage = (source: string): boolean => {
|
|
481
|
+
return (
|
|
482
|
+
collectSwiftRegexLines(source, /\bXCTAssert[A-Za-z0-9_]*\s*\(/).length > 0 ||
|
|
483
|
+
collectSwiftRegexLines(source, /\bXCTFail\s*\(/).length > 0
|
|
484
|
+
);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
export const hasSwiftXCTUnwrapUsage = (source: string): boolean => {
|
|
488
|
+
return collectSwiftRegexLines(source, /\bXCTUnwrap\s*\(/).length > 0;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
export const hasSwiftNSManagedObjectBoundaryUsage = (source: string): boolean => {
|
|
492
|
+
return hasSwiftSanitizedRegexMatch(
|
|
493
|
+
source,
|
|
494
|
+
/\bfunc\b[\s\S]{0,240}\([^)]*\bNSManagedObject\b(?!ID\b|Context\b)[^)]*\)|\b(?:var|let)\s+[A-Za-z_][A-Za-z0-9_]*\s*:\s*(?:\[[^\]]*NSManagedObject\b(?!ID\b|Context\b)[^\]]*\]|NSManagedObject\b(?!ID\b|Context\b))/g
|
|
495
|
+
);
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
export const hasSwiftNSManagedObjectAsyncBoundaryUsage = (source: string): boolean => {
|
|
499
|
+
return hasSwiftSanitizedRegexMatch(
|
|
500
|
+
source,
|
|
501
|
+
/\bfunc\b[\s\S]{0,240}\basync\b[\s\S]{0,200}(?:\([^)]*\bNSManagedObject\b(?!ID\b|Context\b)[^)]*\)|->\s*(?:\[[^\]]*NSManagedObject\b(?!ID\b|Context\b)[^\]]*\]|NSManagedObject\b(?!ID\b|Context\b)))/g
|
|
502
|
+
);
|
|
503
|
+
};
|
|
504
|
+
|
|
430
505
|
export const hasSwiftForceTryUsage = (source: string): boolean => {
|
|
431
506
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
432
507
|
if (current !== 't' || !hasIdentifierAt(swiftSource, index, 'try')) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
getIosSwiftUiModernizationEntry,
|
|
5
|
+
IOS_SWIFTUI_MODERNIZATION_SNAPSHOT,
|
|
6
|
+
listIosSwiftUiModernizationEntries,
|
|
7
|
+
} from './iosSwiftUiModernizationSnapshot';
|
|
8
|
+
|
|
9
|
+
test('iosSwiftUiModernizationSnapshot expone snapshot versionado y determinista', () => {
|
|
10
|
+
assert.equal(IOS_SWIFTUI_MODERNIZATION_SNAPSHOT.snapshotId, 'ios-swiftui-modernization-v1');
|
|
11
|
+
assert.equal(IOS_SWIFTUI_MODERNIZATION_SNAPSHOT.version, '1.0.0');
|
|
12
|
+
assert.equal(IOS_SWIFTUI_MODERNIZATION_SNAPSHOT.sourceSkill, 'swiftui-expert-skill');
|
|
13
|
+
assert.deepEqual(
|
|
14
|
+
listIosSwiftUiModernizationEntries().map((entry) => entry.id),
|
|
15
|
+
['foreground-color', 'corner-radius', 'tab-item', 'scrollview-shows-indicators']
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('iosSwiftUiModernizationSnapshot resuelve entradas canonicas con ruleIds y heuristics alineados', () => {
|
|
20
|
+
const foregroundColor = getIosSwiftUiModernizationEntry('foreground-color');
|
|
21
|
+
const tabItem = getIosSwiftUiModernizationEntry('tab-item');
|
|
22
|
+
|
|
23
|
+
assert.ok(foregroundColor);
|
|
24
|
+
assert.equal(foregroundColor.ruleId, 'skills.ios.no-foreground-color');
|
|
25
|
+
assert.equal(foregroundColor.heuristicRuleId, 'heuristics.ios.foreground-color.ast');
|
|
26
|
+
|
|
27
|
+
assert.ok(tabItem);
|
|
28
|
+
assert.equal(tabItem.ruleId, 'skills.ios.no-tab-item');
|
|
29
|
+
assert.equal(tabItem.heuristicRuleId, 'heuristics.ios.tab-item.ast');
|
|
30
|
+
assert.equal(tabItem.minimumIos, '18.0');
|
|
31
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import rawSnapshot from '../../../../assets/rule-packs/ios-swiftui-modernization-v1.json';
|
|
2
|
+
|
|
3
|
+
export type IosSwiftUiModernizationConfidence = 'HIGH' | 'MEDIUM';
|
|
4
|
+
export type IosSwiftUiModernizationStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
5
|
+
export type IosSwiftUiModernizationCategory = 'styling' | 'tabs' | 'scrolling';
|
|
6
|
+
|
|
7
|
+
export type IosSwiftUiModernizationMatch = {
|
|
8
|
+
kind: 'regex';
|
|
9
|
+
pattern: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type IosSwiftUiModernizationEntry = {
|
|
13
|
+
id: string;
|
|
14
|
+
ruleId: string;
|
|
15
|
+
heuristicRuleId: string;
|
|
16
|
+
category: IosSwiftUiModernizationCategory;
|
|
17
|
+
legacyApi: string;
|
|
18
|
+
modernApi: string;
|
|
19
|
+
rationale: string;
|
|
20
|
+
confidence: IosSwiftUiModernizationConfidence;
|
|
21
|
+
minimumStage: IosSwiftUiModernizationStage;
|
|
22
|
+
minimumIos: string;
|
|
23
|
+
match: IosSwiftUiModernizationMatch;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type IosSwiftUiModernizationSnapshot = {
|
|
27
|
+
snapshotId: string;
|
|
28
|
+
version: string;
|
|
29
|
+
generatedAt: string;
|
|
30
|
+
sourceSkill: string;
|
|
31
|
+
sourceReferences: readonly string[];
|
|
32
|
+
entries: readonly IosSwiftUiModernizationEntry[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
36
|
+
typeof value === 'object' && value !== null;
|
|
37
|
+
|
|
38
|
+
const isNonEmptyString = (value: unknown): value is string =>
|
|
39
|
+
typeof value === 'string' && value.trim().length > 0;
|
|
40
|
+
|
|
41
|
+
const isStage = (value: unknown): value is IosSwiftUiModernizationStage =>
|
|
42
|
+
value === 'PRE_COMMIT' || value === 'PRE_PUSH' || value === 'CI';
|
|
43
|
+
|
|
44
|
+
const isConfidence = (value: unknown): value is IosSwiftUiModernizationConfidence =>
|
|
45
|
+
value === 'HIGH' || value === 'MEDIUM';
|
|
46
|
+
|
|
47
|
+
const isCategory = (value: unknown): value is IosSwiftUiModernizationCategory =>
|
|
48
|
+
value === 'styling' || value === 'tabs' || value === 'scrolling';
|
|
49
|
+
|
|
50
|
+
const isMatch = (value: unknown): value is IosSwiftUiModernizationMatch => {
|
|
51
|
+
return (
|
|
52
|
+
isObject(value) &&
|
|
53
|
+
value.kind === 'regex' &&
|
|
54
|
+
isNonEmptyString(value.pattern)
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const isEntry = (value: unknown): value is IosSwiftUiModernizationEntry => {
|
|
59
|
+
return (
|
|
60
|
+
isObject(value) &&
|
|
61
|
+
isNonEmptyString(value.id) &&
|
|
62
|
+
isNonEmptyString(value.ruleId) &&
|
|
63
|
+
isNonEmptyString(value.heuristicRuleId) &&
|
|
64
|
+
isCategory(value.category) &&
|
|
65
|
+
isNonEmptyString(value.legacyApi) &&
|
|
66
|
+
isNonEmptyString(value.modernApi) &&
|
|
67
|
+
isNonEmptyString(value.rationale) &&
|
|
68
|
+
isConfidence(value.confidence) &&
|
|
69
|
+
isStage(value.minimumStage) &&
|
|
70
|
+
isNonEmptyString(value.minimumIos) &&
|
|
71
|
+
isMatch(value.match)
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isSnapshot = (value: unknown): value is IosSwiftUiModernizationSnapshot => {
|
|
76
|
+
return (
|
|
77
|
+
isObject(value) &&
|
|
78
|
+
isNonEmptyString(value.snapshotId) &&
|
|
79
|
+
isNonEmptyString(value.version) &&
|
|
80
|
+
isNonEmptyString(value.generatedAt) &&
|
|
81
|
+
isNonEmptyString(value.sourceSkill) &&
|
|
82
|
+
Array.isArray(value.sourceReferences) &&
|
|
83
|
+
value.sourceReferences.every((item) => isNonEmptyString(item)) &&
|
|
84
|
+
Array.isArray(value.entries) &&
|
|
85
|
+
value.entries.every((entry) => isEntry(entry))
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const parseSnapshot = (value: unknown): IosSwiftUiModernizationSnapshot => {
|
|
90
|
+
if (!isSnapshot(value)) {
|
|
91
|
+
throw new Error('Invalid iOS SwiftUI modernization snapshot.');
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const IOS_SWIFTUI_MODERNIZATION_SNAPSHOT = parseSnapshot(rawSnapshot);
|
|
97
|
+
|
|
98
|
+
const entryById = new Map(
|
|
99
|
+
IOS_SWIFTUI_MODERNIZATION_SNAPSHOT.entries.map((entry) => [entry.id, entry] as const)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
export const listIosSwiftUiModernizationEntries = (): readonly IosSwiftUiModernizationEntry[] => {
|
|
103
|
+
return IOS_SWIFTUI_MODERNIZATION_SNAPSHOT.entries;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const getIosSwiftUiModernizationEntry = (
|
|
107
|
+
entryId: string
|
|
108
|
+
): IosSwiftUiModernizationEntry | undefined => {
|
|
109
|
+
return entryById.get(entryId);
|
|
110
|
+
};
|
|
@@ -128,6 +128,10 @@ const isSwiftTestPath = (path: string): boolean => {
|
|
|
128
128
|
);
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
+
const isIOSSwiftTestPath = (path: string): boolean => {
|
|
132
|
+
return isIOSSwiftPath(path) && isSwiftTestPath(path);
|
|
133
|
+
};
|
|
134
|
+
|
|
131
135
|
const isKotlinTestPath = (path: string): boolean => {
|
|
132
136
|
const normalized = path.toLowerCase();
|
|
133
137
|
return (
|
|
@@ -594,9 +598,18 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
594
598
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUncheckedSendableUsage, ruleId: 'heuristics.ios.unchecked-sendable.ast', code: 'HEURISTICS_IOS_UNCHECKED_SENDABLE_AST', message: 'AST heuristic detected @unchecked Sendable usage.' },
|
|
595
599
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftObservableObjectUsage, ruleId: 'heuristics.ios.observable-object.ast', code: 'HEURISTICS_IOS_OBSERVABLE_OBJECT_AST', message: 'AST heuristic detected ObservableObject usage.' },
|
|
596
600
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNavigationViewUsage, ruleId: 'heuristics.ios.navigation-view.ast', code: 'HEURISTICS_IOS_NAVIGATION_VIEW_AST', message: 'AST heuristic detected NavigationView usage.' },
|
|
601
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForegroundColorUsage, ruleId: 'heuristics.ios.foreground-color.ast', code: 'HEURISTICS_IOS_FOREGROUND_COLOR_AST', message: 'AST heuristic detected foregroundColor usage.' },
|
|
602
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCornerRadiusUsage, ruleId: 'heuristics.ios.corner-radius.ast', code: 'HEURISTICS_IOS_CORNER_RADIUS_AST', message: 'AST heuristic detected cornerRadius usage.' },
|
|
603
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftTabItemUsage, ruleId: 'heuristics.ios.tab-item.ast', code: 'HEURISTICS_IOS_TAB_ITEM_AST', message: 'AST heuristic detected tabItem usage.' },
|
|
597
604
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnTapGestureUsage, ruleId: 'heuristics.ios.on-tap-gesture.ast', code: 'HEURISTICS_IOS_ON_TAP_GESTURE_AST', message: 'AST heuristic detected onTapGesture usage where Button may be preferred.' },
|
|
598
605
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStringFormatUsage, ruleId: 'heuristics.ios.string-format.ast', code: 'HEURISTICS_IOS_STRING_FORMAT_AST', message: 'AST heuristic detected String(format:) usage.' },
|
|
606
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftScrollViewShowsIndicatorsUsage, ruleId: 'heuristics.ios.scrollview-shows-indicators.ast', code: 'HEURISTICS_IOS_SCROLLVIEW_SHOWS_INDICATORS_AST', message: 'AST heuristic detected ScrollView(showsIndicators: false) usage.' },
|
|
599
607
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUIScreenMainBoundsUsage, ruleId: 'heuristics.ios.uiscreen-main-bounds.ast', code: 'HEURISTICS_IOS_UISCREEN_MAIN_BOUNDS_AST', message: 'AST heuristic detected UIScreen.main.bounds usage.' },
|
|
608
|
+
{ platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftLegacyXCTestImportUsage, ruleId: 'heuristics.ios.testing.xctest-import.ast', code: 'HEURISTICS_IOS_TESTING_XCTEST_IMPORT_AST', message: 'AST heuristic detected XCTest-only test usage where Swift Testing may be preferred.' },
|
|
609
|
+
{ platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftXCTestAssertionUsage, ruleId: 'heuristics.ios.testing.xctassert.ast', code: 'HEURISTICS_IOS_TESTING_XCTASSERT_AST', message: 'AST heuristic detected XCTest assertion usage where #expect may be preferred.' },
|
|
610
|
+
{ platform: 'ios', pathCheck: isIOSSwiftTestPath, excludePaths: [], detect: TextIOS.hasSwiftXCTUnwrapUsage, ruleId: 'heuristics.ios.testing.xctunwrap.ast', code: 'HEURISTICS_IOS_TESTING_XCTUNWRAP_AST', message: 'AST heuristic detected XCTUnwrap usage where #require may be preferred.' },
|
|
611
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNSManagedObjectBoundaryUsage, ruleId: 'heuristics.ios.core-data.nsmanagedobject-boundary.ast', code: 'HEURISTICS_IOS_CORE_DATA_NSMANAGEDOBJECT_BOUNDARY_AST', message: 'AST heuristic detected NSManagedObject in a shared boundary.' },
|
|
612
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNSManagedObjectAsyncBoundaryUsage, ruleId: 'heuristics.ios.core-data.nsmanagedobject-async-boundary.ast', code: 'HEURISTICS_IOS_CORE_DATA_NSMANAGEDOBJECT_ASYNC_BOUNDARY_AST', message: 'AST heuristic detected NSManagedObject in an async boundary.' },
|
|
600
613
|
|
|
601
614
|
// Android
|
|
602
615
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinThreadSleepCall, ruleId: 'heuristics.android.thread-sleep.ast', code: 'HEURISTICS_ANDROID_THREAD_SLEEP_AST', message: 'AST heuristic detected Thread.sleep usage in production Kotlin code.' },
|
|
@@ -671,7 +684,7 @@ export const extractHeuristicFacts = (
|
|
|
671
684
|
if (
|
|
672
685
|
platformDetected &&
|
|
673
686
|
entry.pathCheck(fileFact.path) &&
|
|
674
|
-
entry.excludePaths.every((exclude) => !exclude(fileFact.path)) &&
|
|
687
|
+
(entry.excludePaths ?? []).every((exclude) => !exclude(fileFact.path)) &&
|
|
675
688
|
entry.detect(fileFact.content)
|
|
676
689
|
) {
|
|
677
690
|
heuristicFacts.push(
|
|
@@ -2,21 +2,23 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import type { GateStage } from './GateStage';
|
|
4
4
|
|
|
5
|
-
test('GateStage soporta STAGED, PRE_COMMIT, PRE_PUSH y CI', () => {
|
|
5
|
+
test('GateStage soporta STAGED, PRE_WRITE, PRE_COMMIT, PRE_PUSH y CI', () => {
|
|
6
6
|
const staged: GateStage = 'STAGED';
|
|
7
|
+
const preWrite: GateStage = 'PRE_WRITE';
|
|
7
8
|
const preCommit: GateStage = 'PRE_COMMIT';
|
|
8
9
|
const prePush: GateStage = 'PRE_PUSH';
|
|
9
10
|
const ci: GateStage = 'CI';
|
|
10
11
|
|
|
11
12
|
assert.equal(staged, 'STAGED');
|
|
13
|
+
assert.equal(preWrite, 'PRE_WRITE');
|
|
12
14
|
assert.equal(preCommit, 'PRE_COMMIT');
|
|
13
15
|
assert.equal(prePush, 'PRE_PUSH');
|
|
14
16
|
assert.equal(ci, 'CI');
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
test('GateStage permite colecciones tipadas de stages', () => {
|
|
18
|
-
const stages: GateStage[] = ['STAGED', 'PRE_COMMIT', 'PRE_PUSH', 'CI'];
|
|
20
|
+
const stages: GateStage[] = ['STAGED', 'PRE_WRITE', 'PRE_COMMIT', 'PRE_PUSH', 'CI'];
|
|
19
21
|
|
|
20
|
-
assert.equal(stages.length,
|
|
21
|
-
assert.deepEqual(stages, ['STAGED', 'PRE_COMMIT', 'PRE_PUSH', 'CI']);
|
|
22
|
+
assert.equal(stages.length, 5);
|
|
23
|
+
assert.deepEqual(stages, ['STAGED', 'PRE_WRITE', 'PRE_COMMIT', 'PRE_PUSH', 'CI']);
|
|
22
24
|
});
|
package/core/gate/GateStage.ts
CHANGED
|
@@ -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, 25);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -20,9 +20,18 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
20
20
|
'heuristics.ios.unchecked-sendable.ast',
|
|
21
21
|
'heuristics.ios.observable-object.ast',
|
|
22
22
|
'heuristics.ios.navigation-view.ast',
|
|
23
|
+
'heuristics.ios.foreground-color.ast',
|
|
24
|
+
'heuristics.ios.corner-radius.ast',
|
|
25
|
+
'heuristics.ios.tab-item.ast',
|
|
23
26
|
'heuristics.ios.on-tap-gesture.ast',
|
|
24
27
|
'heuristics.ios.string-format.ast',
|
|
28
|
+
'heuristics.ios.scrollview-shows-indicators.ast',
|
|
25
29
|
'heuristics.ios.uiscreen-main-bounds.ast',
|
|
30
|
+
'heuristics.ios.testing.xctest-import.ast',
|
|
31
|
+
'heuristics.ios.testing.xctassert.ast',
|
|
32
|
+
'heuristics.ios.testing.xctunwrap.ast',
|
|
33
|
+
'heuristics.ios.core-data.nsmanagedobject-boundary.ast',
|
|
34
|
+
'heuristics.ios.core-data.nsmanagedobject-async-boundary.ast',
|
|
26
35
|
]);
|
|
27
36
|
|
|
28
37
|
const byId = new Map(iosRules.map((rule) => [rule.id, rule]));
|
|
@@ -38,6 +47,18 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
38
47
|
byId.get('heuristics.ios.uiscreen-main-bounds.ast')?.then.code,
|
|
39
48
|
'HEURISTICS_IOS_UISCREEN_MAIN_BOUNDS_AST'
|
|
40
49
|
);
|
|
50
|
+
assert.equal(
|
|
51
|
+
byId.get('heuristics.ios.foreground-color.ast')?.then.code,
|
|
52
|
+
'HEURISTICS_IOS_FOREGROUND_COLOR_AST'
|
|
53
|
+
);
|
|
54
|
+
assert.equal(
|
|
55
|
+
byId.get('heuristics.ios.testing.xctassert.ast')?.then.code,
|
|
56
|
+
'HEURISTICS_IOS_TESTING_XCTASSERT_AST'
|
|
57
|
+
);
|
|
58
|
+
assert.equal(
|
|
59
|
+
byId.get('heuristics.ios.core-data.nsmanagedobject-async-boundary.ast')?.then.code,
|
|
60
|
+
'HEURISTICS_IOS_CORE_DATA_NSMANAGEDOBJECT_ASYNC_BOUNDARY_AST'
|
|
61
|
+
);
|
|
41
62
|
|
|
42
63
|
for (const rule of iosRules) {
|
|
43
64
|
assert.equal(rule.platform, 'ios');
|