pumuki 6.3.239 → 6.3.241
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 +6 -0
- package/core/facts/detectors/text/ios.test.ts +38 -0
- package/core/facts/detectors/text/ios.ts +7 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +20 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/integrations/gate/evaluateAiGate.ts +1 -1
- package/package.json +1 -1
- package/skills.lock.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [6.3.241] - 2026-05-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **SwiftUI `.task(id:)` parity:** `skills.ios.guideline.ios-swiftui-expert.use-task-id-for-value-dependent-tasks` now maps to an AUTO heuristic that detects `Task` launched from SwiftUI `onChange`, while preserving `.task(id:)` as the clean lifecycle-aware pattern.
|
|
14
|
+
|
|
9
15
|
## [6.3.127] - 2026-04-28
|
|
10
16
|
|
|
11
17
|
### Fixed
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
hasSwiftNonPrivateStateOwnershipUsage,
|
|
55
55
|
hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage,
|
|
56
56
|
hasSwiftObservableObjectUsage,
|
|
57
|
+
hasSwiftOnChangeTaskUsage,
|
|
57
58
|
hasSwiftOnAppearTaskUsage,
|
|
58
59
|
hasSwiftOnTapGestureUsage,
|
|
59
60
|
hasSwiftOperationQueueUsage,
|
|
@@ -507,6 +508,43 @@ struct FeedView: View {
|
|
|
507
508
|
assert.equal(hasSwiftOnAppearTaskUsage(safe), false);
|
|
508
509
|
});
|
|
509
510
|
|
|
511
|
+
test('hasSwiftOnChangeTaskUsage detecta Task dentro de onChange y preserva task id', () => {
|
|
512
|
+
const source = `
|
|
513
|
+
struct SearchView: View {
|
|
514
|
+
@State private var query = ""
|
|
515
|
+
|
|
516
|
+
var body: some View {
|
|
517
|
+
TextField("Search", text: $query)
|
|
518
|
+
.onChange(of: query) { _, newValue in
|
|
519
|
+
Task {
|
|
520
|
+
await viewModel.search(newValue)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
`;
|
|
526
|
+
const safe = `
|
|
527
|
+
struct SearchView: View {
|
|
528
|
+
@State private var query = ""
|
|
529
|
+
|
|
530
|
+
var body: some View {
|
|
531
|
+
TextField("Search", text: $query)
|
|
532
|
+
.task(id: query) {
|
|
533
|
+
await viewModel.search(query)
|
|
534
|
+
}
|
|
535
|
+
.onChange(of: query) { _, newValue in
|
|
536
|
+
analytics.trackSearch(newValue)
|
|
537
|
+
}
|
|
538
|
+
let text = ".onChange(of: query) { Task { await search() } }"
|
|
539
|
+
// .onChange(of: query) { Task { await search() } }
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
`;
|
|
543
|
+
|
|
544
|
+
assert.equal(hasSwiftOnChangeTaskUsage(source), true);
|
|
545
|
+
assert.equal(hasSwiftOnChangeTaskUsage(safe), false);
|
|
546
|
+
});
|
|
547
|
+
|
|
510
548
|
test('hasSwiftStrongDelegateReferenceUsage detecta delegates fuertes y preserva weak delegates', () => {
|
|
511
549
|
const positive = `
|
|
512
550
|
final class CheckoutCoordinator {
|
|
@@ -481,6 +481,13 @@ export const hasSwiftOnAppearTaskUsage = (source: string): boolean => {
|
|
|
481
481
|
return /\.onAppear\s*\{[\s\S]{0,500}?\bTask\s*(?:\([^)]*\))?\s*\{/.test(sanitized);
|
|
482
482
|
};
|
|
483
483
|
|
|
484
|
+
export const hasSwiftOnChangeTaskUsage = (source: string): boolean => {
|
|
485
|
+
const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
|
|
486
|
+
return /\.onChange\s*\([^)]*\)\s*\{[\s\S]{0,500}?\bTask\s*(?:\([^)]*\))?\s*\{/.test(
|
|
487
|
+
sanitized
|
|
488
|
+
);
|
|
489
|
+
};
|
|
490
|
+
|
|
484
491
|
export const hasSwiftStrongDelegateReferenceUsage = (source: string): boolean => {
|
|
485
492
|
const delegatePropertyPattern =
|
|
486
493
|
/\b(?:var|let)\s+(?:[A-Za-z_][A-Za-z0-9_]*(?:Delegate|DataSource)|delegate|dataSource)\s*:\s*(?:any\s+)?[A-Za-z_][A-Za-z0-9_]*(?:Delegate|DataSource)\b/;
|
|
@@ -647,6 +647,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
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
649
|
{ 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
|
+
{ 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.' },
|
|
650
651
|
{ 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.' },
|
|
651
652
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongSelfEscapingClosureUsage, ruleId: 'heuristics.ios.memory.strong-self-escaping-closure.ast', code: 'HEURISTICS_IOS_MEMORY_STRONG_SELF_ESCAPING_CLOSURE_AST', message: 'AST heuristic detected strong self capture in an escaping iOS closure; weak or unowned captures remain the preferred baseline when ownership is not explicit.' },
|
|
652
653
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCustomSingletonUsage, ruleId: 'heuristics.ios.architecture.custom-singleton.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_CUSTOM_SINGLETON_AST', message: 'AST heuristic detected a custom static shared singleton in iOS production code; dependency injection remains the preferred baseline for app-owned services.' },
|
|
@@ -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, 83);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -18,6 +18,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
18
18
|
'heuristics.ios.operation-queue.ast',
|
|
19
19
|
'heuristics.ios.task-detached.ast',
|
|
20
20
|
'heuristics.ios.swiftui.onappear-task.ast',
|
|
21
|
+
'heuristics.ios.swiftui.onchange-task.ast',
|
|
21
22
|
'heuristics.ios.memory.strong-delegate.ast',
|
|
22
23
|
'heuristics.ios.memory.strong-self-escaping-closure.ast',
|
|
23
24
|
'heuristics.ios.architecture.custom-singleton.ast',
|
|
@@ -100,6 +101,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
100
101
|
byId.get('heuristics.ios.task-detached.ast')?.then.code,
|
|
101
102
|
'HEURISTICS_IOS_TASK_DETACHED_AST'
|
|
102
103
|
);
|
|
104
|
+
assert.equal(
|
|
105
|
+
byId.get('heuristics.ios.swiftui.onchange-task.ast')?.then.code,
|
|
106
|
+
'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST'
|
|
107
|
+
);
|
|
103
108
|
assert.equal(
|
|
104
109
|
byId.get('heuristics.ios.logging.adhoc-print.ast')?.then.code,
|
|
105
110
|
'HEURISTICS_IOS_LOGGING_ADHOC_PRINT_AST'
|
|
@@ -200,6 +200,26 @@ export const iosRules: RuleSet = [
|
|
|
200
200
|
code: 'HEURISTICS_IOS_SWIFTUI_ONAPPEAR_TASK_AST',
|
|
201
201
|
},
|
|
202
202
|
},
|
|
203
|
+
{
|
|
204
|
+
id: 'heuristics.ios.swiftui.onchange-task.ast',
|
|
205
|
+
description:
|
|
206
|
+
'Detects Task launches from SwiftUI onChange where .task(id:) should own value-dependent cancellation.',
|
|
207
|
+
severity: 'WARN',
|
|
208
|
+
platform: 'ios',
|
|
209
|
+
locked: true,
|
|
210
|
+
when: {
|
|
211
|
+
kind: 'Heuristic',
|
|
212
|
+
where: {
|
|
213
|
+
ruleId: 'heuristics.ios.swiftui.onchange-task.ast',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
then: {
|
|
217
|
+
kind: 'Finding',
|
|
218
|
+
message:
|
|
219
|
+
'AST heuristic detected Task launched from SwiftUI onChange; .task(id:) should own value-dependent cancellation.',
|
|
220
|
+
code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
203
223
|
{
|
|
204
224
|
id: 'heuristics.ios.memory.strong-delegate.ast',
|
|
205
225
|
description: 'Detects strong delegate/dataSource references in iOS production code.',
|
|
@@ -312,6 +312,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
312
312
|
'ios.swiftui.onappear-task',
|
|
313
313
|
['heuristics.ios.swiftui.onappear-task.ast']
|
|
314
314
|
),
|
|
315
|
+
'skills.ios.guideline.ios-swiftui-expert.use-task-id-for-value-dependent-tasks': heuristicDetector(
|
|
316
|
+
'ios.swiftui.onchange-task',
|
|
317
|
+
['heuristics.ios.swiftui.onchange-task.ast']
|
|
318
|
+
),
|
|
315
319
|
'skills.ios.no-uiscreen-main-bounds': heuristicDetector('ios.uiscreen-main-bounds', [
|
|
316
320
|
'heuristics.ios.uiscreen-main-bounds.ast',
|
|
317
321
|
]),
|
|
@@ -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: [],
|
|
155
155
|
android: [],
|
|
156
156
|
backend: [],
|
|
157
157
|
frontend: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.241",
|
|
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": {
|