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.
- thailint-0.13.0/PKG-INFO +184 -0
- thailint-0.13.0/README.md +148 -0
- {thailint-0.11.0 → thailint-0.13.0}/pyproject.toml +1 -1
- {thailint-0.11.0 → thailint-0.13.0}/src/analyzers/__init__.py +4 -3
- thailint-0.13.0/src/analyzers/ast_utils.py +54 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/analyzers/typescript_base.py +4 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/__init__.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/config.py +12 -12
- thailint-0.13.0/src/cli/config_merge.py +241 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/__init__.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/code_patterns.py +113 -5
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/code_smells.py +118 -7
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/documentation.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/structure.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/structure_quality.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/utils.py +29 -9
- {thailint-0.11.0 → thailint-0.13.0}/src/cli_main.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/config.py +2 -1
- {thailint-0.11.0 → thailint-0.13.0}/src/core/base.py +3 -2
- {thailint-0.11.0 → thailint-0.13.0}/src/core/cli_utils.py +3 -1
- {thailint-0.11.0 → thailint-0.13.0}/src/core/config_parser.py +5 -2
- thailint-0.13.0/src/core/constants.py +54 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/core/linter_utils.py +4 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/core/rule_discovery.py +5 -1
- {thailint-0.11.0 → thailint-0.13.0}/src/core/violation_builder.py +3 -0
- thailint-0.13.0/src/linter_config/directive_markers.py +109 -0
- thailint-0.13.0/src/linter_config/ignore.py +333 -0
- thailint-0.13.0/src/linter_config/pattern_utils.py +65 -0
- thailint-0.13.0/src/linter_config/rule_matcher.py +89 -0
- thailint-0.13.0/src/linters/collection_pipeline/any_all_analyzer.py +281 -0
- thailint-0.13.0/src/linters/collection_pipeline/ast_utils.py +40 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/collection_pipeline/config.py +12 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/collection_pipeline/continue_analyzer.py +2 -8
- thailint-0.13.0/src/linters/collection_pipeline/detector.py +360 -0
- thailint-0.13.0/src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/collection_pipeline/linter.py +18 -35
- thailint-0.13.0/src/linters/collection_pipeline/suggestion_builder.py +130 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/base_token_analyzer.py +16 -9
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/block_filter.py +7 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/cache.py +7 -2
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/config.py +7 -1
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/constant_matcher.py +34 -25
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/file_analyzer.py +4 -2
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/inline_ignore.py +7 -16
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/linter.py +48 -25
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/python_analyzer.py +18 -10
- thailint-0.13.0/src/linters/dry/python_constant_extractor.py +100 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/single_statement_detector.py +14 -12
- thailint-0.13.0/src/linters/dry/token_hasher.py +173 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/typescript_analyzer.py +11 -6
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/typescript_constant_extractor.py +4 -0
- thailint-0.13.0/src/linters/dry/typescript_statement_detector.py +255 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/typescript_value_extractor.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/violation_filter.py +1 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/violation_generator.py +1 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/atemporal_detector.py +4 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/base_parser.py +4 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/bash_parser.py +4 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/field_validator.py +5 -8
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/linter.py +19 -12
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/markdown_parser.py +6 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/config_loader.py +3 -1
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/linter.py +22 -8
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/pattern_matcher.py +21 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/pattern_validator.py +21 -7
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/rule_checker.py +2 -2
- thailint-0.13.0/src/linters/lazy_ignores/__init__.py +43 -0
- thailint-0.13.0/src/linters/lazy_ignores/config.py +66 -0
- thailint-0.13.0/src/linters/lazy_ignores/directive_utils.py +121 -0
- thailint-0.13.0/src/linters/lazy_ignores/header_parser.py +177 -0
- thailint-0.13.0/src/linters/lazy_ignores/linter.py +158 -0
- thailint-0.13.0/src/linters/lazy_ignores/matcher.py +135 -0
- thailint-0.13.0/src/linters/lazy_ignores/python_analyzer.py +201 -0
- thailint-0.13.0/src/linters/lazy_ignores/rule_id_utils.py +180 -0
- thailint-0.13.0/src/linters/lazy_ignores/skip_detector.py +298 -0
- thailint-0.13.0/src/linters/lazy_ignores/types.py +67 -0
- thailint-0.13.0/src/linters/lazy_ignores/typescript_analyzer.py +146 -0
- thailint-0.13.0/src/linters/lazy_ignores/violation_builder.py +131 -0
- thailint-0.13.0/src/linters/lbyl/__init__.py +29 -0
- thailint-0.13.0/src/linters/lbyl/config.py +63 -0
- thailint-0.13.0/src/linters/lbyl/pattern_detectors/__init__.py +25 -0
- thailint-0.13.0/src/linters/lbyl/pattern_detectors/base.py +46 -0
- thailint-0.13.0/src/linters/magic_numbers/context_analyzer.py +249 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/linter.py +20 -15
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/python_analyzer.py +4 -16
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/typescript_analyzer.py +9 -16
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/config.py +4 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/linter.py +5 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/python_analyzer.py +5 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/violation_builder.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/typescript_analyzer.py +6 -12
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/typescript_function_extractor.py +0 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/linter.py +6 -4
- thailint-0.13.0/src/linters/print_statements/python_analyzer.py +153 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/typescript_analyzer.py +6 -15
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/heuristics.py +4 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/linter.py +12 -12
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/violation_builder.py +0 -4
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stateless_class/linter.py +30 -36
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stateless_class/python_analyzer.py +11 -20
- thailint-0.13.0/src/linters/stringly_typed/__init__.py +36 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/config.py +32 -8
- thailint-0.13.0/src/linters/stringly_typed/context_filter.py +451 -0
- thailint-0.13.0/src/linters/stringly_typed/function_call_violation_builder.py +135 -0
- thailint-0.13.0/src/linters/stringly_typed/ignore_checker.py +102 -0
- thailint-0.13.0/src/linters/stringly_typed/ignore_utils.py +51 -0
- thailint-0.13.0/src/linters/stringly_typed/linter.py +376 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/__init__.py +9 -5
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/analyzer.py +159 -9
- thailint-0.13.0/src/linters/stringly_typed/python/call_tracker.py +175 -0
- thailint-0.13.0/src/linters/stringly_typed/python/comparison_tracker.py +257 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/condition_extractor.py +3 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/conditional_detector.py +4 -1
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/match_analyzer.py +8 -2
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/validation_detector.py +3 -0
- thailint-0.13.0/src/linters/stringly_typed/storage.py +630 -0
- thailint-0.13.0/src/linters/stringly_typed/storage_initializer.py +45 -0
- thailint-0.13.0/src/linters/stringly_typed/typescript/__init__.py +28 -0
- thailint-0.13.0/src/linters/stringly_typed/typescript/analyzer.py +157 -0
- thailint-0.13.0/src/linters/stringly_typed/typescript/call_tracker.py +335 -0
- thailint-0.13.0/src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
- thailint-0.13.0/src/linters/stringly_typed/violation_generator.py +405 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/orchestrator/core.py +13 -4
- thailint-0.13.0/src/templates/thailint_config_template.yaml +324 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/utils/project_root.py +3 -0
- thailint-0.11.0/PKG-INFO +0 -1661
- thailint-0.11.0/README.md +0 -1625
- thailint-0.11.0/src/linter_config/ignore.py +0 -491
- thailint-0.11.0/src/linters/collection_pipeline/detector.py +0 -130
- thailint-0.11.0/src/linters/collection_pipeline/suggestion_builder.py +0 -63
- thailint-0.11.0/src/linters/dry/python_constant_extractor.py +0 -101
- thailint-0.11.0/src/linters/dry/token_hasher.py +0 -173
- thailint-0.11.0/src/linters/dry/typescript_statement_detector.py +0 -255
- thailint-0.11.0/src/linters/magic_numbers/context_analyzer.py +0 -251
- thailint-0.11.0/src/linters/print_statements/python_analyzer.py +0 -149
- thailint-0.11.0/src/linters/stringly_typed/__init__.py +0 -23
- thailint-0.11.0/src/templates/thailint_config_template.yaml +0 -158
- {thailint-0.11.0 → thailint-0.13.0}/CHANGELOG.md +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/LICENSE +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/api.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/__main__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/linters/shared.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/cli/main.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/core/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/core/registry.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/core/types.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/core/violation_utils.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/formatters/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/formatters/sarif.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linter_config/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linter_config/loader.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/collection_pipeline/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/block_grouper.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/cache_query.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/config_loader.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/constant.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/constant_violation_builder.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/deduplicator.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/duplicate_storage.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/storage_initializer.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/dry/violation_builder.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/config.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/css_parser.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/python_parser.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/typescript_parser.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_header/violation_builder.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/directory_matcher.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/path_resolver.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/file_placement/violation_factory.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/config.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/typescript_ignore_checker.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/magic_numbers/violation_builder.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/method_property/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/config.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/linter.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/python_analyzer.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/nesting/violation_builder.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/config.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/print_statements/violation_builder.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/class_analyzer.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/config.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/metrics_evaluator.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/python_analyzer.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/typescript_analyzer.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/srp/typescript_metrics_calculator.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stateless_class/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stateless_class/config.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/constants.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/linters/stringly_typed/python/variable_extractor.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/orchestrator/__init__.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/orchestrator/language_detector.py +0 -0
- {thailint-0.11.0 → thailint-0.13.0}/src/utils/__init__.py +0 -0
thailint-0.13.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://opensource.org/licenses/MIT)
|
|
39
|
+
[](https://www.python.org/downloads/)
|
|
40
|
+
[](https://pypi.org/project/thai-lint/)
|
|
41
|
+
[](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
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://pypi.org/project/thai-lint/)
|
|
6
|
+
[](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.
|
|
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
|
-
#
|
|
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
|