thailint 0.11.0__tar.gz → 0.13.0__tar.gz

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 (201) hide show
  1. thailint-0.13.0/PKG-INFO +184 -0
  2. thailint-0.13.0/README.md +148 -0
  3. {thailint-0.11.0 → thailint-0.13.0}/pyproject.toml +1 -1
  4. {thailint-0.11.0 → thailint-0.13.0}/src/analyzers/__init__.py +4 -3
  5. thailint-0.13.0/src/analyzers/ast_utils.py +54 -0
  6. {thailint-0.11.0 → thailint-0.13.0}/src/analyzers/typescript_base.py +4 -0
  7. {thailint-0.11.0 → thailint-0.13.0}/src/cli/__init__.py +3 -0
  8. {thailint-0.11.0 → thailint-0.13.0}/src/cli/config.py +12 -12
  9. thailint-0.13.0/src/cli/config_merge.py +241 -0
  10. {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/__init__.py +3 -0
  11. {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/code_patterns.py +113 -5
  12. {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/code_smells.py +118 -7
  13. {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/documentation.py +3 -0
  14. {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/structure.py +3 -0
  15. {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/structure_quality.py +3 -0
  16. {thailint-0.11.0 → thailint-0.13.0}/src/cli/utils.py +29 -9
  17. {thailint-0.11.0 → thailint-0.13.0}/src/cli_main.py +3 -0
  18. {thailint-0.11.0 → thailint-0.13.0}/src/config.py +2 -1
  19. {thailint-0.11.0 → thailint-0.13.0}/src/core/base.py +3 -2
  20. {thailint-0.11.0 → thailint-0.13.0}/src/core/cli_utils.py +3 -1
  21. {thailint-0.11.0 → thailint-0.13.0}/src/core/config_parser.py +5 -2
  22. thailint-0.13.0/src/core/constants.py +54 -0
  23. {thailint-0.11.0 → thailint-0.13.0}/src/core/linter_utils.py +4 -0
  24. {thailint-0.11.0 → thailint-0.13.0}/src/core/rule_discovery.py +5 -1
  25. {thailint-0.11.0 → thailint-0.13.0}/src/core/violation_builder.py +3 -0
  26. thailint-0.13.0/src/linter_config/directive_markers.py +109 -0
  27. thailint-0.13.0/src/linter_config/ignore.py +333 -0
  28. thailint-0.13.0/src/linter_config/pattern_utils.py +65 -0
  29. thailint-0.13.0/src/linter_config/rule_matcher.py +89 -0
  30. thailint-0.13.0/src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  31. thailint-0.13.0/src/linters/collection_pipeline/ast_utils.py +40 -0
  32. {thailint-0.11.0 → thailint-0.13.0}/src/linters/collection_pipeline/config.py +12 -0
  33. {thailint-0.11.0 → thailint-0.13.0}/src/linters/collection_pipeline/continue_analyzer.py +2 -8
  34. thailint-0.13.0/src/linters/collection_pipeline/detector.py +360 -0
  35. thailint-0.13.0/src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  36. {thailint-0.11.0 → thailint-0.13.0}/src/linters/collection_pipeline/linter.py +18 -35
  37. thailint-0.13.0/src/linters/collection_pipeline/suggestion_builder.py +130 -0
  38. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/base_token_analyzer.py +16 -9
  39. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/block_filter.py +7 -4
  40. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/cache.py +7 -2
  41. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/config.py +7 -1
  42. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/constant_matcher.py +34 -25
  43. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/file_analyzer.py +4 -2
  44. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/inline_ignore.py +7 -16
  45. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/linter.py +48 -25
  46. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/python_analyzer.py +18 -10
  47. thailint-0.13.0/src/linters/dry/python_constant_extractor.py +100 -0
  48. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/single_statement_detector.py +14 -12
  49. thailint-0.13.0/src/linters/dry/token_hasher.py +173 -0
  50. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/typescript_analyzer.py +11 -6
  51. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/typescript_constant_extractor.py +4 -0
  52. thailint-0.13.0/src/linters/dry/typescript_statement_detector.py +255 -0
  53. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/typescript_value_extractor.py +3 -0
  54. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/violation_filter.py +1 -4
  55. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/violation_generator.py +1 -4
  56. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/atemporal_detector.py +4 -0
  57. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/base_parser.py +4 -0
  58. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/bash_parser.py +4 -0
  59. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/field_validator.py +5 -8
  60. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/linter.py +19 -12
  61. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/markdown_parser.py +6 -0
  62. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/config_loader.py +3 -1
  63. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/linter.py +22 -8
  64. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/pattern_matcher.py +21 -4
  65. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/pattern_validator.py +21 -7
  66. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/rule_checker.py +2 -2
  67. thailint-0.13.0/src/linters/lazy_ignores/__init__.py +43 -0
  68. thailint-0.13.0/src/linters/lazy_ignores/config.py +66 -0
  69. thailint-0.13.0/src/linters/lazy_ignores/directive_utils.py +121 -0
  70. thailint-0.13.0/src/linters/lazy_ignores/header_parser.py +177 -0
  71. thailint-0.13.0/src/linters/lazy_ignores/linter.py +158 -0
  72. thailint-0.13.0/src/linters/lazy_ignores/matcher.py +135 -0
  73. thailint-0.13.0/src/linters/lazy_ignores/python_analyzer.py +201 -0
  74. thailint-0.13.0/src/linters/lazy_ignores/rule_id_utils.py +180 -0
  75. thailint-0.13.0/src/linters/lazy_ignores/skip_detector.py +298 -0
  76. thailint-0.13.0/src/linters/lazy_ignores/types.py +67 -0
  77. thailint-0.13.0/src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  78. thailint-0.13.0/src/linters/lazy_ignores/violation_builder.py +131 -0
  79. thailint-0.13.0/src/linters/lbyl/__init__.py +29 -0
  80. thailint-0.13.0/src/linters/lbyl/config.py +63 -0
  81. thailint-0.13.0/src/linters/lbyl/pattern_detectors/__init__.py +25 -0
  82. thailint-0.13.0/src/linters/lbyl/pattern_detectors/base.py +46 -0
  83. thailint-0.13.0/src/linters/magic_numbers/context_analyzer.py +249 -0
  84. {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/linter.py +20 -15
  85. {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/python_analyzer.py +4 -16
  86. {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/typescript_analyzer.py +9 -16
  87. {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/config.py +4 -0
  88. {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/linter.py +5 -4
  89. {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/python_analyzer.py +5 -4
  90. {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/violation_builder.py +3 -0
  91. {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/typescript_analyzer.py +6 -12
  92. {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/typescript_function_extractor.py +0 -4
  93. {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/linter.py +6 -4
  94. thailint-0.13.0/src/linters/print_statements/python_analyzer.py +153 -0
  95. {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/typescript_analyzer.py +6 -15
  96. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/heuristics.py +4 -4
  97. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/linter.py +12 -12
  98. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/violation_builder.py +0 -4
  99. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stateless_class/linter.py +30 -36
  100. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stateless_class/python_analyzer.py +11 -20
  101. thailint-0.13.0/src/linters/stringly_typed/__init__.py +36 -0
  102. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/config.py +32 -8
  103. thailint-0.13.0/src/linters/stringly_typed/context_filter.py +451 -0
  104. thailint-0.13.0/src/linters/stringly_typed/function_call_violation_builder.py +135 -0
  105. thailint-0.13.0/src/linters/stringly_typed/ignore_checker.py +102 -0
  106. thailint-0.13.0/src/linters/stringly_typed/ignore_utils.py +51 -0
  107. thailint-0.13.0/src/linters/stringly_typed/linter.py +376 -0
  108. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/__init__.py +9 -5
  109. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/analyzer.py +159 -9
  110. thailint-0.13.0/src/linters/stringly_typed/python/call_tracker.py +175 -0
  111. thailint-0.13.0/src/linters/stringly_typed/python/comparison_tracker.py +257 -0
  112. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/condition_extractor.py +3 -0
  113. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/conditional_detector.py +4 -1
  114. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/match_analyzer.py +8 -2
  115. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/validation_detector.py +3 -0
  116. thailint-0.13.0/src/linters/stringly_typed/storage.py +630 -0
  117. thailint-0.13.0/src/linters/stringly_typed/storage_initializer.py +45 -0
  118. thailint-0.13.0/src/linters/stringly_typed/typescript/__init__.py +28 -0
  119. thailint-0.13.0/src/linters/stringly_typed/typescript/analyzer.py +157 -0
  120. thailint-0.13.0/src/linters/stringly_typed/typescript/call_tracker.py +335 -0
  121. thailint-0.13.0/src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
  122. thailint-0.13.0/src/linters/stringly_typed/violation_generator.py +405 -0
  123. {thailint-0.11.0 → thailint-0.13.0}/src/orchestrator/core.py +13 -4
  124. thailint-0.13.0/src/templates/thailint_config_template.yaml +324 -0
  125. {thailint-0.11.0 → thailint-0.13.0}/src/utils/project_root.py +3 -0
  126. thailint-0.11.0/PKG-INFO +0 -1661
  127. thailint-0.11.0/README.md +0 -1625
  128. thailint-0.11.0/src/linter_config/ignore.py +0 -491
  129. thailint-0.11.0/src/linters/collection_pipeline/detector.py +0 -130
  130. thailint-0.11.0/src/linters/collection_pipeline/suggestion_builder.py +0 -63
  131. thailint-0.11.0/src/linters/dry/python_constant_extractor.py +0 -101
  132. thailint-0.11.0/src/linters/dry/token_hasher.py +0 -173
  133. thailint-0.11.0/src/linters/dry/typescript_statement_detector.py +0 -255
  134. thailint-0.11.0/src/linters/magic_numbers/context_analyzer.py +0 -251
  135. thailint-0.11.0/src/linters/print_statements/python_analyzer.py +0 -149
  136. thailint-0.11.0/src/linters/stringly_typed/__init__.py +0 -23
  137. thailint-0.11.0/src/templates/thailint_config_template.yaml +0 -158
  138. {thailint-0.11.0 → thailint-0.13.0}/CHANGELOG.md +0 -0
  139. {thailint-0.11.0 → thailint-0.13.0}/LICENSE +0 -0
  140. {thailint-0.11.0 → thailint-0.13.0}/src/__init__.py +0 -0
  141. {thailint-0.11.0 → thailint-0.13.0}/src/api.py +0 -0
  142. {thailint-0.11.0 → thailint-0.13.0}/src/cli/__main__.py +0 -0
  143. {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/shared.py +0 -0
  144. {thailint-0.11.0 → thailint-0.13.0}/src/cli/main.py +0 -0
  145. {thailint-0.11.0 → thailint-0.13.0}/src/core/__init__.py +0 -0
  146. {thailint-0.11.0 → thailint-0.13.0}/src/core/registry.py +0 -0
  147. {thailint-0.11.0 → thailint-0.13.0}/src/core/types.py +0 -0
  148. {thailint-0.11.0 → thailint-0.13.0}/src/core/violation_utils.py +0 -0
  149. {thailint-0.11.0 → thailint-0.13.0}/src/formatters/__init__.py +0 -0
  150. {thailint-0.11.0 → thailint-0.13.0}/src/formatters/sarif.py +0 -0
  151. {thailint-0.11.0 → thailint-0.13.0}/src/linter_config/__init__.py +0 -0
  152. {thailint-0.11.0 → thailint-0.13.0}/src/linter_config/loader.py +0 -0
  153. {thailint-0.11.0 → thailint-0.13.0}/src/linters/__init__.py +0 -0
  154. {thailint-0.11.0 → thailint-0.13.0}/src/linters/collection_pipeline/__init__.py +0 -0
  155. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/__init__.py +0 -0
  156. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/block_grouper.py +0 -0
  157. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/cache_query.py +0 -0
  158. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/config_loader.py +0 -0
  159. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/constant.py +0 -0
  160. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/constant_violation_builder.py +0 -0
  161. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/deduplicator.py +0 -0
  162. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/duplicate_storage.py +0 -0
  163. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/storage_initializer.py +0 -0
  164. {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/violation_builder.py +0 -0
  165. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/__init__.py +0 -0
  166. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/config.py +0 -0
  167. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/css_parser.py +0 -0
  168. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/python_parser.py +0 -0
  169. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/typescript_parser.py +0 -0
  170. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/violation_builder.py +0 -0
  171. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/__init__.py +0 -0
  172. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/directory_matcher.py +0 -0
  173. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/path_resolver.py +0 -0
  174. {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/violation_factory.py +0 -0
  175. {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/__init__.py +0 -0
  176. {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/config.py +0 -0
  177. {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/typescript_ignore_checker.py +0 -0
  178. {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/violation_builder.py +0 -0
  179. {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/__init__.py +0 -0
  180. {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/__init__.py +0 -0
  181. {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/config.py +0 -0
  182. {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/linter.py +0 -0
  183. {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/python_analyzer.py +0 -0
  184. {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/violation_builder.py +0 -0
  185. {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/__init__.py +0 -0
  186. {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/config.py +0 -0
  187. {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/violation_builder.py +0 -0
  188. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/__init__.py +0 -0
  189. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/class_analyzer.py +0 -0
  190. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/config.py +0 -0
  191. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/metrics_evaluator.py +0 -0
  192. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/python_analyzer.py +0 -0
  193. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/typescript_analyzer.py +0 -0
  194. {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/typescript_metrics_calculator.py +0 -0
  195. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stateless_class/__init__.py +0 -0
  196. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stateless_class/config.py +0 -0
  197. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/constants.py +0 -0
  198. {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/variable_extractor.py +0 -0
  199. {thailint-0.11.0 → thailint-0.13.0}/src/orchestrator/__init__.py +0 -0
  200. {thailint-0.11.0 → thailint-0.13.0}/src/orchestrator/language_detector.py +0 -0
  201. {thailint-0.11.0 → thailint-0.13.0}/src/utils/__init__.py +0 -0
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: thailint
3
+ Version: 0.13.0
4
+ Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python
8
+ Author: Steve Jackson
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Classifier: Topic :: Utilities
25
+ Classifier: Typing :: Typed
26
+ Requires-Dist: click (>=8.1.0,<9.0.0)
27
+ Requires-Dist: pyprojroot (>=0.3.0,<0.4.0)
28
+ Requires-Dist: pyyaml (>=6.0,<7.0)
29
+ Requires-Dist: tree-sitter (>=0.25.2,<0.26.0)
30
+ Requires-Dist: tree-sitter-typescript (>=0.23.2,<0.24.0)
31
+ Project-URL: Documentation, https://thai-lint.readthedocs.io/
32
+ Project-URL: Homepage, https://github.com/be-wise-be-kind/thai-lint
33
+ Project-URL: Repository, https://github.com/be-wise-be-kind/thai-lint
34
+ Description-Content-Type: text/markdown
35
+
36
+ # thai-lint
37
+
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
40
+ [![PyPI](https://img.shields.io/pypi/v/thai-lint)](https://pypi.org/project/thai-lint/)
41
+ [![Documentation](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/)
42
+
43
+ **The AI Linter** - Catch the mistakes AI coding assistants keep making.
44
+
45
+ thailint detects anti-patterns that AI tools frequently introduce: duplicate code, excessive nesting, magic numbers, SRP violations, and more. It works across Python, TypeScript, and JavaScript with unified rules - filling gaps that existing linters miss.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install thai-lint
51
+ ```
52
+
53
+ Or with Docker:
54
+ ```bash
55
+ docker run --rm -v $(pwd):/data washad/thailint:latest --help
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```bash
61
+ # Generate a config file (optional)
62
+ thailint init-config
63
+
64
+ # Run any linter
65
+ thailint dry src/
66
+ ```
67
+
68
+ That's it. See violations, fix them, ship better code.
69
+
70
+ ## Available Linters
71
+
72
+ | Linter | What It Catches | Command | Docs |
73
+ |--------|-----------------|---------|------|
74
+ | **DRY** | Duplicate code across files | `thailint dry src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/dry-linter/) |
75
+ | **Nesting** | Deeply nested if/for/while blocks | `thailint nesting src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/nesting-linter/) |
76
+ | **Magic Numbers** | Unnamed numeric literals | `thailint magic-numbers src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/) |
77
+ | **SRP** | Classes doing too much | `thailint srp src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/srp-linter/) |
78
+ | **File Header** | Missing documentation headers | `thailint file-header src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/file-header-linter/) |
79
+ | **Stateless Class** | Classes that should be functions | `thailint stateless-class src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stateless-class-linter/) |
80
+ | **Collection Pipeline** | Loops with embedded filtering | `thailint pipeline src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/collection-pipeline-linter/) |
81
+ | **Method Property** | Methods that should be @property | `thailint method-property src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/method-property-linter/) |
82
+ | **File Placement** | Files in wrong directories | `thailint file-placement src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/) |
83
+ | **Lazy Ignores** | Unjustified linting suppressions | `thailint lazy-ignores src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lazy-ignores-linter/) |
84
+ | **Print Statements** | Debug prints left in code | `thailint print-statements src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/print-statements-linter/) |
85
+ | **Stringly Typed** | Strings that should be enums | `thailint stringly-typed src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stringly-typed-linter/) |
86
+
87
+ ## Configuration
88
+
89
+ Create `.thailint.yaml` in your project root:
90
+
91
+ ```yaml
92
+ dry:
93
+ enabled: true
94
+ min_duplicate_lines: 4
95
+
96
+ nesting:
97
+ enabled: true
98
+ max_nesting_depth: 3
99
+
100
+ magic-numbers:
101
+ enabled: true
102
+ allowed_numbers: [-1, 0, 1, 2, 10, 100]
103
+ ```
104
+
105
+ Or generate one automatically:
106
+ ```bash
107
+ thailint init-config --preset lenient # or: strict, standard
108
+ ```
109
+
110
+ See [Configuration Reference](https://thai-lint.readthedocs.io/en/latest/configuration/) for all options.
111
+
112
+ ## Output Formats
113
+
114
+ ```bash
115
+ # Human-readable (default)
116
+ thailint dry src/
117
+
118
+ # JSON for CI/CD
119
+ thailint dry --format json src/
120
+
121
+ # SARIF for GitHub Code Scanning
122
+ thailint dry --format sarif src/ > results.sarif
123
+ ```
124
+
125
+ ## Ignoring Violations
126
+
127
+ ```python
128
+ # Line-level
129
+ timeout = 3600 # thailint: ignore[magic-numbers]
130
+
131
+ # File-level
132
+ # thailint: ignore-file[dry]
133
+ ```
134
+
135
+ Or in config:
136
+ ```yaml
137
+ dry:
138
+ ignore:
139
+ - "tests/"
140
+ - "**/generated/**"
141
+ ```
142
+
143
+ See [How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/) for all 5 ignore levels.
144
+
145
+ ## CI/CD Integration
146
+
147
+ ```yaml
148
+ # GitHub Actions
149
+ - name: Run thailint
150
+ run: |
151
+ pip install thai-lint
152
+ thailint dry src/
153
+ thailint nesting src/
154
+ ```
155
+
156
+ Exit codes: `0` = success, `1` = violations found, `2` = error.
157
+
158
+ ## Documentation
159
+
160
+ - **[Quick Start Guide](https://thai-lint.readthedocs.io/en/latest/quick-start/)** - Get running in 5 minutes
161
+ - **[Configuration Reference](https://thai-lint.readthedocs.io/en/latest/configuration/)** - All config options
162
+ - **[Troubleshooting](https://thai-lint.readthedocs.io/en/latest/troubleshooting/)** - Common issues
163
+ - **[Full Documentation](https://thai-lint.readthedocs.io/)** - Everything else
164
+
165
+ ## Contributing
166
+
167
+ Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
168
+
169
+ ```bash
170
+ git clone https://github.com/be-wise-be-kind/thai-lint.git
171
+ cd thai-lint
172
+ poetry install
173
+ just test
174
+ ```
175
+
176
+ ## License
177
+
178
+ MIT License - see [LICENSE](LICENSE) for details.
179
+
180
+ ## Support
181
+
182
+ - **Issues**: [github.com/be-wise-be-kind/thai-lint/issues](https://github.com/be-wise-be-kind/thai-lint/issues)
183
+ - **Docs**: [thai-lint.readthedocs.io](https://thai-lint.readthedocs.io/)
184
+
@@ -0,0 +1,148 @@
1
+ # thai-lint
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
5
+ [![PyPI](https://img.shields.io/pypi/v/thai-lint)](https://pypi.org/project/thai-lint/)
6
+ [![Documentation](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/)
7
+
8
+ **The AI Linter** - Catch the mistakes AI coding assistants keep making.
9
+
10
+ thailint detects anti-patterns that AI tools frequently introduce: duplicate code, excessive nesting, magic numbers, SRP violations, and more. It works across Python, TypeScript, and JavaScript with unified rules - filling gaps that existing linters miss.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pip install thai-lint
16
+ ```
17
+
18
+ Or with Docker:
19
+ ```bash
20
+ docker run --rm -v $(pwd):/data washad/thailint:latest --help
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ # Generate a config file (optional)
27
+ thailint init-config
28
+
29
+ # Run any linter
30
+ thailint dry src/
31
+ ```
32
+
33
+ That's it. See violations, fix them, ship better code.
34
+
35
+ ## Available Linters
36
+
37
+ | Linter | What It Catches | Command | Docs |
38
+ |--------|-----------------|---------|------|
39
+ | **DRY** | Duplicate code across files | `thailint dry src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/dry-linter/) |
40
+ | **Nesting** | Deeply nested if/for/while blocks | `thailint nesting src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/nesting-linter/) |
41
+ | **Magic Numbers** | Unnamed numeric literals | `thailint magic-numbers src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/magic-numbers-linter/) |
42
+ | **SRP** | Classes doing too much | `thailint srp src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/srp-linter/) |
43
+ | **File Header** | Missing documentation headers | `thailint file-header src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/file-header-linter/) |
44
+ | **Stateless Class** | Classes that should be functions | `thailint stateless-class src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stateless-class-linter/) |
45
+ | **Collection Pipeline** | Loops with embedded filtering | `thailint pipeline src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/collection-pipeline-linter/) |
46
+ | **Method Property** | Methods that should be @property | `thailint method-property src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/method-property-linter/) |
47
+ | **File Placement** | Files in wrong directories | `thailint file-placement src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/file-placement-linter/) |
48
+ | **Lazy Ignores** | Unjustified linting suppressions | `thailint lazy-ignores src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/lazy-ignores-linter/) |
49
+ | **Print Statements** | Debug prints left in code | `thailint print-statements src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/print-statements-linter/) |
50
+ | **Stringly Typed** | Strings that should be enums | `thailint stringly-typed src/` | [Guide](https://thai-lint.readthedocs.io/en/latest/stringly-typed-linter/) |
51
+
52
+ ## Configuration
53
+
54
+ Create `.thailint.yaml` in your project root:
55
+
56
+ ```yaml
57
+ dry:
58
+ enabled: true
59
+ min_duplicate_lines: 4
60
+
61
+ nesting:
62
+ enabled: true
63
+ max_nesting_depth: 3
64
+
65
+ magic-numbers:
66
+ enabled: true
67
+ allowed_numbers: [-1, 0, 1, 2, 10, 100]
68
+ ```
69
+
70
+ Or generate one automatically:
71
+ ```bash
72
+ thailint init-config --preset lenient # or: strict, standard
73
+ ```
74
+
75
+ See [Configuration Reference](https://thai-lint.readthedocs.io/en/latest/configuration/) for all options.
76
+
77
+ ## Output Formats
78
+
79
+ ```bash
80
+ # Human-readable (default)
81
+ thailint dry src/
82
+
83
+ # JSON for CI/CD
84
+ thailint dry --format json src/
85
+
86
+ # SARIF for GitHub Code Scanning
87
+ thailint dry --format sarif src/ > results.sarif
88
+ ```
89
+
90
+ ## Ignoring Violations
91
+
92
+ ```python
93
+ # Line-level
94
+ timeout = 3600 # thailint: ignore[magic-numbers]
95
+
96
+ # File-level
97
+ # thailint: ignore-file[dry]
98
+ ```
99
+
100
+ Or in config:
101
+ ```yaml
102
+ dry:
103
+ ignore:
104
+ - "tests/"
105
+ - "**/generated/**"
106
+ ```
107
+
108
+ See [How to Ignore Violations](https://thai-lint.readthedocs.io/en/latest/how-to-ignore-violations/) for all 5 ignore levels.
109
+
110
+ ## CI/CD Integration
111
+
112
+ ```yaml
113
+ # GitHub Actions
114
+ - name: Run thailint
115
+ run: |
116
+ pip install thai-lint
117
+ thailint dry src/
118
+ thailint nesting src/
119
+ ```
120
+
121
+ Exit codes: `0` = success, `1` = violations found, `2` = error.
122
+
123
+ ## Documentation
124
+
125
+ - **[Quick Start Guide](https://thai-lint.readthedocs.io/en/latest/quick-start/)** - Get running in 5 minutes
126
+ - **[Configuration Reference](https://thai-lint.readthedocs.io/en/latest/configuration/)** - All config options
127
+ - **[Troubleshooting](https://thai-lint.readthedocs.io/en/latest/troubleshooting/)** - Common issues
128
+ - **[Full Documentation](https://thai-lint.readthedocs.io/)** - Everything else
129
+
130
+ ## Contributing
131
+
132
+ Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
133
+
134
+ ```bash
135
+ git clone https://github.com/be-wise-be-kind/thai-lint.git
136
+ cd thai-lint
137
+ poetry install
138
+ just test
139
+ ```
140
+
141
+ ## License
142
+
143
+ MIT License - see [LICENSE](LICENSE) for details.
144
+
145
+ ## Support
146
+
147
+ - **Issues**: [github.com/be-wise-be-kind/thai-lint/issues](https://github.com/be-wise-be-kind/thai-lint/issues)
148
+ - **Docs**: [thai-lint.readthedocs.io](https://thai-lint.readthedocs.io/)
@@ -17,7 +17,7 @@ build-backend = "poetry.core.masonry.api"
17
17
 
18
18
  [tool.poetry]
19
19
  name = "thailint"
20
- version = "0.11.0"
20
+ version = "0.13.0"
21
21
  description = "The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages"
22
22
  authors = ["Steve Jackson"]
23
23
  license = "MIT"
@@ -9,15 +9,16 @@ Overview: Package containing base analyzer classes for different programming lan
9
9
  (TypeScriptBaseAnalyzer, etc.) that linter-specific analyzers extend. Centralizes
10
10
  language parsing infrastructure to improve maintainability and consistency.
11
11
 
12
- Dependencies: tree-sitter, language-specific tree-sitter bindings
12
+ Dependencies: tree-sitter, language-specific tree-sitter bindings, ast module
13
13
 
14
- Exports: TypeScriptBaseAnalyzer
14
+ Exports: TypeScriptBaseAnalyzer, build_parent_map
15
15
 
16
16
  Interfaces: Base analyzer classes with parse(), walk_tree(), and extract() methods
17
17
 
18
18
  Implementation: Composition-based design for linter analyzers to use base utilities
19
19
  """
20
20
 
21
+ from .ast_utils import build_parent_map
21
22
  from .typescript_base import TypeScriptBaseAnalyzer
22
23
 
23
- __all__ = ["TypeScriptBaseAnalyzer"]
24
+ __all__ = ["TypeScriptBaseAnalyzer", "build_parent_map"]
@@ -0,0 +1,54 @@
1
+ """
2
+ Purpose: Common Python AST utilities for linter analyzers
3
+
4
+ Scope: Shared AST traversal utilities for Python code analysis
5
+
6
+ Overview: Provides common AST utility functions used across multiple Python linters.
7
+ Centralizes shared patterns like parent map building to eliminate code duplication.
8
+ The build_parent_map function creates a dictionary mapping AST nodes to their parents,
9
+ enabling upward tree traversal for context detection.
10
+
11
+ Dependencies: ast module for AST node types
12
+
13
+ Exports: build_parent_map
14
+
15
+ Interfaces: build_parent_map(tree: ast.AST) -> dict[ast.AST, ast.AST]
16
+
17
+ Implementation: Recursive AST traversal with parent tracking
18
+ """
19
+
20
+ import ast
21
+
22
+
23
+ def build_parent_map(tree: ast.AST) -> dict[ast.AST, ast.AST]:
24
+ """Build a map of AST nodes to their parent nodes.
25
+
26
+ Enables upward tree traversal for context detection (e.g., finding if a node
27
+ is inside a particular block type).
28
+
29
+ Args:
30
+ tree: Root AST node to build map from
31
+
32
+ Returns:
33
+ Dictionary mapping each node to its parent node
34
+ """
35
+ parent_map: dict[ast.AST, ast.AST] = {}
36
+ _build_parent_map_recursive(tree, None, parent_map)
37
+ return parent_map
38
+
39
+
40
+ def _build_parent_map_recursive(
41
+ node: ast.AST, parent: ast.AST | None, parent_map: dict[ast.AST, ast.AST]
42
+ ) -> None:
43
+ """Recursively build parent map.
44
+
45
+ Args:
46
+ node: Current AST node
47
+ parent: Parent of current node
48
+ parent_map: Dictionary to populate
49
+ """
50
+ if parent is not None:
51
+ parent_map[node] = parent
52
+
53
+ for child in ast.iter_child_nodes(node):
54
+ _build_parent_map_recursive(child, node, parent_map)
@@ -18,6 +18,10 @@ Exports: TypeScriptBaseAnalyzer class with parsing and traversal utilities
18
18
  Interfaces: parse_typescript(code), walk_tree(node, node_type), extract_node_text(node)
19
19
 
20
20
  Implementation: Tree-sitter parser singleton, recursive AST traversal, composition pattern
21
+
22
+ Suppressions:
23
+ - type:ignore[assignment]: Tree-sitter TS_PARSER fallback when import fails
24
+ - type:ignore[assignment,misc]: Tree-sitter Node type alias (optional dependency fallback)
21
25
  """
22
26
 
23
27
  from typing import Any
@@ -16,6 +16,9 @@ Exports: cli (main Click command group with all commands registered)
16
16
  Interfaces: Single import point for CLI access via 'from src.cli import cli'
17
17
 
18
18
  Implementation: Imports submodules to trigger command registration via Click decorators
19
+
20
+ Suppressions:
21
+ - F401: Module re-exports required for public API interface
19
22
  """
20
23
 
21
24
  # Import the CLI group from main module
@@ -26,9 +26,11 @@ import sys
26
26
  from pathlib import Path
27
27
 
28
28
  import click
29
+ import yaml
29
30
 
30
31
  from src.config import ConfigError, save_config, validate_config
31
32
 
33
+ from .config_merge import perform_merge
32
34
  from .main import cli
33
35
 
34
36
  # Configure module logger
@@ -98,8 +100,6 @@ def _format_config_json(cfg: dict) -> None:
98
100
 
99
101
  def _format_config_yaml(cfg: dict) -> None:
100
102
  """Format configuration as YAML."""
101
- import yaml
102
-
103
103
  click.echo(yaml.dump(cfg, default_flow_style=False, sort_keys=False))
104
104
 
105
105
 
@@ -285,6 +285,9 @@ def init_config(preset: str, non_interactive: bool, force: bool, output: str) ->
285
285
  Creates a richly-commented configuration file with sensible defaults
286
286
  and optional customizations for different strictness levels.
287
287
 
288
+ If a config file already exists, missing linter sections will be added
289
+ without modifying existing settings. Use --force to completely overwrite.
290
+
288
291
  For AI agents, use --non-interactive mode:
289
292
  thailint init-config --non-interactive --preset lenient
290
293
 
@@ -308,7 +311,7 @@ def init_config(preset: str, non_interactive: bool, force: bool, output: str) ->
308
311
  thailint init-config --preset lenient
309
312
 
310
313
  \\b
311
- # Overwrite existing config
314
+ # Overwrite existing config (replaces entire file)
312
315
  thailint init-config --force
313
316
 
314
317
  \\b
@@ -317,19 +320,16 @@ def init_config(preset: str, non_interactive: bool, force: bool, output: str) ->
317
320
  """
318
321
  output_path = Path(output)
319
322
 
320
- # Check if file exists (unless --force)
321
- if output_path.exists() and not force:
322
- click.echo(f"Error: {output} already exists", err=True)
323
- click.echo("", err=True)
324
- click.echo("Use --force to overwrite:", err=True)
325
- click.echo(" thailint init-config --force", err=True)
326
- sys.exit(1)
327
-
328
323
  # Interactive mode: Ask user for preferences
329
324
  if not non_interactive:
330
325
  preset = _run_interactive_preset_selection(preset)
331
326
 
332
- # Generate config based on preset
327
+ # If file exists and not forcing overwrite, merge missing sections
328
+ if output_path.exists() and not force:
329
+ perform_merge(output_path, preset, output, _generate_config_content)
330
+ return
331
+
332
+ # Generate full config based on preset
333
333
  config_content = _generate_config_content(preset)
334
334
 
335
335
  # Write config file