@sun-asterisk/sunlint 1.3.48 → 1.3.49

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.
Files changed (152) hide show
  1. package/core/file-targeting-service.js +148 -15
  2. package/core/init-command.js +118 -70
  3. package/core/project-detector.js +517 -0
  4. package/core/tui-select.js +245 -0
  5. package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
  6. package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
  7. package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
  8. package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
  9. package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
  10. package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
  11. package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
  12. package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
  13. package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
  14. package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
  15. package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
  16. package/package.json +1 -1
  17. package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
  18. package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
  19. package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
  20. package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
  21. package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
  22. package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
  23. package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
  24. package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
  25. package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
  26. package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
  27. package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
  28. package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
  29. package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
  30. package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
  31. package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
  32. package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
  33. package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
  34. package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
  35. package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
  36. package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
  37. package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
  38. package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
  39. package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
  40. package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
  41. package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
  42. package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
  43. package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
  44. package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
  45. package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
  46. package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
  47. package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
  48. package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
  49. package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
  50. package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
  51. package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
  52. package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
  53. package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
  54. package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
  55. package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
  56. package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
  57. package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
  58. package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
  59. package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
  60. package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
  61. package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
  62. package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
  63. package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
  64. package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
  65. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
  66. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
  67. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
  68. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
  69. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
  70. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
  71. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
  72. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
  73. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
  74. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
  75. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
  76. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
  77. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
  78. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
  79. package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
  80. package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
  81. package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
  82. package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
  83. package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
  84. package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
  85. package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
  86. package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
  87. package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
  88. package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
  89. package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
  90. package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
  91. package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
  92. package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
  93. package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
  94. package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
  95. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
  96. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
  97. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
  98. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
  99. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
  100. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
  101. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
  102. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
  103. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
  104. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
  105. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
  106. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
  107. package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
  108. package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
  109. package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
  110. package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
  111. package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
  112. package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
  113. package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
  114. package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
  115. package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
  116. package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
  117. package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
  118. package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
  119. package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
  120. package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
  121. package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
  122. package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
  123. package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
  124. package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
  125. package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
  126. package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
  127. package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
  128. package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
  129. package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
  130. package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
  131. package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
  132. package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
  133. package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
  134. package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
  135. package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
  136. package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
  137. package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
  138. package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
  139. package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
  140. package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
  141. package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
  142. package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
  143. package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
  144. package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
  145. package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
  146. package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
  147. package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
  148. package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
  149. package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
  150. package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
  151. package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
  152. package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: Do Not Ignore Superclass Logic
3
+ impact: HIGH
4
+ impactDescription: ensures proper inheritance behavior
5
+ tags: inheritance, override, superclass, oop, quality
6
+ ---
7
+
8
+ ## Do Not Ignore Superclass Logic
9
+
10
+ When overriding methods, ensure superclass behavior is preserved unless explicitly intended otherwise. This is especially important in Flutter for `initState`, `dispose`, `build`, and lifecycle methods.
11
+
12
+ **Incorrect (ignoring superclass):**
13
+
14
+ ```dart
15
+ class BaseRepository {
16
+ Future<void> save(Entity entity) async {
17
+ validate(entity);
18
+ await beforeSave(entity);
19
+ await database.insert(entity.toMap());
20
+ await afterSave(entity);
21
+ }
22
+ }
23
+
24
+ class UserRepository extends BaseRepository {
25
+ @override
26
+ Future<void> save(User user) async {
27
+ // Completely ignores validation, hooks, etc.
28
+ await database.insert(user.toMap());
29
+ }
30
+ }
31
+ ```
32
+
33
+ **Correct (calling super):**
34
+
35
+ ```dart
36
+ class UserRepository extends BaseRepository {
37
+ @override
38
+ Future<void> save(User user) async {
39
+ // Add user-specific preprocessing
40
+ user = user.copyWith(updatedAt: DateTime.now());
41
+
42
+ // Call superclass implementation
43
+ await super.save(user);
44
+
45
+ // Add user-specific postprocessing
46
+ await updateSearchIndex(user);
47
+ }
48
+ }
49
+ ```
50
+
51
+ **Flutter lifecycle - always call super at correct position:**
52
+
53
+ ```dart
54
+ // initState: super FIRST
55
+ @override
56
+ void initState() {
57
+ super.initState(); // Must be first
58
+ _controller = AnimationController(vsync: this);
59
+ }
60
+
61
+ // dispose: super LAST
62
+ @override
63
+ void dispose() {
64
+ _controller.dispose(); // Clean up first
65
+ super.dispose(); // Must be last
66
+ }
67
+
68
+ // didChangeDependencies: super FIRST
69
+ @override
70
+ void didChangeDependencies() {
71
+ super.didChangeDependencies();
72
+ _theme = Theme.of(context);
73
+ }
74
+ ```
75
+
76
+ **When to intentionally skip super:**
77
+
78
+ ```dart
79
+ class UserRepository extends BaseRepository {
80
+ /// Override: users require special serialization,
81
+ /// base validation is not applicable for legacy schema.
82
+ @override
83
+ Future<void> save(User user) async {
84
+ await validateUser(user); // Custom validation replaces base
85
+ // Intentionally not calling super.save()
86
+ await userTable.upsert(user.toLegacyMap());
87
+ }
88
+ }
89
+ ```
90
+
91
+ **Tools:** dart_analyzer, Code Review, flutter_lints
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: Do Not Hardcode Configuration
3
+ impact: HIGH
4
+ impactDescription: enables environment-specific deployments
5
+ tags: configuration, environment, deployment, quality
6
+ ---
7
+
8
+ ## Do Not Hardcode Configuration
9
+
10
+ Hardcoded config requires code changes to deploy to different environments.
11
+
12
+ **Incorrect (hardcoded config):**
13
+
14
+ ```dart
15
+ const apiUrl = 'https://api.production.example.com';
16
+ const timeout = 5000;
17
+ const maxFileSize = 10485760;
18
+ const enableAnalytics = true;
19
+ ```
20
+
21
+ **Correct (externalized config via dart-define):**
22
+
23
+ ```dart
24
+ // lib/config/app_config.dart
25
+ class AppConfig {
26
+ AppConfig._();
27
+
28
+ static const String apiUrl = String.fromEnvironment(
29
+ 'API_URL',
30
+ defaultValue: 'https://api.dev.example.com',
31
+ );
32
+
33
+ static const int timeoutMs = int.fromEnvironment(
34
+ 'TIMEOUT_MS',
35
+ defaultValue: 5000,
36
+ );
37
+
38
+ static const int maxFileSizeBytes = int.fromEnvironment(
39
+ 'MAX_FILE_SIZE',
40
+ defaultValue: 10 * 1024 * 1024, // 10MB
41
+ );
42
+
43
+ static const bool enableAnalytics = bool.fromEnvironment(
44
+ 'ENABLE_ANALYTICS',
45
+ defaultValue: false,
46
+ );
47
+ }
48
+
49
+ // Usage
50
+ final client = HttpClient(baseUrl: AppConfig.apiUrl, timeout: Duration(milliseconds: AppConfig.timeoutMs));
51
+ ```
52
+
53
+ **Build with environment config:**
54
+
55
+ ```bash
56
+ # Development
57
+ flutter run --dart-define=API_URL=https://api.dev.example.com --dart-define=ENABLE_ANALYTICS=false
58
+
59
+ # Production
60
+ flutter build apk --dart-define=API_URL=https://api.production.example.com --dart-define=ENABLE_ANALYTICS=true
61
+
62
+ # Using a JSON config file (flutter_launcher_icons approach)
63
+ flutter run --dart-define-from-file=config/dev.json
64
+ flutter build apk --dart-define-from-file=config/prod.json
65
+ ```
66
+
67
+ **Example config files:**
68
+
69
+ ```json
70
+ // config/dev.json
71
+ {
72
+ "API_URL": "https://api.dev.example.com",
73
+ "ENABLE_ANALYTICS": "false",
74
+ "TIMEOUT_MS": "10000"
75
+ }
76
+ ```
77
+
78
+ ```json
79
+ // config/prod.json
80
+ {
81
+ "API_URL": "https://api.production.example.com",
82
+ "ENABLE_ANALYTICS": "true",
83
+ "TIMEOUT_MS": "5000"
84
+ }
85
+ ```
86
+
87
+ **For flavor-based config:**
88
+
89
+ ```dart
90
+ // lib/config/flavors.dart
91
+ enum Flavor { dev, staging, production }
92
+
93
+ class FlavorConfig {
94
+ static Flavor? _flavor;
95
+
96
+ static void initialize(Flavor flavor) {
97
+ _flavor = flavor;
98
+ }
99
+
100
+ static String get apiUrl => switch (_flavor!) {
101
+ Flavor.dev => 'https://api.dev.example.com',
102
+ Flavor.staging => 'https://api.staging.example.com',
103
+ Flavor.production => 'https://api.example.com',
104
+ };
105
+ }
106
+ ```
107
+
108
+ **Tools:** dart-define, envied, flutter_flavor, Code Review
@@ -0,0 +1,149 @@
1
+ # Go Gin Framework — SunLint Agent Guide
2
+
3
+ > Priority directives for AI agents working on Go + Gin projects.
4
+ > Rule files: `.agent/skills/sunlint-code-quality/rules/`
5
+
6
+ ---
7
+
8
+ ## Critical Patterns — Apply Every Time
9
+
10
+ ### Middleware Chain Termination
11
+ - After sending a response that should stop the chain → `c.AbortWithStatusJSON(code, body)` then `return`
12
+ - `c.AbortWithStatusJSON` calls `c.Abort()` internally — don't call both
13
+ - Plain `return` from a handler does NOT stop downstream handlers
14
+ - See: `GN001-abort-after-response.md`
15
+
16
+ ### Request Binding + Validation
17
+ - Every `c.ShouldBindJSON(&req)` → must be followed by `if err != nil { c.AbortWithStatusJSON(400, ...); return }`
18
+ - Declare all validation rules as struct `binding:` tags: `binding:"required,email"`, `binding:"min=1,max=255"`
19
+ - **Never** skip the error check from `ShouldBind*`
20
+ - See: `GN003-bind-error-handling.md`, `GN008-struct-validation-tags.md`
21
+
22
+ ### Context Usage
23
+ - Inside handlers: `ctx := c.Request.Context()` — pass to all downstream calls
24
+ - **Never** `context.Background()` inside a handler
25
+ - **Never** pass `*gin.Context` to a goroutine — copy scalar values first, pass `c.Request.Context()`
26
+ - See: `GN002-request-context.md`, `GN010-context-scope.md`
27
+
28
+ ### Do Not Log Sensitive DataTesting commands rõ ràng trong package.json/Makefile
29
+ Standard conventions (eslint, prettier, gofmt đã config sẵn)
30
+ 4. Test với agents trước khi commit
31
+ Quy trình recommended:
32
+
33
+ Bước 1: Tạo baseline
34
+
35
+ # Chạy agent KHÔNG có AGENTS.md trên vài tasks nhỏ
36
+ # Đo success rate, cost
37
+
38
+ Bước 2: Thêm AGENTS.md, measure lại
39
+
40
+ # So sánh với baseline
41
+ # Nếu không improve hoặc cost tăng quá nhiều → bỏ AGENTS.md
42
+
43
+ Bước 3: Iterate
44
+
45
+ # Nếu quyết định giữ AGENTS.md, hãy giữ nó concise
46
+ # Monitor agent behavior qua time
47
+
48
+ 5. Monitor Context Length
49
+ Context files nên dưới 500 tokens (t
50
+ - Logging middleware logs only: method, path, status, duration, request_id, client IP
51
+ - **Never** log request bodies, `Authorization` header values, or any field named password/token/card
52
+ - See: `GN012-no-log-sensitive.md`
53
+
54
+ ---
55
+
56
+ ## Architecture Patterns
57
+
58
+ ### Handler struct — dependency injection
59
+ Handlers are structs with injected service interfaces. No globals. No `new(Service)` inside handlers.
60
+ ```go
61
+ type OrderHandler struct {
62
+ service OrderService // interface — mockable
63
+ logger *slog.Logger
64
+ }
65
+ func NewOrderHandler(svc OrderService, logger *slog.Logger) *OrderHandler {
66
+ return &OrderHandler{service: svc, logger: logger}
67
+ }
68
+ ```
69
+ See: `GN004-dependency-injection.md`
70
+
71
+ ### Route organisation
72
+ ```go
73
+ r := gin.New()
74
+ r.Use(gin.Recovery(), RequestID(), RequestLogger(logger)) // global
75
+
76
+ public := r.Group("/api/v1")
77
+ { /* unauthenticated routes */ }
78
+
79
+ private := r.Group("/api/v1")
80
+ private.Use(JWTAuth(validator))
81
+ { /* authenticated routes */ }
82
+ ```
83
+ See: `GN005-route-groups-middleware.md`, `GN011-middleware-concerns.md`
84
+
85
+ ### Production setup checklist
86
+ ```go
87
+ gin.SetMode(os.Getenv("GIN_MODE")) // "release" in prod — GN007
88
+ r := gin.New()
89
+ r.Use(CustomRecovery(logger)) // GN009
90
+ r.Use(RequestID()) // GN011
91
+ r.Use(RequestLogger(logger)) // GN012
92
+ // ... register groups (GN005)
93
+ ```
94
+
95
+ ### HTTP Status Codes
96
+ - `POST` success → `c.JSON(http.StatusCreated, obj)` (201)
97
+ - `DELETE` no body → `c.Status(http.StatusNoContent)` (204)
98
+ - Not found → `c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "not found"})` (404)
99
+ - Validation error → `c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})` (400)
100
+ - **Always** use `net/http` constants — never magic integers
101
+ - See: `GN006-http-status-codes.md`
102
+
103
+ ### Cross-cutting concerns
104
+ Auth, rate limiting, CORS, request ID, structured logging → **always middleware**, never inside handlers.
105
+ See: `GN011-middleware-concerns.md`
106
+
107
+ ---
108
+
109
+ ## Run Commands
110
+
111
+ ```bash
112
+ # Development
113
+ go run ./cmd/api/main.go
114
+ GIN_MODE=debug go run ./cmd/api/main.go
115
+
116
+ # Build
117
+ go build -o bin/api ./cmd/api/
118
+
119
+ # Test (always with race detector)
120
+ go test -race ./...
121
+ go test -race -run TestOrderHandler ./internal/handlers/
122
+ go test -cover ./...
123
+
124
+ # Linting
125
+ golangci-lint run ./...
126
+ staticcheck ./...
127
+
128
+ # Env for production
129
+ GIN_MODE=release
130
+ PORT=8080
131
+ ```
132
+
133
+ ---
134
+
135
+ ## What NOT to do (quick reference)
136
+
137
+ | ❌ Wrong | ✅ Correct |
138
+ |---|---|
139
+ | `return` after `c.JSON` in middleware | `c.AbortWithStatusJSON(...)` then `return` |
140
+ | `context.Background()` in handler | `c.Request.Context()` |
141
+ | `go func() { use c.Get(...) }()` | Copy value before goroutine: `val := c.GetString(...)` |
142
+ | `c.ShouldBindJSON(&req)` without err check | `if err := c.ShouldBindJSON(&req); err != nil { abort }` |
143
+ | Manual `if req.Email == ""` validation | `binding:"required,email"` struct tag |
144
+ | `var db *gorm.DB` package global | Inject `db *gorm.DB` as struct field |
145
+ | Auth check inside handler | `JWTAuth()` middleware on route group |
146
+ | `log.Printf("body: %s", body)` | Log method+path+status+duration only |
147
+ | `gin.Default()` in production | `gin.New()` + explicit `Recovery()` + structured logger |
148
+ | `c.JSON(200, gin.H{"error": ...})` | `c.AbortWithStatusJSON(http.StatusBadRequest, ...)` |
149
+ | `GIN_MODE` not set (debug default) | `GIN_MODE=release` in production environment |
@@ -0,0 +1,75 @@
1
+ ---
2
+ title: "GN001 – Call c.Abort() After Terminating in Middleware"
3
+ impact: high
4
+ impactDescription: "Missing c.Abort() causes subsequent middleware handlers and the final route handler to still execute after an early response has been sent, causing duplicate writes and data leaks."
5
+ tags: [go, gin, middleware, correctness]
6
+ ---
7
+
8
+ # GN001 – Call `c.Abort()` After Terminating in Middleware
9
+
10
+ ## Rule
11
+
12
+ Any middleware that sends a response and intends to stop the chain **must** call `c.Abort()` (or `c.AbortWithStatus()` / `c.AbortWithStatusJSON()`) immediately after writing. Never rely on `return` alone.
13
+
14
+ ## Why
15
+
16
+ `c.Next()` executes subsequent handlers in sequence. A plain `return` exits the current handler but Gin still runs the remaining handlers in the chain. `c.Abort()` sets the index past all remaining handlers so the chain stops cleanly.
17
+
18
+ ## Wrong
19
+
20
+ ```go
21
+ func AuthMiddleware() gin.HandlerFunc {
22
+ return func(c *gin.Context) {
23
+ token := c.GetHeader("Authorization")
24
+ if token == "" {
25
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
26
+ return // ❌ returns from this func but downstream handlers still run
27
+ }
28
+ c.Next()
29
+ }
30
+ }
31
+
32
+ func RateLimitMiddleware() gin.HandlerFunc {
33
+ return func(c *gin.Context) {
34
+ if isRateLimited(c.ClientIP()) {
35
+ c.JSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
36
+ // ❌ forgot both return and Abort — next handler fires and writes again
37
+ }
38
+ c.Next()
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Correct
44
+
45
+ ```go
46
+ func AuthMiddleware() gin.HandlerFunc {
47
+ return func(c *gin.Context) {
48
+ token := c.GetHeader("Authorization")
49
+ if token == "" {
50
+ c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
51
+ // c.Abort() is called inside AbortWithStatusJSON — no c.Next() needed
52
+ return
53
+ }
54
+ // validation passes — continue chain
55
+ c.Set("user_id", parsedUserID)
56
+ c.Next()
57
+ }
58
+ }
59
+
60
+ func RateLimitMiddleware() gin.HandlerFunc {
61
+ return func(c *gin.Context) {
62
+ if isRateLimited(c.ClientIP()) {
63
+ c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
64
+ return
65
+ }
66
+ c.Next()
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Notes
72
+
73
+ - `c.AbortWithStatus(code)` and `c.AbortWithStatusJSON(code, obj)` both call `c.Abort()` internally — you don't need to call `c.Abort()` separately when using these.
74
+ - After `c.Abort()`, code after the call in the same function still runs — use `return` to exit the function body.
75
+ - Verify with unit tests: register middleware + handler, send a bad request, assert the handler body was NOT executed.
@@ -0,0 +1,64 @@
1
+ ---
2
+ title: "GN002 – Use c.Request.Context(), Not context.Background()"
3
+ impact: medium
4
+ impactDescription: "context.Background() ignores client disconnects and request deadlines; c.Request.Context() propagates cancellation automatically."
5
+ tags: [go, gin, context, correctness]
6
+ ---
7
+
8
+ # GN002 – Use `c.Request.Context()` Not `context.Background()`
9
+
10
+ ## Rule
11
+
12
+ When passing a context to database calls, external HTTP requests, or any `ctx context.Context` parameter inside a Gin handler, always use `c.Request.Context()`. Never create a fresh `context.Background()` inside a handler.
13
+
14
+ ## Why
15
+
16
+ `c.Request.Context()` is derived from the HTTP request and carries the client's cancellation signal. If the client disconnects mid-request, the context is cancelled and all downstream work (DB queries, gRPC calls) is cancelled too — preventing wasted compute. `context.Background()` is never cancelled and leaks work after the client is gone.
17
+
18
+ ## Wrong
19
+
20
+ ```go
21
+ func (h *OrderHandler) Create(c *gin.Context) {
22
+ var req CreateOrderRequest
23
+ if err := c.ShouldBindJSON(&req); err != nil {
24
+ c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
25
+ return
26
+ }
27
+
28
+ // ❌ context.Background() — ignores client disconnect and server deadlines
29
+ order, err := h.service.CreateOrder(context.Background(), req)
30
+ if err != nil {
31
+ c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
32
+ return
33
+ }
34
+ c.JSON(http.StatusCreated, order)
35
+ }
36
+ ```
37
+
38
+ ## Correct
39
+
40
+ ```go
41
+ func (h *OrderHandler) Create(c *gin.Context) {
42
+ var req CreateOrderRequest
43
+ if err := c.ShouldBindJSON(&req); err != nil {
44
+ c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
45
+ return
46
+ }
47
+
48
+ // ✅ propagates request lifetime, deadline, and cancellation
49
+ ctx := c.Request.Context()
50
+ order, err := h.service.CreateOrder(ctx, req)
51
+ if err != nil {
52
+ c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
53
+ return
54
+ }
55
+ c.JSON(http.StatusCreated, order)
56
+ }
57
+ ```
58
+
59
+ ## Notes
60
+
61
+ - Extract the context once: `ctx := c.Request.Context()` at the top of the handler body.
62
+ - Do **not** store this context in a struct field — contexts must flow through function arguments.
63
+ - If you need a timeout shorter than the request's own deadline: `ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second); defer cancel()`.
64
+ - See GN010 for the rule about not storing Gin context in goroutines.
@@ -0,0 +1,70 @@
1
+ ---
2
+ title: "GN003 – Always Handle ShouldBindJSON / ShouldBind Errors"
3
+ impact: high
4
+ impactDescription: "Ignoring ShouldBindJSON errors means malformed or missing fields are silently treated as zero values, causing corrupted data writes and bypassed validation."
5
+ tags: [go, gin, validation, correctness]
6
+ ---
7
+
8
+ # GN003 – Always Handle `ShouldBindJSON` / `ShouldBind` Errors
9
+
10
+ ## Rule
11
+
12
+ Every call to `c.ShouldBindJSON`, `c.ShouldBind`, `c.ShouldBindQuery`, or `c.ShouldBindUri` must be followed by an error check. If the error is non-nil, abort immediately with a `400 Bad Request` response.
13
+
14
+ ## Why
15
+
16
+ `ShouldBind*` populates the target struct but returns an error for malformed JSON, missing required fields (enforced by `binding:"required"` tags), or type mismatches. Ignoring the error means the handler continues with a partially or incorrectly populated struct, silently writing bad data.
17
+
18
+ ## Wrong
19
+
20
+ ```go
21
+ func (h *UserHandler) Create(c *gin.Context) {
22
+ var req CreateUserRequest
23
+ c.ShouldBindJSON(&req) // ❌ error ignored — continues with zero-value struct fields
24
+
25
+ user, err := h.service.CreateUser(c.Request.Context(), req)
26
+ // ...
27
+ }
28
+
29
+ // Worse: using BindJSON (panics on error in some versions, logs but doesn't stop)
30
+ func (h *UserHandler) Update(c *gin.Context) {
31
+ var req UpdateUserRequest
32
+ c.BindJSON(&req) // ❌ BindJSON writes 400 header but handler still continues
33
+ h.service.UpdateUser(c.Request.Context(), req)
34
+ }
35
+ ```
36
+
37
+ ## Correct
38
+
39
+ ```go
40
+ type CreateUserRequest struct {
41
+ Name string `json:"name" binding:"required,min=2,max=100"`
42
+ Email string `json:"email" binding:"required,email"`
43
+ Age int `json:"age" binding:"required,min=18"`
44
+ }
45
+
46
+ func (h *UserHandler) Create(c *gin.Context) {
47
+ var req CreateUserRequest
48
+ if err := c.ShouldBindJSON(&req); err != nil {
49
+ c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
50
+ "error": "invalid request",
51
+ "details": err.Error(),
52
+ })
53
+ return
54
+ }
55
+
56
+ user, err := h.service.CreateUser(c.Request.Context(), req)
57
+ if err != nil {
58
+ c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "creation failed"})
59
+ return
60
+ }
61
+ c.JSON(http.StatusCreated, user)
62
+ }
63
+ ```
64
+
65
+ ## Notes
66
+
67
+ - Prefer `ShouldBind*` over `Bind*` — the `Bind*` family writes a `400` header but doesn't abort the handler, making code flow confusing.
68
+ - Use struct tags: `binding:"required"`, `binding:"email"`, `binding:"min=1,max=255"` to shift validation into the binding layer.
69
+ - For detailed error messages, type-assert the error to `validator.ValidationErrors` and format field names cleanly.
70
+ - See GN008 for struct validation tag conventions.
@@ -0,0 +1,78 @@
1
+ ---
2
+ title: "GN004 – Use Dependency Injection, Not Global Variables"
3
+ impact: high
4
+ impactDescription: "Global database connections and service instances in package-level vars are untestable, race-prone, and make initialization order fragile."
5
+ tags: [go, gin, architecture, testing]
6
+ ---
7
+
8
+ # GN004 – Use Dependency Injection, Not Global Variables
9
+
10
+ ## Rule
11
+
12
+ Database connections, service instances, configuration objects, and HTTP clients must be injected as struct fields into handlers. Never declare them as `var db *gorm.DB` at package level and access them from handler functions.
13
+
14
+ ## Why
15
+
16
+ Global state:
17
+ - Cannot be replaced with a mock in unit tests.
18
+ - Initialization order is implicit — the global may be `nil` if setup hasn't run.
19
+ - Race conditions when tests share globals.
20
+
21
+ Dependency injection via struct fields makes dependencies explicit, swappable, and testable.
22
+
23
+ ## Wrong
24
+
25
+ ```go
26
+ // db/db.go — package-level global
27
+ var DB *gorm.DB
28
+
29
+ func Init() {
30
+ DB, _ = gorm.Open(...)
31
+ }
32
+
33
+ // handlers/user.go — accesses global directly
34
+ func GetUser(c *gin.Context) {
35
+ var user User
36
+ db.DB.First(&user, c.Param("id")) // ❌ depends on global initialization order
37
+ c.JSON(200, user)
38
+ }
39
+ ```
40
+
41
+ ## Correct
42
+
43
+ ```go
44
+ // handlers/user_handler.go
45
+ type UserHandler struct {
46
+ db *gorm.DB // injected
47
+ service UserService // interface — swappable in tests
48
+ logger *slog.Logger
49
+ }
50
+
51
+ func NewUserHandler(db *gorm.DB, svc UserService, logger *slog.Logger) *UserHandler {
52
+ return &UserHandler{db: db, service: svc, logger: logger}
53
+ }
54
+
55
+ func (h *UserHandler) GetUser(c *gin.Context) {
56
+ ctx := c.Request.Context()
57
+ user, err := h.service.FindUserByID(ctx, c.Param("id"))
58
+ if err != nil {
59
+ c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "not found"})
60
+ return
61
+ }
62
+ c.JSON(http.StatusOK, user)
63
+ }
64
+
65
+ // main.go — wiring
66
+ db := database.Connect(cfg.DSN)
67
+ userSvc := services.NewUserService(db)
68
+ userHandler := handlers.NewUserHandler(db, userSvc, logger)
69
+
70
+ r := gin.New()
71
+ r.GET("/users/:id", userHandler.GetUser)
72
+ ```
73
+
74
+ ## Notes
75
+
76
+ - Define service dependencies as Go interfaces so handlers can be unit-tested with mocks without a real DB.
77
+ - Use a DI framework (e.g., `google/wire`, `samber/do`) for large projects to automate wiring.
78
+ - Configuration (`Config` structs) should be loaded once in `main` and injected — never read from `os.Getenv` directly inside handlers.