pumuki 6.3.240 → 6.3.242
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 +12 -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 +6 -0
- package/integrations/config/skillsMarkdownRules.ts +7 -0
- package/package.json +1 -1
- package/skills.lock.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [6.3.242] - 2026-05-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **SwiftUI Observable shared-state parity:** `skills.ios.guideline.ios-swiftui-expert.use-observable-for-shared-state-with-mainactor-if-not-using-default-ac` now maps to the existing AUTO `ObservableObject` heuristic, converting the shared-state migration guideline into runtime evidence without introducing actor-isolation false positives.
|
|
14
|
+
|
|
15
|
+
## [6.3.241] - 2026-05-14
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **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.
|
|
20
|
+
|
|
9
21
|
## [6.3.127] - 2026-04-28
|
|
10
22
|
|
|
11
23
|
### 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.',
|
|
@@ -201,6 +201,8 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
201
201
|
'skills.ios.no-observable-object': heuristicDetector('ios.observable-object', [
|
|
202
202
|
'heuristics.ios.observable-object.ast',
|
|
203
203
|
]),
|
|
204
|
+
'skills.ios.guideline.ios-swiftui-expert.use-observable-for-shared-state-with-mainactor-if-not-using-default-ac':
|
|
205
|
+
heuristicDetector('ios.observable-object', ['heuristics.ios.observable-object.ast']),
|
|
204
206
|
'skills.ios.no-legacy-swiftui-observable-wrapper': heuristicDetector(
|
|
205
207
|
'ios.legacy-swiftui-observable-wrapper',
|
|
206
208
|
['heuristics.ios.legacy-swiftui-observable-wrapper.ast']
|
|
@@ -312,6 +314,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
312
314
|
'ios.swiftui.onappear-task',
|
|
313
315
|
['heuristics.ios.swiftui.onappear-task.ast']
|
|
314
316
|
),
|
|
317
|
+
'skills.ios.guideline.ios-swiftui-expert.use-task-id-for-value-dependent-tasks': heuristicDetector(
|
|
318
|
+
'ios.swiftui.onchange-task',
|
|
319
|
+
['heuristics.ios.swiftui.onchange-task.ast']
|
|
320
|
+
),
|
|
315
321
|
'skills.ios.no-uiscreen-main-bounds': heuristicDetector('ios.uiscreen-main-bounds', [
|
|
316
322
|
'heuristics.ios.uiscreen-main-bounds.ast',
|
|
317
323
|
]),
|
|
@@ -253,6 +253,13 @@ const normalizeKnownRuleTarget = (
|
|
|
253
253
|
if (includes('observableobject') || includes('observable object')) {
|
|
254
254
|
return 'skills.ios.no-observable-object';
|
|
255
255
|
}
|
|
256
|
+
if (
|
|
257
|
+
(includes('@observable') || includes('observable')) &&
|
|
258
|
+
includes('shared state') &&
|
|
259
|
+
includes('mainactor')
|
|
260
|
+
) {
|
|
261
|
+
return 'skills.ios.guideline.ios-swiftui-expert.use-observable-for-shared-state-with-mainactor-if-not-using-default-ac';
|
|
262
|
+
}
|
|
256
263
|
if (
|
|
257
264
|
includes('observedobject') ||
|
|
258
265
|
(includes('legacy') && includes('stateobject')) ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.242",
|
|
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-
|
|
4
|
+
"generatedAt": "2026-05-14T07:00:57.248Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -8620,7 +8620,7 @@
|
|
|
8620
8620
|
"name": "ios-swiftui-expert-guidelines",
|
|
8621
8621
|
"version": "1.0.0",
|
|
8622
8622
|
"source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8623
|
-
"hash": "
|
|
8623
|
+
"hash": "eec9633ddf388ee680cf4bd74c4e52f9112d1925bea59f50d482c47d1aba59f4",
|
|
8624
8624
|
"rules": [
|
|
8625
8625
|
{
|
|
8626
8626
|
"id": "skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic",
|
|
@@ -8895,7 +8895,7 @@
|
|
|
8895
8895
|
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8896
8896
|
"confidence": "MEDIUM",
|
|
8897
8897
|
"locked": true,
|
|
8898
|
-
"evaluationMode": "
|
|
8898
|
+
"evaluationMode": "AUTO",
|
|
8899
8899
|
"origin": "core"
|
|
8900
8900
|
},
|
|
8901
8901
|
{
|