pumuki 6.3.192 → 6.3.194
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/detectors/text/ios.test.ts +32 -0
- package/core/facts/detectors/text/ios.ts +11 -0
- package/core/facts/extractHeuristicFacts.ts +20 -0
- package/core/rules/presets/heuristics/ios.test.ts +21 -1
- package/core/rules/presets/heuristics/ios.ts +72 -0
- package/docs/codex-skills/ios-enterprise-rules.md +15 -2
- package/integrations/config/skillsDetectorRegistry.ts +20 -0
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
hasSwiftDispatchQueueUsage,
|
|
14
14
|
hasSwiftDispatchSemaphoreUsage,
|
|
15
15
|
hasSwiftAdHocLoggingUsage,
|
|
16
|
+
hasSwiftAlamofireUsage,
|
|
16
17
|
hasSwiftForEachIndicesUsage,
|
|
17
18
|
hasSwiftForceCastUsage,
|
|
18
19
|
hasSwiftFontWeightBoldUsage,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
hasSwiftSheetIsPresentedUsage,
|
|
44
45
|
hasSwiftScrollViewShowsIndicatorsUsage,
|
|
45
46
|
hasSwiftSensitiveLoggingUsage,
|
|
47
|
+
hasSwiftJSONSerializationUsage,
|
|
46
48
|
hasSwiftStringFormatUsage,
|
|
47
49
|
hasSwiftTabItemUsage,
|
|
48
50
|
hasSwiftTaskDetachedUsage,
|
|
@@ -191,6 +193,36 @@ logger.error("Refresh failed \\(refreshToken)")
|
|
|
191
193
|
assert.equal(hasSwiftSensitiveLoggingUsage(structuredSafe), false);
|
|
192
194
|
});
|
|
193
195
|
|
|
196
|
+
test('detectores iOS de networking y JSON detectan Alamofire y JSONSerialization sin leer comentarios ni strings', () => {
|
|
197
|
+
const source = `
|
|
198
|
+
import Alamofire
|
|
199
|
+
|
|
200
|
+
final class APIClient {
|
|
201
|
+
func load() {
|
|
202
|
+
AF.request("https://example.com")
|
|
203
|
+
let object = try? JSONSerialization.jsonObject(with: Data())
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
`;
|
|
207
|
+
const ignored = `
|
|
208
|
+
import Foundation
|
|
209
|
+
|
|
210
|
+
final class APIClient {
|
|
211
|
+
func load() async throws {
|
|
212
|
+
let url = URL(string: "https://example.com")
|
|
213
|
+
let dto = try JSONDecoder().decode(UserDTO.self, from: Data())
|
|
214
|
+
let text = "JSONSerialization.jsonObject"
|
|
215
|
+
// AF.request("debug")
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
assert.equal(hasSwiftAlamofireUsage(source), true);
|
|
221
|
+
assert.equal(hasSwiftJSONSerializationUsage(source), true);
|
|
222
|
+
assert.equal(hasSwiftAlamofireUsage(ignored), false);
|
|
223
|
+
assert.equal(hasSwiftJSONSerializationUsage(ignored), false);
|
|
224
|
+
});
|
|
225
|
+
|
|
194
226
|
test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
|
|
195
227
|
const source = `
|
|
196
228
|
final class LegacyBox: @unchecked Sendable {}
|
|
@@ -464,6 +464,17 @@ export const hasSwiftSensitiveLoggingUsage = (source: string): boolean => {
|
|
|
464
464
|
});
|
|
465
465
|
};
|
|
466
466
|
|
|
467
|
+
export const hasSwiftAlamofireUsage = (source: string): boolean => {
|
|
468
|
+
return (
|
|
469
|
+
collectSwiftRegexLines(source, /^\s*import\s+Alamofire\b/).length > 0 ||
|
|
470
|
+
collectSwiftRegexLines(source, /\b(?:AF|Alamofire)\s*\.\s*request\b/).length > 0
|
|
471
|
+
);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
export const hasSwiftJSONSerializationUsage = (source: string): boolean => {
|
|
475
|
+
return collectSwiftRegexLines(source, /\bJSONSerialization\s*\./).length > 0;
|
|
476
|
+
};
|
|
477
|
+
|
|
467
478
|
export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
|
|
468
479
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
469
480
|
if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
|
|
@@ -76,6 +76,22 @@ const isIOSSwiftPath = (path: string): boolean => {
|
|
|
76
76
|
return path.endsWith('.swift') && path.startsWith('apps/ios/');
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const isIOSPodfilePath = (path: string): boolean => {
|
|
80
|
+
const normalized = path.replace(/\\/g, '/');
|
|
81
|
+
return (
|
|
82
|
+
normalized.startsWith('apps/ios/') &&
|
|
83
|
+
(normalized.endsWith('/Podfile') || normalized.endsWith('/Podfile.lock'))
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const isIOSCartfilePath = (path: string): boolean => {
|
|
88
|
+
const normalized = path.replace(/\\/g, '/');
|
|
89
|
+
return (
|
|
90
|
+
normalized.startsWith('apps/ios/') &&
|
|
91
|
+
(normalized.endsWith('/Cartfile') || normalized.endsWith('/Cartfile.resolved'))
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
79
95
|
const isIOSApplicationOrPresentationPath = (path: string): boolean => {
|
|
80
96
|
return (
|
|
81
97
|
isIOSSwiftPath(path) &&
|
|
@@ -608,6 +624,8 @@ type TextDetectorRegistryEntry = {
|
|
|
608
624
|
|
|
609
625
|
const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
610
626
|
// iOS
|
|
627
|
+
{ platform: 'ios', pathCheck: isIOSPodfilePath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.dependencies.cocoapods.ast', code: 'HEURISTICS_IOS_DEPENDENCIES_COCOAPODS_AST', message: 'AST heuristic detected CocoaPods dependency files in an iOS project; Swift Package Manager remains the preferred baseline for new code.' },
|
|
628
|
+
{ platform: 'ios', pathCheck: isIOSCartfilePath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.dependencies.carthage.ast', code: 'HEURISTICS_IOS_DEPENDENCIES_CARTHAGE_AST', message: 'AST heuristic detected Carthage dependency files in an iOS project; Swift Package Manager remains the preferred baseline for new code.' },
|
|
611
629
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForceUnwrap, ruleId: 'heuristics.ios.force-unwrap.ast', code: 'HEURISTICS_IOS_FORCE_UNWRAP_AST', message: 'AST heuristic detected force unwrap usage.' },
|
|
612
630
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAnyViewUsage, ruleId: 'heuristics.ios.anyview.ast', code: 'HEURISTICS_IOS_ANYVIEW_AST', message: 'AST heuristic detected AnyView usage.' },
|
|
613
631
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForceTryUsage, ruleId: 'heuristics.ios.force-try.ast', code: 'HEURISTICS_IOS_FORCE_TRY_AST', message: 'AST heuristic detected force try usage.' },
|
|
@@ -620,6 +638,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
620
638
|
{ 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.' },
|
|
621
639
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAdHocLoggingUsage, ruleId: 'heuristics.ios.logging.adhoc-print.ast', code: 'HEURISTICS_IOS_LOGGING_ADHOC_PRINT_AST', message: 'AST heuristic detected print/debugPrint/dump/NSLog/os_log usage in iOS production code.' },
|
|
622
640
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveLoggingUsage, ruleId: 'heuristics.ios.logging.sensitive-data.ast', code: 'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST', message: 'AST heuristic detected sensitive data in an iOS logging call.' },
|
|
641
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAlamofireUsage, ruleId: 'heuristics.ios.networking.alamofire.ast', code: 'HEURISTICS_IOS_NETWORKING_ALAMOFIRE_AST', message: 'AST heuristic detected Alamofire usage in iOS production code; URLSession remains the preferred baseline for new code.' },
|
|
642
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftJSONSerializationUsage, ruleId: 'heuristics.ios.json.jsonserialization.ast', code: 'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST', message: 'AST heuristic detected JSONSerialization usage in iOS production code; Codable remains the preferred baseline for new code.' },
|
|
623
643
|
{ 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.' },
|
|
624
644
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPreconcurrencyUsage, ruleId: 'heuristics.ios.preconcurrency.ast', code: 'HEURISTICS_IOS_PRECONCURRENCY_AST', message: 'AST heuristic detected @preconcurrency usage.' },
|
|
625
645
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonisolatedUnsafeUsage, ruleId: 'heuristics.ios.nonisolated-unsafe.ast', code: 'HEURISTICS_IOS_NONISOLATED_UNSAFE_AST', message: 'AST heuristic detected nonisolated(unsafe) usage.' },
|
|
@@ -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, 49);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -19,6 +19,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
19
19
|
'heuristics.ios.task-detached.ast',
|
|
20
20
|
'heuristics.ios.logging.adhoc-print.ast',
|
|
21
21
|
'heuristics.ios.logging.sensitive-data.ast',
|
|
22
|
+
'heuristics.ios.networking.alamofire.ast',
|
|
23
|
+
'heuristics.ios.json.jsonserialization.ast',
|
|
24
|
+
'heuristics.ios.dependencies.cocoapods.ast',
|
|
25
|
+
'heuristics.ios.dependencies.carthage.ast',
|
|
22
26
|
'heuristics.ios.unchecked-sendable.ast',
|
|
23
27
|
'heuristics.ios.preconcurrency.ast',
|
|
24
28
|
'heuristics.ios.nonisolated-unsafe.ast',
|
|
@@ -71,6 +75,22 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
71
75
|
byId.get('heuristics.ios.logging.sensitive-data.ast')?.then.code,
|
|
72
76
|
'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST'
|
|
73
77
|
);
|
|
78
|
+
assert.equal(
|
|
79
|
+
byId.get('heuristics.ios.networking.alamofire.ast')?.then.code,
|
|
80
|
+
'HEURISTICS_IOS_NETWORKING_ALAMOFIRE_AST'
|
|
81
|
+
);
|
|
82
|
+
assert.equal(
|
|
83
|
+
byId.get('heuristics.ios.json.jsonserialization.ast')?.then.code,
|
|
84
|
+
'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST'
|
|
85
|
+
);
|
|
86
|
+
assert.equal(
|
|
87
|
+
byId.get('heuristics.ios.dependencies.cocoapods.ast')?.then.code,
|
|
88
|
+
'HEURISTICS_IOS_DEPENDENCIES_COCOAPODS_AST'
|
|
89
|
+
);
|
|
90
|
+
assert.equal(
|
|
91
|
+
byId.get('heuristics.ios.dependencies.carthage.ast')?.then.code,
|
|
92
|
+
'HEURISTICS_IOS_DEPENDENCIES_CARTHAGE_AST'
|
|
93
|
+
);
|
|
74
94
|
assert.equal(
|
|
75
95
|
byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
|
|
76
96
|
'HEURISTICS_IOS_PRECONCURRENCY_AST'
|
|
@@ -217,6 +217,78 @@ export const iosRules: RuleSet = [
|
|
|
217
217
|
code: 'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST',
|
|
218
218
|
},
|
|
219
219
|
},
|
|
220
|
+
{
|
|
221
|
+
id: 'heuristics.ios.networking.alamofire.ast',
|
|
222
|
+
description: 'Detects Alamofire usage in iOS production code; URLSession is the preferred baseline for new code.',
|
|
223
|
+
severity: 'WARN',
|
|
224
|
+
platform: 'ios',
|
|
225
|
+
locked: true,
|
|
226
|
+
when: {
|
|
227
|
+
kind: 'Heuristic',
|
|
228
|
+
where: {
|
|
229
|
+
ruleId: 'heuristics.ios.networking.alamofire.ast',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
then: {
|
|
233
|
+
kind: 'Finding',
|
|
234
|
+
message: 'AST heuristic detected Alamofire usage in iOS production code; URLSession remains the preferred baseline for new code.',
|
|
235
|
+
code: 'HEURISTICS_IOS_NETWORKING_ALAMOFIRE_AST',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: 'heuristics.ios.json.jsonserialization.ast',
|
|
240
|
+
description: 'Detects JSONSerialization usage in iOS production code; Codable is the preferred baseline for new code.',
|
|
241
|
+
severity: 'WARN',
|
|
242
|
+
platform: 'ios',
|
|
243
|
+
locked: true,
|
|
244
|
+
when: {
|
|
245
|
+
kind: 'Heuristic',
|
|
246
|
+
where: {
|
|
247
|
+
ruleId: 'heuristics.ios.json.jsonserialization.ast',
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
then: {
|
|
251
|
+
kind: 'Finding',
|
|
252
|
+
message: 'AST heuristic detected JSONSerialization usage in iOS production code; Codable remains the preferred baseline for new code.',
|
|
253
|
+
code: 'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'heuristics.ios.dependencies.cocoapods.ast',
|
|
258
|
+
description: 'Detects CocoaPods dependency files in iOS projects; Swift Package Manager is the preferred baseline for new code.',
|
|
259
|
+
severity: 'WARN',
|
|
260
|
+
platform: 'ios',
|
|
261
|
+
locked: true,
|
|
262
|
+
when: {
|
|
263
|
+
kind: 'Heuristic',
|
|
264
|
+
where: {
|
|
265
|
+
ruleId: 'heuristics.ios.dependencies.cocoapods.ast',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
then: {
|
|
269
|
+
kind: 'Finding',
|
|
270
|
+
message: 'AST heuristic detected CocoaPods dependency files in an iOS project; Swift Package Manager remains the preferred baseline for new code.',
|
|
271
|
+
code: 'HEURISTICS_IOS_DEPENDENCIES_COCOAPODS_AST',
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: 'heuristics.ios.dependencies.carthage.ast',
|
|
276
|
+
description: 'Detects Carthage dependency files in iOS projects; Swift Package Manager is the preferred baseline for new code.',
|
|
277
|
+
severity: 'WARN',
|
|
278
|
+
platform: 'ios',
|
|
279
|
+
locked: true,
|
|
280
|
+
when: {
|
|
281
|
+
kind: 'Heuristic',
|
|
282
|
+
where: {
|
|
283
|
+
ruleId: 'heuristics.ios.dependencies.carthage.ast',
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
then: {
|
|
287
|
+
kind: 'Finding',
|
|
288
|
+
message: 'AST heuristic detected Carthage dependency files in an iOS project; Swift Package Manager remains the preferred baseline for new code.',
|
|
289
|
+
code: 'HEURISTICS_IOS_DEPENDENCIES_CARTHAGE_AST',
|
|
290
|
+
},
|
|
291
|
+
},
|
|
220
292
|
{
|
|
221
293
|
id: 'heuristics.ios.unchecked-sendable.ast',
|
|
222
294
|
description: 'Detects @unchecked Sendable usage in iOS production code.',
|
|
@@ -499,8 +499,21 @@ struct UseCaseFactory {
|
|
|
499
499
|
✅ **Request/Response interceptors** - Logging, auth tokens
|
|
500
500
|
✅ **SSL pinning** - Para apps con alta seguridad
|
|
501
501
|
✅ **Network reachability** - Detectar conectividad
|
|
502
|
-
|
|
503
|
-
|
|
502
|
+
⚠️ **Alamofire** - No introducir en código nuevo; en brownfield existente se trata como señal de migración gradual. URLSession nativo es la línea base preferente.
|
|
503
|
+
⚠️ **JSONSerialization** - No introducir en código nuevo; en brownfield existente se trata como señal de migración gradual. Codable es la línea base preferente.
|
|
504
|
+
|
|
505
|
+
### Enforcement AST inicial de networking y JSON iOS
|
|
506
|
+
|
|
507
|
+
- `skills.ios.guideline.ios.alamofire-prohibido-usar-urlsession-nativo` se mapea a `heuristics.ios.networking.alamofire.ast`.
|
|
508
|
+
- `skills.ios.guideline.ios.codable-decodificacio-n-automa-tica-de-json-nunca-jsonserialization` se mapea a `heuristics.ios.json.jsonserialization.ast`.
|
|
509
|
+
- `skills.ios.guideline.ios.codable-para-serializacio-n-json-nunca-jsonserialization` se mapea a `heuristics.ios.json.jsonserialization.ast`.
|
|
510
|
+
- En `PROJECT MODE: brownfield`, estos hallazgos son señal de baseline/adopción y deben evitar drift nuevo sin bloquear deuda histórica salvo promoción explícita de policy.
|
|
511
|
+
|
|
512
|
+
### Enforcement AST inicial de dependencias iOS
|
|
513
|
+
|
|
514
|
+
- `skills.ios.guideline.ios.cocoapods-prohibido` se mapea a `heuristics.ios.dependencies.cocoapods.ast`.
|
|
515
|
+
- `skills.ios.guideline.ios.carthage-prohibido` se mapea a `heuristics.ios.dependencies.carthage.ast`.
|
|
516
|
+
- En `PROJECT MODE: brownfield`, estos hallazgos son señal de baseline/adopción y deben evitar drift nuevo sin bloquear deuda histórica salvo promoción explícita de policy. Swift Package Manager permanece como baseline preferente para código nuevo.
|
|
504
517
|
|
|
505
518
|
```swift
|
|
506
519
|
// ✅ Ejemplo: APIClient con URLSession y async/await
|
|
@@ -55,6 +55,26 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
55
55
|
'ios.logging.sensitive-data',
|
|
56
56
|
['heuristics.ios.logging.sensitive-data.ast']
|
|
57
57
|
),
|
|
58
|
+
'skills.ios.guideline.ios.alamofire-prohibido-usar-urlsession-nativo': heuristicDetector(
|
|
59
|
+
'ios.networking.alamofire',
|
|
60
|
+
['heuristics.ios.networking.alamofire.ast']
|
|
61
|
+
),
|
|
62
|
+
'skills.ios.guideline.ios.codable-decodificacio-n-automa-tica-de-json-nunca-jsonserialization': heuristicDetector(
|
|
63
|
+
'ios.json.jsonserialization',
|
|
64
|
+
['heuristics.ios.json.jsonserialization.ast']
|
|
65
|
+
),
|
|
66
|
+
'skills.ios.guideline.ios.codable-para-serializacio-n-json-nunca-jsonserialization': heuristicDetector(
|
|
67
|
+
'ios.json.jsonserialization',
|
|
68
|
+
['heuristics.ios.json.jsonserialization.ast']
|
|
69
|
+
),
|
|
70
|
+
'skills.ios.guideline.ios.cocoapods-prohibido': heuristicDetector(
|
|
71
|
+
'ios.dependencies.cocoapods',
|
|
72
|
+
['heuristics.ios.dependencies.cocoapods.ast']
|
|
73
|
+
),
|
|
74
|
+
'skills.ios.guideline.ios.carthage-prohibido': heuristicDetector(
|
|
75
|
+
'ios.dependencies.carthage',
|
|
76
|
+
['heuristics.ios.dependencies.carthage.ast']
|
|
77
|
+
),
|
|
58
78
|
'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
|
|
59
79
|
'heuristics.ios.unchecked-sendable.ast',
|
|
60
80
|
]),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.194",
|
|
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": {
|