pumuki 6.3.285 → 6.3.286
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/core/facts/detectors/text/ios.test.ts +83 -0
- package/core/facts/detectors/text/ios.ts +119 -0
- package/core/facts/detectors/typescript/index.test.ts +555 -0
- package/core/facts/detectors/typescript/index.ts +476 -0
- package/core/facts/extractHeuristicFacts.ts +25 -11
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +19 -0
- package/core/rules/presets/heuristics/typescript.test.ts +49 -1
- package/core/rules/presets/heuristics/typescript.ts +144 -0
- package/integrations/config/skillsDetectorRegistry.ts +49 -0
- package/package.json +1 -1
|
@@ -76,6 +76,11 @@ const isBackendTypeScriptPath = (path: string): boolean => {
|
|
|
76
76
|
return isTypeScriptHeuristicTargetPath(path) && path.startsWith('apps/backend/');
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const isBackendControllerTypeScriptPath = (path: string): boolean => {
|
|
80
|
+
const normalized = path.replace(/\\/g, '/').toLowerCase();
|
|
81
|
+
return isBackendTypeScriptPath(path) && normalized.endsWith('.controller.ts');
|
|
82
|
+
};
|
|
83
|
+
|
|
79
84
|
const isBackendDomainEntityPath = (path: string): boolean => {
|
|
80
85
|
const normalized = path.replace(/\\/g, '/').toLowerCase();
|
|
81
86
|
return (
|
|
@@ -485,11 +490,18 @@ const astDetectorRegistry: ReadonlyArray<ASTDetectorRegistryEntry> = [
|
|
|
485
490
|
{ detect: TS.hasConsoleLogCall, ruleId: 'heuristics.ts.console-log.ast', code: 'HEURISTICS_CONSOLE_LOG_AST', message: 'AST heuristic detected console.log usage.' },
|
|
486
491
|
{ detect: TS.hasConsoleErrorCall, ruleId: 'heuristics.ts.console-error.ast', code: 'HEURISTICS_CONSOLE_ERROR_AST', message: 'AST heuristic detected console.error usage.' },
|
|
487
492
|
{ detect: TS.hasSensitiveDataLoggingCall, ruleId: 'heuristics.ts.backend.sensitive-data-logging.ast', code: 'HEURISTICS_BACKEND_SENSITIVE_DATA_LOGGING_AST', message: 'AST heuristic detected sensitive data passed to a logging sink.' },
|
|
493
|
+
{ detect: TS.hasBackendLogWithoutContext, locateLines: TS.findBackendLogWithoutContextLines, ruleId: 'heuristics.ts.backend.log-without-context.ast', code: 'HEURISTICS_BACKEND_LOG_WITHOUT_CONTEXT_AST', message: 'AST heuristic detected backend log call without request/user/trace context.', pathCheck: isBackendTypeScriptPath },
|
|
488
494
|
{ detect: TS.hasBackendMagicNumberLiteral, ruleId: 'heuristics.ts.backend.magic-number-literal.ast', code: 'HEURISTICS_BACKEND_MAGIC_NUMBER_LITERAL_AST', message: 'AST heuristic detected an inline numeric literal; use a named constant for backend readability and maintainability.', pathCheck: isBackendTypeScriptPath },
|
|
489
495
|
{ detect: TS.hasBackendGenericErrorThrow, ruleId: 'heuristics.ts.backend.generic-error-throw.ast', code: 'HEURISTICS_BACKEND_GENERIC_ERROR_THROW_AST', message: 'AST heuristic detected throw new Error in backend code; use a specific domain/application exception.', pathCheck: isBackendTypeScriptPath },
|
|
496
|
+
{ detect: TS.hasBackendRawThrowExpression, locateLines: TS.findBackendRawThrowExpressionLines, ruleId: 'heuristics.ts.backend.raw-throw-expression.ast', code: 'HEURISTICS_BACKEND_RAW_THROW_EXPRESSION_AST', message: 'AST heuristic detected raw throw expression in backend code; throw a typed exception.', pathCheck: isBackendTypeScriptPath },
|
|
490
497
|
{ detect: TS.hasBackendProductionMockOrSpy, ruleId: 'heuristics.ts.backend.production-mock-or-spy.ast', code: 'HEURISTICS_BACKEND_PRODUCTION_MOCK_OR_SPY_AST', message: 'AST heuristic detected mock or spy test primitive in backend production code.', pathCheck: isBackendTypeScriptPath },
|
|
491
498
|
{ detect: TS.hasBackendErrorStackInHttpResponse, ruleId: 'heuristics.ts.backend.error-stack-in-http-response.ast', code: 'HEURISTICS_BACKEND_ERROR_STACK_IN_HTTP_RESPONSE_AST', message: 'AST heuristic detected stack trace exposed in a backend HTTP response.', pathCheck: isBackendTypeScriptPath },
|
|
499
|
+
{ detect: TS.hasBackendInconsistentErrorResponse, locateLines: TS.findBackendInconsistentErrorResponseLines, ruleId: 'heuristics.ts.backend.inconsistent-error-response.ast', code: 'HEURISTICS_BACKEND_INCONSISTENT_ERROR_RESPONSE_AST', message: 'AST heuristic detected backend error response without statusCode, message, timestamp and path.', pathCheck: isBackendTypeScriptPath },
|
|
500
|
+
{ detect: TS.hasBackendErrorPayloadWithSuccessStatus, locateLines: TS.findBackendErrorPayloadWithSuccessStatusLines, ruleId: 'heuristics.ts.backend.error-payload-success-status.ast', code: 'HEURISTICS_BACKEND_ERROR_PAYLOAD_SUCCESS_STATUS_AST', message: 'AST heuristic detected backend error payload sent with a success HTTP status.', pathCheck: isBackendTypeScriptPath },
|
|
501
|
+
{ detect: TS.hasBackendControllerEntityResponse, locateLines: TS.findBackendControllerEntityResponseLines, ruleId: 'heuristics.ts.backend.controller-entity-response.ast', code: 'HEURISTICS_BACKEND_CONTROLLER_ENTITY_RESPONSE_AST', message: 'AST heuristic detected backend controller exposing domain entities directly; return DTOs instead.', pathCheck: isBackendControllerTypeScriptPath },
|
|
492
502
|
{ detect: TS.hasBackendAnemicDomainModel, ruleId: 'heuristics.ts.backend.anemic-domain-model.ast', code: 'HEURISTICS_BACKEND_ANEMIC_DOMAIN_MODEL_AST', message: 'AST heuristic detected an anemic backend domain entity without business behavior.', pathCheck: isBackendDomainEntityPath },
|
|
503
|
+
{ detect: TS.hasBackendPermissiveCorsConfiguration, locateLines: TS.findBackendPermissiveCorsConfigurationLines, ruleId: 'heuristics.ts.backend.permissive-cors.ast', code: 'HEURISTICS_BACKEND_PERMISSIVE_CORS_AST', message: 'AST heuristic detected permissive backend CORS configuration; restrict allowed origins explicitly.', pathCheck: isBackendTypeScriptPath },
|
|
504
|
+
{ detect: TS.hasBackendStringLiteralUnionEnumCandidate, locateLines: TS.findBackendStringLiteralUnionEnumCandidateLines, ruleId: 'heuristics.ts.backend.string-literal-union-enum.ast', code: 'HEURISTICS_BACKEND_STRING_LITERAL_UNION_ENUM_AST', message: 'AST heuristic detected fixed backend string literal union; use an enum or centralized typed constants.', pathCheck: isBackendTypeScriptPath },
|
|
493
505
|
{ detect: TS.hasEvalCall, ruleId: 'heuristics.ts.eval.ast', code: 'HEURISTICS_EVAL_AST', message: 'AST heuristic detected eval usage.' },
|
|
494
506
|
{ detect: TS.hasFunctionConstructorUsage, ruleId: 'heuristics.ts.function-constructor.ast', code: 'HEURISTICS_FUNCTION_CONSTRUCTOR_AST', message: 'AST heuristic detected Function constructor usage.' },
|
|
495
507
|
{ detect: TS.hasSetTimeoutStringCallback, ruleId: 'heuristics.ts.set-timeout-string.ast', code: 'HEURISTICS_SET_TIMEOUT_STRING_AST', message: 'AST heuristic detected setTimeout with a string callback.' },
|
|
@@ -516,6 +528,7 @@ const astDetectorRegistry: ReadonlyArray<ASTDetectorRegistryEntry> = [
|
|
|
516
528
|
{ detect: TS.hasNestJsConstructorDependencyWithoutDecorator, ruleId: 'heuristics.ts.nestjs.constructor-di-without-decorator.ast', code: 'HEURISTICS_NESTJS_CONSTRUCTOR_DI_WITHOUT_DECORATOR_AST', message: 'AST heuristic detected NestJS constructor dependency injection without an explicit class decorator.' },
|
|
517
529
|
{ detect: TS.hasBackendControllerRouteWithoutGuard, locateLines: TS.findBackendControllerRouteWithoutGuardLines, ruleId: 'heuristics.ts.backend.controller-route-without-guard.ast', code: 'HEURISTICS_BACKEND_CONTROLLER_ROUTE_WITHOUT_GUARD_AST', message: 'AST heuristic detected NestJS controller route without UseGuards at method or class level.', pathCheck: isBackendTypeScriptPath },
|
|
518
530
|
{ detect: TS.hasPersistenceMutationWithoutAuditEvent, ruleId: 'heuristics.ts.backend.persistence-mutation-without-audit-event.ast', code: 'HEURISTICS_BACKEND_PERSISTENCE_MUTATION_WITHOUT_AUDIT_EVENT_AST', message: 'AST heuristic detected backend persistence mutation without audit log or domain event.' },
|
|
531
|
+
{ detect: TS.hasBackendHardDeleteWithoutSoftDelete, locateLines: TS.findBackendHardDeleteWithoutSoftDeleteLines, ruleId: 'heuristics.ts.backend.hard-delete-without-soft-delete.ast', code: 'HEURISTICS_BACKEND_HARD_DELETE_WITHOUT_SOFT_DELETE_AST', message: 'AST heuristic detected backend physical delete; use soft delete with deletedAt/deleted_at.', pathCheck: isBackendTypeScriptPath },
|
|
519
532
|
{ detect: TS.hasDtoPropertyWithoutValidation, ruleId: 'heuristics.ts.backend.dto-property-without-validation.ast', code: 'HEURISTICS_BACKEND_DTO_PROPERTY_WITHOUT_VALIDATION_AST', message: 'AST heuristic detected DTO property without class-validator/class-transformer decorator.' },
|
|
520
533
|
{ detect: TS.hasDtoNestedPropertyWithoutNestedValidation, ruleId: 'heuristics.ts.backend.dto-nested-property-without-nested-validation.ast', code: 'HEURISTICS_BACKEND_DTO_NESTED_PROPERTY_WITHOUT_NESTED_VALIDATION_AST', message: 'AST heuristic detected nested DTO property without @ValidateNested/@Type decorators.' },
|
|
521
534
|
{ detect: TS.hasLargeClassDeclaration, ruleId: 'heuristics.ts.god-class-large-class.ast', code: 'HEURISTICS_GOD_CLASS_LARGE_CLASS_AST', message: 'AST heuristic detected God Class candidate by mixed responsibility nodes in a single class declaration.' },
|
|
@@ -728,6 +741,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
728
741
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUnownedSelfCaptureUsage, ruleId: 'heuristics.ios.memory.unowned-self-capture.ast', code: 'HEURISTICS_IOS_MEMORY_UNOWNED_SELF_CAPTURE_AST', message: 'AST heuristic detected unowned capture in an iOS closure; use weak capture unless lifetime is explicitly guaranteed.' },
|
|
729
742
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNestedIfPyramidUsage, ruleId: 'heuristics.ios.maintainability.nested-if-pyramid.ast', code: 'HEURISTICS_IOS_MAINTAINABILITY_NESTED_IF_PYRAMID_AST', message: 'AST heuristic detected nested if pyramid in iOS code; prefer guard clauses and early returns.' },
|
|
730
743
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftProductionCommentUsage, ruleId: 'heuristics.ios.maintainability.comment-trivia.ast', code: 'HEURISTICS_IOS_MAINTAINABILITY_COMMENT_TRIVIA_AST', message: 'AST heuristic detected source comments in iOS production code; prefer self-documenting names and extracted concepts.' },
|
|
744
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftWarningSuppressionUsage, locateLines: TextIOS.collectSwiftWarningSuppressionLines, primaryNode: (lines) => ({ kind: 'member', name: 'Swift warning or lint suppression directive', lines }), relatedNodes: (lines) => [{ kind: 'member', name: 'replacement: fix the warned code or configure the exception centrally', lines }], why: 'Local warning and lint suppressions hide code-quality failures from the normal compiler/tooling feedback loop.', impact: 'Suppressed diagnostics can turn into future errors or let unsafe production code bypass Pumuki and repository skills without a visible remediation path.', expected_fix: 'Remove local swiftlint/swiftformat/periphery disable directives and #warning markers by fixing the underlying code, or move a justified project-wide exception into policy/config with traceability.', ruleId: 'heuristics.ios.maintainability.warning-suppression.ast', code: 'HEURISTICS_IOS_MAINTAINABILITY_WARNING_SUPPRESSION_AST', message: 'AST heuristic detected local warning/lint suppression in iOS production code; fix the underlying issue instead of hiding diagnostics.' },
|
|
731
745
|
{ 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.' },
|
|
732
746
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSwinjectUsage, ruleId: 'heuristics.ios.architecture.swinject.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_SWINJECT_AST', message: 'AST heuristic detected Swinject usage; manual dependency injection or SwiftUI Environment remain the preferred native baseline.' },
|
|
733
747
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMassiveViewControllerResponsibilityUsage, ruleId: 'heuristics.ios.architecture.massive-view-controller.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_MASSIVE_VIEW_CONTROLLER_AST', message: 'AST heuristic detected a UIViewController with direct infrastructure/data access; move data access behind application/domain boundaries.' },
|
|
@@ -744,12 +758,12 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
744
758
|
{ platform: 'ios', pathCheck: isIOSInfoPlistPath, excludePaths: [], detect: TextIOS.hasSwiftInsecureTransportUsage, ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected permissive App Transport Security configuration; HTTPS and ATS remain the preferred baseline.' },
|
|
745
759
|
{ platform: 'ios', pathCheck: isIOSLocalizableStringsPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.localization.localizable-strings.ast', code: 'HEURISTICS_IOS_LOCALIZATION_LOCALIZABLE_STRINGS_AST', message: 'AST heuristic detected Localizable.strings usage; String Catalogs (.xcstrings) remain the preferred baseline for new localization work.' },
|
|
746
760
|
{ platform: 'ios', pathCheck: isIOSInterfaceBuilderPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.interface-builder.storyboard-xib.ast', code: 'HEURISTICS_IOS_INTERFACE_BUILDER_STORYBOARD_XIB_AST', message: 'AST heuristic detected Storyboard/XIB usage; programmatic SwiftUI/UIKit UI remains the preferred baseline for versionable iOS code.' },
|
|
747
|
-
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedUiStringUsage, ruleId: 'heuristics.ios.localization.hardcoded-ui-string.ast', code: 'HEURISTICS_IOS_LOCALIZATION_HARDCODED_UI_STRING_AST', message: 'AST heuristic detected hardcoded user-facing SwiftUI text; String(localized:) and String Catalogs remain the preferred baseline.' },
|
|
761
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedUiStringUsage, locateLines: TextIOS.collectSwiftHardcodedUiStringLines, primaryNode: (lines) => ({ kind: 'property', name: 'hardcoded user-facing SwiftUI string', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: String Catalog key or String(localized:)', lines }], why: 'User-facing literals in SwiftUI code bypass String Catalogs and make localization drift invisible.', impact: 'A consumer slice can be blocked without knowing which text literal must move to localization assets.', expected_fix: 'Move visible copy to String Catalogs or use repository-approved localized string keys; keep only localization keys in SwiftUI code.', ruleId: 'heuristics.ios.localization.hardcoded-ui-string.ast', code: 'HEURISTICS_IOS_LOCALIZATION_HARDCODED_UI_STRING_AST', message: 'AST heuristic detected hardcoded user-facing SwiftUI text; String(localized:) and String Catalogs remain the preferred baseline.' },
|
|
748
762
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLooseAssetResourceUsage, ruleId: 'heuristics.ios.assets.loose-resource.ast', code: 'HEURISTICS_IOS_ASSETS_LOOSE_RESOURCE_AST', message: 'AST heuristic detected loose image resource loading in iOS production code; Asset Catalogs remain the preferred baseline.' },
|
|
749
763
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftFixedFontSizeUsage, locateLines: TextIOS.collectSwiftFixedFontSizeLines, primaryNode: (lines) => ({ kind: 'call', name: 'fixed font size API', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: Dynamic Type semantic text style or scaled metric', lines }], why: 'Fixed font sizes bypass Dynamic Type unless explicitly scaled through the text system.', impact: 'Accessibility regressions become hard to remediate if the gate reports only the file instead of the exact font call.', expected_fix: 'Use semantic SwiftUI text styles such as .headline/.body, Font.TextStyle, or UIFontMetrics/scaled metrics when a custom size is required.', ruleId: 'heuristics.ios.accessibility.fixed-font-size.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST', message: 'AST heuristic detected fixed font sizing in iOS production code; Dynamic Type semantic text styles remain the preferred baseline.' },
|
|
750
764
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPhysicalTextAlignmentUsage, ruleId: 'heuristics.ios.localization.physical-text-alignment.ast', code: 'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST', message: 'AST heuristic detected physical left/right text alignment in iOS production code; leading/trailing remain the preferred RTL-safe baseline.' },
|
|
751
765
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMainThreadBlockingSleepUsage, ruleId: 'heuristics.ios.performance.blocking-sleep.ast', code: 'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST', message: 'AST heuristic detected blocking sleep usage in iOS production code; async clocks, suspension or cancellable scheduling remain the preferred baseline.' },
|
|
752
|
-
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage, ruleId: 'heuristics.ios.accessibility.icon-only-control-label.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_ICON_ONLY_CONTROL_LABEL_AST', message: 'AST heuristic detected an icon-only SwiftUI control without accessibilityLabel; explicit accessible labels remain the preferred baseline.' },
|
|
766
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage, locateLines: TextIOS.collectSwiftIconOnlyControlWithoutAccessibilityLabelLines, primaryNode: (lines) => ({ kind: 'call', name: 'icon-only SwiftUI control', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: add accessibilityLabel to the control', lines }], why: 'Icon-only controls have no accessible name unless the label is supplied explicitly.', impact: 'VoiceOver and UI automation users cannot identify or operate the control reliably when the gate reports only the file.', expected_fix: 'Add .accessibilityLabel(...) to the icon-only control or replace the control with a visible text label.', ruleId: 'heuristics.ios.accessibility.icon-only-control-label.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_ICON_ONLY_CONTROL_LABEL_AST', message: 'AST heuristic detected an icon-only SwiftUI control without accessibilityLabel; explicit accessible labels remain the preferred baseline.' },
|
|
753
767
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInteractiveControlWithoutAccessibilityIdentifierUsage, locateLines: TextIOS.collectSwiftInteractiveControlWithoutAccessibilityIdentifierLines, ruleId: 'heuristics.ios.accessibility.missing-accessibility-identifier.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_MISSING_ACCESSIBILITY_IDENTIFIER_AST', message: 'AST heuristic detected an interactive SwiftUI control without accessibilityIdentifier; stable identifiers are required for UI automation and traceability.' },
|
|
754
768
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftBindableMissingForObservableBindingUsage, locateLines: TextIOS.collectSwiftBindableMissingForObservableBindingUsageLines, ruleId: 'heuristics.ios.swiftui.missing-bindable-observable-binding.ast', code: 'HEURISTICS_IOS_SWIFTUI_MISSING_BINDABLE_OBSERVABLE_BINDING_AST', message: 'AST heuristic detected an injected @Observable used as a binding without @Bindable.' },
|
|
755
769
|
{ 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.' },
|
|
@@ -764,28 +778,28 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
764
778
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLowContrastStaticColorPairUsage, ruleId: 'heuristics.ios.accessibility.low-contrast-static-color-pair.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_LOW_CONTRAST_STATIC_COLOR_PAIR_AST', message: 'AST heuristic detected a static SwiftUI foreground/background color pair with insufficient contrast.' },
|
|
765
779
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonPrivateStateOwnershipUsage, ruleId: 'heuristics.ios.swiftui.non-private-state-ownership.ast', code: 'HEURISTICS_IOS_SWIFTUI_NON_PRIVATE_STATE_OWNERSHIP_AST', message: 'AST heuristic detected @State/@StateObject without private visibility; SwiftUI owned state should be private.' },
|
|
766
780
|
{ 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.' },
|
|
767
|
-
{ 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.' },
|
|
768
|
-
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachSelfIdentityUsage, ruleId: 'heuristics.ios.swiftui.foreach-self-identity.ast', code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_SELF_IDENTITY_AST', message: 'AST heuristic detected ForEach(..., id:
|
|
781
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachIndicesUsage, locateLines: TextIOS.collectSwiftForEachIndicesLines, primaryNode: (lines) => ({ kind: 'call', name: 'ForEach over collection indices', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: ForEach over Identifiable elements or stable ids', lines }], why: 'Iterating indices couples SwiftUI identity to collection position instead of the domain element.', impact: 'Insertions, filtering or reordering can produce unstable view identity and confusing diffing behavior.', expected_fix: 'Iterate elements that conform to Identifiable, or pass an explicit stable id such as id: \\.id.', 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.' },
|
|
782
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachSelfIdentityUsage, locateLines: TextIOS.collectSwiftForEachSelfIdentityLines, primaryNode: (lines) => ({ kind: 'call', name: 'ForEach using id: \\.self', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: stable domain id or Identifiable model', lines }], why: 'id: \\.self is only stable when the value is immutable and uniquely identifies the domain entity.', impact: 'Lists can animate or update incorrectly when value identity changes with presentation data.', expected_fix: 'Use Identifiable models or an explicit stable domain identifier such as id: \\.id.', ruleId: 'heuristics.ios.swiftui.foreach-self-identity.ast', code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_SELF_IDENTITY_AST', message: 'AST heuristic detected ForEach(..., id: \\.self) usage; prefer a stable domain identity such as id: \\.id or Identifiable models.' },
|
|
769
783
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSelfPrintChangesUsage, ruleId: 'heuristics.ios.swiftui.self-print-changes.ast', code: 'HEURISTICS_IOS_SWIFTUI_SELF_PRINT_CHANGES_AST', message: 'AST heuristic detected Self._printChanges() in SwiftUI presentation; keep this debugging helper out of production view code.' },
|
|
770
|
-
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInlineForEachTransformUsage, ruleId: 'heuristics.ios.swiftui.inline-foreach-transform.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST', message: 'AST heuristic detected inline filter/map/sort work inside ForEach; prefiltered or cached collections remain the preferred baseline.' },
|
|
771
|
-
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiForEachConditionalViewCountUsage, ruleId: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast', code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_CONDITIONAL_VIEW_COUNT_AST', message: 'AST heuristic detected conditional view count inside ForEach; keep a constant number of views per element by moving branching into row views or modifiers.' },
|
|
784
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInlineForEachTransformUsage, locateLines: TextIOS.collectSwiftInlineForEachTransformLines, primaryNode: (lines) => ({ kind: 'call', name: 'ForEach with inline collection transform', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: precomputed/cached collection', lines }], why: 'Inline filtering, mapping or sorting inside ForEach repeats work in the render path and obscures list identity.', impact: 'SwiftUI list updates become harder to reason about and performance can degrade under recomposition.', expected_fix: 'Move the transformed collection to a named computed value, view model output or cached state before passing it to ForEach.', ruleId: 'heuristics.ios.swiftui.inline-foreach-transform.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST', message: 'AST heuristic detected inline filter/map/sort work inside ForEach; prefiltered or cached collections remain the preferred baseline.' },
|
|
785
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiForEachConditionalViewCountUsage, locateLines: TextIOS.collectSwiftUiForEachConditionalViewCountLines, primaryNode: (lines) => ({ kind: 'call', name: 'ForEach row with conditional view count', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: row view with stable structure or conditional modifiers', lines }], why: 'Changing the number of views emitted by each ForEach element can destabilize SwiftUI diffing.', impact: 'Rows can lose identity or produce unexpected animations when conditions toggle.', expected_fix: 'Move branching into a dedicated row view or prefer conditional modifiers/values that keep a stable view structure per element.', ruleId: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast', code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_CONDITIONAL_VIEW_COUNT_AST', message: 'AST heuristic detected conditional view count inside ForEach; keep a constant number of views per element by moving branching into row views or modifiers.' },
|
|
772
786
|
{ 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.' },
|
|
773
787
|
{ 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.' },
|
|
774
788
|
{ 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.' },
|
|
775
789
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftExplicitColorStaticMemberUsage, locateLines: TextIOS.collectSwiftExplicitColorStaticMemberLines, primaryNode: (lines) => ({ kind: 'member', name: 'explicit Color.* static member', lines }), relatedNodes: (lines) => [{ kind: 'member', name: 'replacement: SwiftUI static member lookup .colorName', lines }], why: 'Explicit Color.* in SwiftUI view modifiers is noisier than static member lookup and can hide style token drift in presentation code.', impact: 'The fix is local and mechanical, but without line evidence the user sees a whole-file block instead of the exact member access.', expected_fix: 'Replace Color.blue/Color.primary/etc. with .blue/.primary in SwiftUI style contexts, or use named asset colors such as Color("BrandPrimary") when design tokens are required.', ruleId: 'heuristics.ios.swiftui.explicit-color-static-member.ast', code: 'HEURISTICS_IOS_SWIFTUI_EXPLICIT_COLOR_STATIC_MEMBER_AST', message: 'AST heuristic detected Color.* static member usage where SwiftUI static member lookup may be preferred.' },
|
|
776
790
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftClosureBasedViewBuilderContentUsage, ruleId: 'heuristics.ios.swiftui.closure-based-viewbuilder-content.ast', code: 'HEURISTICS_IOS_SWIFTUI_CLOSURE_BASED_VIEWBUILDER_CONTENT_AST', message: 'AST heuristic detected closure-based content storage; @ViewBuilder let content: Content remains the preferred SwiftUI container baseline.' },
|
|
777
791
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLargeConfigContextViewPropertyUsage, ruleId: 'heuristics.ios.swiftui.large-config-context-prop.ast', code: 'HEURISTICS_IOS_SWIFTUI_LARGE_CONFIG_CONTEXT_PROP_AST', message: 'AST heuristic detected a SwiftUI View storing a broad config/context object; pass only needed values to reduce update fan-out.' },
|
|
778
|
-
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiConditionalSameViewIdentityUsage, ruleId: 'heuristics.ios.swiftui.conditional-same-view-identity.ast', code: 'HEURISTICS_IOS_SWIFTUI_CONDITIONAL_SAME_VIEW_IDENTITY_AST', message: 'AST heuristic detected conditional branches rebuilding the same SwiftUI View type; prefer conditional modifiers or values to preserve view identity.' },
|
|
792
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiConditionalSameViewIdentityUsage, locateLines: TextIOS.collectSwiftUiConditionalSameViewIdentityLines, primaryNode: (lines) => ({ kind: 'call', name: 'conditional branches rebuilding same SwiftUI view type', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: conditional modifier or conditional value', lines }], why: 'Rebuilding the same view type in if/else branches hides a state change that should be a modifier or value.', impact: 'SwiftUI identity can churn unnecessarily and block pixel-perfect slices without pointing to the conditional branch.', expected_fix: 'Keep one view instance and move the condition into modifiers, parameters or extracted stable row state.', ruleId: 'heuristics.ios.swiftui.conditional-same-view-identity.ast', code: 'HEURISTICS_IOS_SWIFTUI_CONDITIONAL_SAME_VIEW_IDENTITY_AST', message: 'AST heuristic detected conditional branches rebuilding the same SwiftUI View type; prefer conditional modifiers or values to preserve view identity.' },
|
|
779
793
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiParentOwnedSheetActionUsage, ruleId: 'heuristics.ios.swiftui.parent-owned-sheet-action.ast', code: 'HEURISTICS_IOS_SWIFTUI_PARENT_OWNED_SHEET_ACTION_AST', message: 'AST heuristic detected a SwiftUI sheet receiving parent-owned action callbacks; sheets should own save/cancel actions and call dismiss() internally.' },
|
|
780
794
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftRedundantReactiveStateAssignmentUsage, ruleId: 'heuristics.ios.swiftui.redundant-reactive-state-assignment.ast', code: 'HEURISTICS_IOS_SWIFTUI_REDUNDANT_REACTIVE_STATE_ASSIGNMENT_AST', message: 'AST heuristic detected reactive state assignment without a value-change guard; check for value changes before assigning state in hot paths.' },
|
|
781
|
-
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonLazyScrollForEachUsage, ruleId: 'heuristics.ios.swiftui.non-lazy-scroll-foreach.ast', code: 'HEURISTICS_IOS_SWIFTUI_NON_LAZY_SCROLL_FOREACH_AST', message: 'AST heuristic detected ScrollView with a non-lazy stack feeding ForEach; LazyVStack/LazyHStack remain the preferred baseline for large scrollable collections.' },
|
|
795
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonLazyScrollForEachUsage, locateLines: TextIOS.collectSwiftNonLazyScrollForEachLines, primaryNode: (lines) => ({ kind: 'call', name: 'ScrollView non-lazy stack with ForEach', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: LazyVStack or LazyHStack', lines }], why: 'A non-lazy stack inside ScrollView renders all rows eagerly instead of virtualizing collection content.', impact: 'Large lists can degrade performance and the gate must point to the ScrollView/ForEach pair, not the whole file.', expected_fix: 'Replace VStack/HStack under ScrollView with LazyVStack/LazyHStack when rendering collection rows.', ruleId: 'heuristics.ios.swiftui.non-lazy-scroll-foreach.ast', code: 'HEURISTICS_IOS_SWIFTUI_NON_LAZY_SCROLL_FOREACH_AST', message: 'AST heuristic detected ScrollView with a non-lazy stack feeding ForEach; LazyVStack/LazyHStack remain the preferred baseline for large scrollable collections.' },
|
|
782
796
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftViewBodyObjectCreationUsage, ruleId: 'heuristics.ios.swiftui.body-object-creation.ast', code: 'HEURISTICS_IOS_SWIFTUI_BODY_OBJECT_CREATION_AST', message: 'AST heuristic detected formatter object creation inside SwiftUI body; keep body simple and move expensive objects out of render paths.' },
|
|
783
797
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiImageDataDecodingUsage, ruleId: 'heuristics.ios.swiftui.image-data-decoding.ast', code: 'HEURISTICS_IOS_SWIFTUI_IMAGE_DATA_DECODING_AST', message: 'AST heuristic detected UIImage(data:) in SwiftUI presentation; downsample image data before rendering large images.' },
|
|
784
|
-
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiInlineActionLogicUsage, ruleId: 'heuristics.ios.swiftui.inline-action-logic.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_ACTION_LOGIC_AST', message: 'AST heuristic detected inline logic inside a SwiftUI action handler; action handlers should reference methods and keep view declarations focused.' },
|
|
798
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiInlineActionLogicUsage, locateLines: TextIOS.collectSwiftUiInlineActionLogicLines, primaryNode: (lines) => ({ kind: 'call', name: 'SwiftUI Button action with inline logic', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: extracted action method', lines }], why: 'Inline branching or async work in a SwiftUI action makes the view declaration own behavior instead of delegating to a named action.', impact: 'Reviewers and agents cannot remediate a blocked view safely when the finding lacks the exact Button action node.', expected_fix: 'Extract the action body to a named method or view model command and reference that method from Button.', ruleId: 'heuristics.ios.swiftui.inline-action-logic.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_ACTION_LOGIC_AST', message: 'AST heuristic detected inline logic inside a SwiftUI action handler; action handlers should reference methods and keep view declarations focused.' },
|
|
785
799
|
{ 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.' },
|
|
786
800
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUntypedNavigationLinkDestinationUsage, ruleId: 'heuristics.ios.swiftui.untyped-navigation-link-destination.ast', code: 'HEURISTICS_IOS_SWIFTUI_UNTYPED_NAVIGATION_LINK_DESTINATION_AST', message: 'AST heuristic detected untyped NavigationLink destination usage; prefer NavigationLink(value:) with navigationDestination(for:) for type-safe navigation.' },
|
|
787
|
-
{ 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.' },
|
|
788
|
-
{ 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.' },
|
|
801
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForegroundColorUsage, locateLines: TextIOS.collectSwiftForegroundColorLines, primaryNode: (lines) => ({ kind: 'call', name: 'SwiftUI foregroundColor modifier', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: foregroundStyle or design token', lines }], why: 'foregroundColor is a legacy SwiftUI styling API compared with foregroundStyle and tokenized style values.', impact: 'Modernization blockers need the exact modifier line to avoid whole-file refactors.', expected_fix: 'Replace .foregroundColor(...) with .foregroundStyle(...) or the repository-approved design token style.', ruleId: 'heuristics.ios.foreground-color.ast', code: 'HEURISTICS_IOS_FOREGROUND_COLOR_AST', message: 'AST heuristic detected foregroundColor usage.' },
|
|
802
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCornerRadiusUsage, locateLines: TextIOS.collectSwiftCornerRadiusLines, primaryNode: (lines) => ({ kind: 'call', name: 'SwiftUI cornerRadius modifier', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: clipShape with rounded rectangle style', lines }], why: 'cornerRadius is a legacy shape shortcut that hides the shape semantics used by modern SwiftUI styling.', impact: 'The fix is local, but without line evidence the gate forces unsafe file-wide remediation.', expected_fix: 'Replace .cornerRadius(...) with .clipShape(.rect(cornerRadius: ...)) or a named reusable shape/style token.', ruleId: 'heuristics.ios.corner-radius.ast', code: 'HEURISTICS_IOS_CORNER_RADIUS_AST', message: 'AST heuristic detected cornerRadius usage.' },
|
|
789
803
|
{ 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.' },
|
|
790
804
|
{ 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.' },
|
|
791
805
|
{ 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.' },
|
|
@@ -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, 103);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -29,6 +29,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
29
29
|
'heuristics.ios.memory.unowned-self-capture.ast',
|
|
30
30
|
'heuristics.ios.maintainability.nested-if-pyramid.ast',
|
|
31
31
|
'heuristics.ios.maintainability.comment-trivia.ast',
|
|
32
|
+
'heuristics.ios.maintainability.warning-suppression.ast',
|
|
32
33
|
'heuristics.ios.architecture.custom-singleton.ast',
|
|
33
34
|
'heuristics.ios.architecture.swinject.ast',
|
|
34
35
|
'heuristics.ios.architecture.massive-view-controller.ast',
|
|
@@ -148,6 +149,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
148
149
|
byId.get('heuristics.ios.maintainability.comment-trivia.ast')?.then.code,
|
|
149
150
|
'HEURISTICS_IOS_MAINTAINABILITY_COMMENT_TRIVIA_AST'
|
|
150
151
|
);
|
|
152
|
+
assert.equal(
|
|
153
|
+
byId.get('heuristics.ios.maintainability.warning-suppression.ast')?.then.code,
|
|
154
|
+
'HEURISTICS_IOS_MAINTAINABILITY_WARNING_SUPPRESSION_AST'
|
|
155
|
+
);
|
|
151
156
|
assert.equal(
|
|
152
157
|
byId.get('heuristics.ios.swiftui.onchange-task.ast')?.then.code,
|
|
153
158
|
'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST'
|
|
@@ -407,6 +407,25 @@ export const iosRules: RuleSet = [
|
|
|
407
407
|
code: 'HEURISTICS_IOS_MAINTAINABILITY_COMMENT_TRIVIA_AST',
|
|
408
408
|
},
|
|
409
409
|
},
|
|
410
|
+
{
|
|
411
|
+
id: 'heuristics.ios.maintainability.warning-suppression.ast',
|
|
412
|
+
description: 'Detects local warning or lint suppression directives in iOS production Swift code.',
|
|
413
|
+
severity: 'WARN',
|
|
414
|
+
platform: 'ios',
|
|
415
|
+
locked: true,
|
|
416
|
+
when: {
|
|
417
|
+
kind: 'Heuristic',
|
|
418
|
+
where: {
|
|
419
|
+
ruleId: 'heuristics.ios.maintainability.warning-suppression.ast',
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
then: {
|
|
423
|
+
kind: 'Finding',
|
|
424
|
+
message:
|
|
425
|
+
'AST heuristic detected local warning/lint suppression in iOS production code; fix the underlying issue instead of hiding diagnostics.',
|
|
426
|
+
code: 'HEURISTICS_IOS_MAINTAINABILITY_WARNING_SUPPRESSION_AST',
|
|
427
|
+
},
|
|
428
|
+
},
|
|
410
429
|
{
|
|
411
430
|
id: 'heuristics.ios.architecture.custom-singleton.ast',
|
|
412
431
|
description: 'Detects custom static shared singletons in iOS production code.',
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { typescriptRules } from './typescript';
|
|
4
4
|
|
|
5
5
|
test('typescriptRules define reglas heurísticas locked para plataforma generic', () => {
|
|
6
|
-
assert.equal(typescriptRules.length,
|
|
6
|
+
assert.equal(typescriptRules.length, 47);
|
|
7
7
|
|
|
8
8
|
const ids = typescriptRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -12,11 +12,18 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
|
|
|
12
12
|
'heuristics.ts.console-log.ast',
|
|
13
13
|
'heuristics.ts.console-error.ast',
|
|
14
14
|
'heuristics.ts.backend.sensitive-data-logging.ast',
|
|
15
|
+
'heuristics.ts.backend.log-without-context.ast',
|
|
15
16
|
'heuristics.ts.backend.magic-number-literal.ast',
|
|
16
17
|
'heuristics.ts.backend.generic-error-throw.ast',
|
|
18
|
+
'heuristics.ts.backend.raw-throw-expression.ast',
|
|
17
19
|
'heuristics.ts.backend.production-mock-or-spy.ast',
|
|
18
20
|
'heuristics.ts.backend.error-stack-in-http-response.ast',
|
|
21
|
+
'heuristics.ts.backend.inconsistent-error-response.ast',
|
|
22
|
+
'heuristics.ts.backend.error-payload-success-status.ast',
|
|
23
|
+
'heuristics.ts.backend.controller-entity-response.ast',
|
|
19
24
|
'heuristics.ts.backend.anemic-domain-model.ast',
|
|
25
|
+
'heuristics.ts.backend.permissive-cors.ast',
|
|
26
|
+
'heuristics.ts.backend.string-literal-union-enum.ast',
|
|
20
27
|
'heuristics.ts.eval.ast',
|
|
21
28
|
'heuristics.ts.function-constructor.ast',
|
|
22
29
|
'heuristics.ts.set-timeout-string.ast',
|
|
@@ -43,6 +50,7 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
|
|
|
43
50
|
'heuristics.ts.nestjs.constructor-di-without-decorator.ast',
|
|
44
51
|
'heuristics.ts.backend.controller-route-without-guard.ast',
|
|
45
52
|
'heuristics.ts.backend.persistence-mutation-without-audit-event.ast',
|
|
53
|
+
'heuristics.ts.backend.hard-delete-without-soft-delete.ast',
|
|
46
54
|
'heuristics.ts.backend.dto-property-without-validation.ast',
|
|
47
55
|
'heuristics.ts.backend.dto-nested-property-without-nested-validation.ast',
|
|
48
56
|
'heuristics.ts.god-class-large-class.ast',
|
|
@@ -85,6 +93,10 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
|
|
|
85
93
|
byId.get('heuristics.ts.backend.sensitive-data-logging.ast')?.then.code,
|
|
86
94
|
'HEURISTICS_BACKEND_SENSITIVE_DATA_LOGGING_AST'
|
|
87
95
|
);
|
|
96
|
+
assert.equal(
|
|
97
|
+
byId.get('heuristics.ts.backend.log-without-context.ast')?.then.code,
|
|
98
|
+
'HEURISTICS_BACKEND_LOG_WITHOUT_CONTEXT_AST'
|
|
99
|
+
);
|
|
88
100
|
assert.equal(
|
|
89
101
|
byId.get('heuristics.ts.backend.magic-number-literal.ast')?.then.code,
|
|
90
102
|
'HEURISTICS_BACKEND_MAGIC_NUMBER_LITERAL_AST'
|
|
@@ -93,6 +105,10 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
|
|
|
93
105
|
byId.get('heuristics.ts.backend.generic-error-throw.ast')?.then.code,
|
|
94
106
|
'HEURISTICS_BACKEND_GENERIC_ERROR_THROW_AST'
|
|
95
107
|
);
|
|
108
|
+
assert.equal(
|
|
109
|
+
byId.get('heuristics.ts.backend.raw-throw-expression.ast')?.then.code,
|
|
110
|
+
'HEURISTICS_BACKEND_RAW_THROW_EXPRESSION_AST'
|
|
111
|
+
);
|
|
96
112
|
assert.equal(
|
|
97
113
|
byId.get('heuristics.ts.backend.production-mock-or-spy.ast')?.then.code,
|
|
98
114
|
'HEURISTICS_BACKEND_PRODUCTION_MOCK_OR_SPY_AST'
|
|
@@ -101,10 +117,30 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
|
|
|
101
117
|
byId.get('heuristics.ts.backend.error-stack-in-http-response.ast')?.then.code,
|
|
102
118
|
'HEURISTICS_BACKEND_ERROR_STACK_IN_HTTP_RESPONSE_AST'
|
|
103
119
|
);
|
|
120
|
+
assert.equal(
|
|
121
|
+
byId.get('heuristics.ts.backend.inconsistent-error-response.ast')?.then.code,
|
|
122
|
+
'HEURISTICS_BACKEND_INCONSISTENT_ERROR_RESPONSE_AST'
|
|
123
|
+
);
|
|
124
|
+
assert.equal(
|
|
125
|
+
byId.get('heuristics.ts.backend.error-payload-success-status.ast')?.then.code,
|
|
126
|
+
'HEURISTICS_BACKEND_ERROR_PAYLOAD_SUCCESS_STATUS_AST'
|
|
127
|
+
);
|
|
128
|
+
assert.equal(
|
|
129
|
+
byId.get('heuristics.ts.backend.controller-entity-response.ast')?.then.code,
|
|
130
|
+
'HEURISTICS_BACKEND_CONTROLLER_ENTITY_RESPONSE_AST'
|
|
131
|
+
);
|
|
104
132
|
assert.equal(
|
|
105
133
|
byId.get('heuristics.ts.backend.anemic-domain-model.ast')?.then.code,
|
|
106
134
|
'HEURISTICS_BACKEND_ANEMIC_DOMAIN_MODEL_AST'
|
|
107
135
|
);
|
|
136
|
+
assert.equal(
|
|
137
|
+
byId.get('heuristics.ts.backend.permissive-cors.ast')?.then.code,
|
|
138
|
+
'HEURISTICS_BACKEND_PERMISSIVE_CORS_AST'
|
|
139
|
+
);
|
|
140
|
+
assert.equal(
|
|
141
|
+
byId.get('heuristics.ts.backend.string-literal-union-enum.ast')?.then.code,
|
|
142
|
+
'HEURISTICS_BACKEND_STRING_LITERAL_UNION_ENUM_AST'
|
|
143
|
+
);
|
|
108
144
|
assert.equal(
|
|
109
145
|
byId.get('heuristics.ts.frontend-test-direct-network-call.ast')?.then.code,
|
|
110
146
|
'HEURISTICS_FRONTEND_TEST_DIRECT_NETWORK_CALL_AST'
|
|
@@ -137,6 +173,10 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
|
|
|
137
173
|
byId.get('heuristics.ts.backend.persistence-mutation-without-audit-event.ast')?.then.code,
|
|
138
174
|
'HEURISTICS_BACKEND_PERSISTENCE_MUTATION_WITHOUT_AUDIT_EVENT_AST'
|
|
139
175
|
);
|
|
176
|
+
assert.equal(
|
|
177
|
+
byId.get('heuristics.ts.backend.hard-delete-without-soft-delete.ast')?.then.code,
|
|
178
|
+
'HEURISTICS_BACKEND_HARD_DELETE_WITHOUT_SOFT_DELETE_AST'
|
|
179
|
+
);
|
|
140
180
|
assert.equal(
|
|
141
181
|
byId.get('heuristics.ts.backend.dto-property-without-validation.ast')?.then.code,
|
|
142
182
|
'HEURISTICS_BACKEND_DTO_PROPERTY_WITHOUT_VALIDATION_AST'
|
|
@@ -156,14 +196,22 @@ test('typescriptRules define reglas heurísticas locked para plataforma generic'
|
|
|
156
196
|
rule.id === 'heuristics.ts.god-class-large-class.ast' ||
|
|
157
197
|
rule.id === 'heuristics.ts.backend.controller-route-without-guard.ast' ||
|
|
158
198
|
rule.id === 'heuristics.ts.backend.persistence-mutation-without-audit-event.ast' ||
|
|
199
|
+
rule.id === 'heuristics.ts.backend.hard-delete-without-soft-delete.ast' ||
|
|
159
200
|
rule.id === 'heuristics.ts.backend.dto-property-without-validation.ast' ||
|
|
160
201
|
rule.id === 'heuristics.ts.backend.dto-nested-property-without-nested-validation.ast' ||
|
|
161
202
|
rule.id === 'heuristics.ts.backend.sensitive-data-logging.ast' ||
|
|
203
|
+
rule.id === 'heuristics.ts.backend.log-without-context.ast' ||
|
|
162
204
|
rule.id === 'heuristics.ts.backend.magic-number-literal.ast' ||
|
|
163
205
|
rule.id === 'heuristics.ts.backend.generic-error-throw.ast' ||
|
|
206
|
+
rule.id === 'heuristics.ts.backend.raw-throw-expression.ast' ||
|
|
164
207
|
rule.id === 'heuristics.ts.backend.production-mock-or-spy.ast' ||
|
|
165
208
|
rule.id === 'heuristics.ts.backend.error-stack-in-http-response.ast' ||
|
|
209
|
+
rule.id === 'heuristics.ts.backend.inconsistent-error-response.ast' ||
|
|
210
|
+
rule.id === 'heuristics.ts.backend.error-payload-success-status.ast' ||
|
|
211
|
+
rule.id === 'heuristics.ts.backend.controller-entity-response.ast' ||
|
|
166
212
|
rule.id === 'heuristics.ts.backend.anemic-domain-model.ast' ||
|
|
213
|
+
rule.id === 'heuristics.ts.backend.permissive-cors.ast' ||
|
|
214
|
+
rule.id === 'heuristics.ts.backend.string-literal-union-enum.ast' ||
|
|
167
215
|
rule.id === 'heuristics.ts.sensitive-token-in-url.ast'
|
|
168
216
|
) {
|
|
169
217
|
assert.equal(rule.severity, 'ERROR');
|
|
@@ -91,6 +91,24 @@ export const typescriptRules: RuleSet = [
|
|
|
91
91
|
code: 'HEURISTICS_BACKEND_SENSITIVE_DATA_LOGGING_AST',
|
|
92
92
|
},
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
id: 'heuristics.ts.backend.log-without-context.ast',
|
|
96
|
+
description: 'Detects backend logger calls that omit request, user or trace context.',
|
|
97
|
+
severity: 'ERROR',
|
|
98
|
+
platform: 'generic',
|
|
99
|
+
locked: true,
|
|
100
|
+
when: {
|
|
101
|
+
kind: 'Heuristic',
|
|
102
|
+
where: {
|
|
103
|
+
ruleId: 'heuristics.ts.backend.log-without-context.ast',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
then: {
|
|
107
|
+
kind: 'Finding',
|
|
108
|
+
message: 'AST heuristic detected backend log call without request/user/trace context.',
|
|
109
|
+
code: 'HEURISTICS_BACKEND_LOG_WITHOUT_CONTEXT_AST',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
94
112
|
{
|
|
95
113
|
id: 'heuristics.ts.backend.magic-number-literal.ast',
|
|
96
114
|
description: 'Detects inline numeric literals in backend TypeScript code.',
|
|
@@ -127,6 +145,24 @@ export const typescriptRules: RuleSet = [
|
|
|
127
145
|
code: 'HEURISTICS_BACKEND_GENERIC_ERROR_THROW_AST',
|
|
128
146
|
},
|
|
129
147
|
},
|
|
148
|
+
{
|
|
149
|
+
id: 'heuristics.ts.backend.raw-throw-expression.ast',
|
|
150
|
+
description: 'Detects backend throw statements that throw raw literals, objects, or identifiers instead of typed exceptions.',
|
|
151
|
+
severity: 'ERROR',
|
|
152
|
+
platform: 'generic',
|
|
153
|
+
locked: true,
|
|
154
|
+
when: {
|
|
155
|
+
kind: 'Heuristic',
|
|
156
|
+
where: {
|
|
157
|
+
ruleId: 'heuristics.ts.backend.raw-throw-expression.ast',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
then: {
|
|
161
|
+
kind: 'Finding',
|
|
162
|
+
message: 'AST heuristic detected raw throw expression in backend code; throw a typed exception.',
|
|
163
|
+
code: 'HEURISTICS_BACKEND_RAW_THROW_EXPRESSION_AST',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
130
166
|
{
|
|
131
167
|
id: 'heuristics.ts.backend.production-mock-or-spy.ast',
|
|
132
168
|
description: 'Detects mock or spy test primitives in backend production TypeScript code.',
|
|
@@ -163,6 +199,60 @@ export const typescriptRules: RuleSet = [
|
|
|
163
199
|
code: 'HEURISTICS_BACKEND_ERROR_STACK_IN_HTTP_RESPONSE_AST',
|
|
164
200
|
},
|
|
165
201
|
},
|
|
202
|
+
{
|
|
203
|
+
id: 'heuristics.ts.backend.inconsistent-error-response.ast',
|
|
204
|
+
description: 'Detects backend error responses that do not expose the consistent statusCode/message/timestamp/path contract.',
|
|
205
|
+
severity: 'ERROR',
|
|
206
|
+
platform: 'generic',
|
|
207
|
+
locked: true,
|
|
208
|
+
when: {
|
|
209
|
+
kind: 'Heuristic',
|
|
210
|
+
where: {
|
|
211
|
+
ruleId: 'heuristics.ts.backend.inconsistent-error-response.ast',
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
then: {
|
|
215
|
+
kind: 'Finding',
|
|
216
|
+
message: 'AST heuristic detected inconsistent backend error response contract.',
|
|
217
|
+
code: 'HEURISTICS_BACKEND_INCONSISTENT_ERROR_RESPONSE_AST',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'heuristics.ts.backend.error-payload-success-status.ast',
|
|
222
|
+
description: 'Detects backend error payloads sent with success HTTP status codes.',
|
|
223
|
+
severity: 'ERROR',
|
|
224
|
+
platform: 'generic',
|
|
225
|
+
locked: true,
|
|
226
|
+
when: {
|
|
227
|
+
kind: 'Heuristic',
|
|
228
|
+
where: {
|
|
229
|
+
ruleId: 'heuristics.ts.backend.error-payload-success-status.ast',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
then: {
|
|
233
|
+
kind: 'Finding',
|
|
234
|
+
message: 'AST heuristic detected backend error payload sent with a success HTTP status.',
|
|
235
|
+
code: 'HEURISTICS_BACKEND_ERROR_PAYLOAD_SUCCESS_STATUS_AST',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: 'heuristics.ts.backend.controller-entity-response.ast',
|
|
240
|
+
description: 'Detects backend controllers that expose domain entities directly instead of response DTOs.',
|
|
241
|
+
severity: 'ERROR',
|
|
242
|
+
platform: 'generic',
|
|
243
|
+
locked: true,
|
|
244
|
+
when: {
|
|
245
|
+
kind: 'Heuristic',
|
|
246
|
+
where: {
|
|
247
|
+
ruleId: 'heuristics.ts.backend.controller-entity-response.ast',
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
then: {
|
|
251
|
+
kind: 'Finding',
|
|
252
|
+
message: 'AST heuristic detected backend controller exposing domain entities directly.',
|
|
253
|
+
code: 'HEURISTICS_BACKEND_CONTROLLER_ENTITY_RESPONSE_AST',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
166
256
|
{
|
|
167
257
|
id: 'heuristics.ts.backend.anemic-domain-model.ast',
|
|
168
258
|
description: 'Detects backend domain entities that expose state without business behavior.',
|
|
@@ -181,6 +271,42 @@ export const typescriptRules: RuleSet = [
|
|
|
181
271
|
code: 'HEURISTICS_BACKEND_ANEMIC_DOMAIN_MODEL_AST',
|
|
182
272
|
},
|
|
183
273
|
},
|
|
274
|
+
{
|
|
275
|
+
id: 'heuristics.ts.backend.permissive-cors.ast',
|
|
276
|
+
description: 'Detects permissive backend CORS configuration in NestJS/Express bootstrap code.',
|
|
277
|
+
severity: 'ERROR',
|
|
278
|
+
platform: 'generic',
|
|
279
|
+
locked: true,
|
|
280
|
+
when: {
|
|
281
|
+
kind: 'Heuristic',
|
|
282
|
+
where: {
|
|
283
|
+
ruleId: 'heuristics.ts.backend.permissive-cors.ast',
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
then: {
|
|
287
|
+
kind: 'Finding',
|
|
288
|
+
message: 'AST heuristic detected permissive backend CORS configuration.',
|
|
289
|
+
code: 'HEURISTICS_BACKEND_PERMISSIVE_CORS_AST',
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: 'heuristics.ts.backend.string-literal-union-enum.ast',
|
|
294
|
+
description: 'Detects backend fixed string literal unions that should be represented as enums or centralized typed constants.',
|
|
295
|
+
severity: 'ERROR',
|
|
296
|
+
platform: 'generic',
|
|
297
|
+
locked: true,
|
|
298
|
+
when: {
|
|
299
|
+
kind: 'Heuristic',
|
|
300
|
+
where: {
|
|
301
|
+
ruleId: 'heuristics.ts.backend.string-literal-union-enum.ast',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
then: {
|
|
305
|
+
kind: 'Finding',
|
|
306
|
+
message: 'AST heuristic detected a backend fixed string literal union; use an enum or centralized typed constants.',
|
|
307
|
+
code: 'HEURISTICS_BACKEND_STRING_LITERAL_UNION_ENUM_AST',
|
|
308
|
+
},
|
|
309
|
+
},
|
|
184
310
|
{
|
|
185
311
|
id: 'heuristics.ts.eval.ast',
|
|
186
312
|
description: 'Detects eval invocations in TypeScript/TSX production files.',
|
|
@@ -658,6 +784,24 @@ export const typescriptRules: RuleSet = [
|
|
|
658
784
|
code: 'HEURISTICS_BACKEND_PERSISTENCE_MUTATION_WITHOUT_AUDIT_EVENT_AST',
|
|
659
785
|
},
|
|
660
786
|
},
|
|
787
|
+
{
|
|
788
|
+
id: 'heuristics.ts.backend.hard-delete-without-soft-delete.ast',
|
|
789
|
+
description: 'Detects backend physical delete calls where soft delete with deletedAt/deleted_at should be used.',
|
|
790
|
+
severity: 'ERROR',
|
|
791
|
+
platform: 'generic',
|
|
792
|
+
locked: true,
|
|
793
|
+
when: {
|
|
794
|
+
kind: 'Heuristic',
|
|
795
|
+
where: {
|
|
796
|
+
ruleId: 'heuristics.ts.backend.hard-delete-without-soft-delete.ast',
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
then: {
|
|
800
|
+
kind: 'Finding',
|
|
801
|
+
message: 'AST heuristic detected backend physical delete; use soft delete with deletedAt/deleted_at.',
|
|
802
|
+
code: 'HEURISTICS_BACKEND_HARD_DELETE_WITHOUT_SOFT_DELETE_AST',
|
|
803
|
+
},
|
|
804
|
+
},
|
|
661
805
|
{
|
|
662
806
|
id: 'heuristics.ts.backend.dto-property-without-validation.ast',
|
|
663
807
|
description:
|
|
@@ -122,6 +122,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
122
122
|
'ios.maintainability.comment-trivia',
|
|
123
123
|
['heuristics.ios.maintainability.comment-trivia.ast']
|
|
124
124
|
),
|
|
125
|
+
'skills.ios.guideline.ios.ignoring-warnings-warnings-errores-futuros': heuristicDetector(
|
|
126
|
+
'ios.maintainability.warning-suppression',
|
|
127
|
+
['heuristics.ios.maintainability.warning-suppression.ast']
|
|
128
|
+
),
|
|
125
129
|
'skills.ios.guideline.ios.no-singleton-usar-inyeccio-n-de-dependencias-no-compartir-instancias-g': heuristicDetector(
|
|
126
130
|
'ios.architecture.custom-singleton',
|
|
127
131
|
['heuristics.ios.architecture.custom-singleton.ast']
|
|
@@ -777,6 +781,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
777
781
|
heuristicDetector('typescript.backend.persistence-mutation-without-audit-event', [
|
|
778
782
|
'heuristics.ts.backend.persistence-mutation-without-audit-event.ast',
|
|
779
783
|
]),
|
|
784
|
+
'skills.backend.guideline.backend.soft-deletes-deletedat-en-lugar-de-delete-fi-sico':
|
|
785
|
+
heuristicDetector('typescript.backend.hard-delete-without-soft-delete', [
|
|
786
|
+
'heuristics.ts.backend.hard-delete-without-soft-delete.ast',
|
|
787
|
+
]),
|
|
788
|
+
'skills.backend.guideline.backend.soft-deletes-por-defecto-deletedat-column':
|
|
789
|
+
heuristicDetector('typescript.backend.hard-delete-without-soft-delete', [
|
|
790
|
+
'heuristics.ts.backend.hard-delete-without-soft-delete.ast',
|
|
791
|
+
]),
|
|
780
792
|
'skills.backend.guideline.backend.class-validator-decorators-isstring-isemail-min-max':
|
|
781
793
|
heuristicDetector('typescript.backend.dto-property-without-validation', [
|
|
782
794
|
'heuristics.ts.backend.dto-property-without-validation.ast',
|
|
@@ -837,6 +849,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
837
849
|
heuristicDetector('typescript.backend.sensitive-data-logging', [
|
|
838
850
|
'heuristics.ts.backend.sensitive-data-logging.ast',
|
|
839
851
|
]),
|
|
852
|
+
'skills.backend.guideline.backend.contexto-en-logs-userid-requestid-traceid':
|
|
853
|
+
heuristicDetector('typescript.backend.log-without-context', [
|
|
854
|
+
'heuristics.ts.backend.log-without-context.ast',
|
|
855
|
+
]),
|
|
856
|
+
'skills.backend.guideline.backend.loggear-errores-con-contexto-completo':
|
|
857
|
+
heuristicDetector('typescript.backend.log-without-context', [
|
|
858
|
+
'heuristics.ts.backend.log-without-context.ast',
|
|
859
|
+
]),
|
|
840
860
|
'skills.backend.guideline.backend.magic-numbers-usar-constantes-con-nombres-descriptivos':
|
|
841
861
|
heuristicDetector('typescript.backend.magic-number-literal', [
|
|
842
862
|
'heuristics.ts.backend.magic-number-literal.ast',
|
|
@@ -848,6 +868,7 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
848
868
|
'skills.backend.guideline.backend.custom-exceptions-validationexception-notfoundexception-unauthorizedex':
|
|
849
869
|
heuristicDetector('typescript.backend.generic-error-throw', [
|
|
850
870
|
'heuristics.ts.backend.generic-error-throw.ast',
|
|
871
|
+
'heuristics.ts.backend.raw-throw-expression.ast',
|
|
851
872
|
]),
|
|
852
873
|
'skills.backend.guideline.backend.mocks-en-produccio-n-solo-datos-reales':
|
|
853
874
|
heuristicDetector('typescript.backend.production-mock-or-spy', [
|
|
@@ -861,10 +882,38 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
861
882
|
heuristicDetector('typescript.backend.error-stack-in-http-response', [
|
|
862
883
|
'heuristics.ts.backend.error-stack-in-http-response.ast',
|
|
863
884
|
]),
|
|
885
|
+
'skills.backend.guideline.backend.error-responses-consistentes-statuscode-message-timestamp-path':
|
|
886
|
+
heuristicDetector('typescript.backend.inconsistent-error-response', [
|
|
887
|
+
'heuristics.ts.backend.inconsistent-error-response.ast',
|
|
888
|
+
]),
|
|
889
|
+
'skills.backend.guideline.backend.http-status-codes-apropiados-200-201-400-401-403-404-500':
|
|
890
|
+
heuristicDetector('typescript.backend.error-payload-success-status', [
|
|
891
|
+
'heuristics.ts.backend.error-payload-success-status.ast',
|
|
892
|
+
]),
|
|
893
|
+
'skills.backend.guideline.backend.http-status-codes-sema-ntica-correcta':
|
|
894
|
+
heuristicDetector('typescript.backend.error-payload-success-status', [
|
|
895
|
+
'heuristics.ts.backend.error-payload-success-status.ast',
|
|
896
|
+
]),
|
|
897
|
+
'skills.backend.guideline.backend.retornar-dtos-no-exponer-entidades-directamente':
|
|
898
|
+
heuristicDetector('typescript.backend.controller-entity-response', [
|
|
899
|
+
'heuristics.ts.backend.controller-entity-response.ast',
|
|
900
|
+
]),
|
|
864
901
|
'skills.backend.guideline.backend.anemic-domain-models-entidades-solo-con-getters-setters':
|
|
865
902
|
heuristicDetector('typescript.backend.anemic-domain-model', [
|
|
866
903
|
'heuristics.ts.backend.anemic-domain-model.ast',
|
|
867
904
|
]),
|
|
905
|
+
'skills.backend.guideline.backend.cors-configurado-solo-ori-genes-permitidos':
|
|
906
|
+
heuristicDetector('typescript.backend.permissive-cors', [
|
|
907
|
+
'heuristics.ts.backend.permissive-cors.ast',
|
|
908
|
+
]),
|
|
909
|
+
'skills.backend.guideline.backend.cors-configurar-ori-genes-permitidos':
|
|
910
|
+
heuristicDetector('typescript.backend.permissive-cors', [
|
|
911
|
+
'heuristics.ts.backend.permissive-cors.ast',
|
|
912
|
+
]),
|
|
913
|
+
'skills.backend.guideline.backend.enums-para-valores-fijos-orderstatus-paymentmethod-incidenttype':
|
|
914
|
+
heuristicDetector('typescript.backend.string-literal-union-enum', [
|
|
915
|
+
'heuristics.ts.backend.string-literal-union-enum.ast',
|
|
916
|
+
]),
|
|
868
917
|
'skills.backend.guideline.backend.password-hashing-bcrypt-con-salt-rounds-10':
|
|
869
918
|
heuristicDetector('typescript.bcrypt-weak-salt-rounds', [
|
|
870
919
|
'heuristics.ts.bcrypt-weak-salt-rounds.ast',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.286",
|
|
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": {
|