thailint 0.3.1__tar.gz → 0.4.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.3.1 → thailint-0.4.0}/PKG-INFO +79 -21
- {thailint-0.3.1 → thailint-0.4.0}/README.md +78 -20
- {thailint-0.3.1 → thailint-0.4.0}/pyproject.toml +1 -1
- {thailint-0.3.1 → thailint-0.4.0}/src/cli.py +135 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/magic_numbers/config.py +9 -4
- thailint-0.4.0/src/templates/thailint_config_template.yaml +132 -0
- {thailint-0.3.1 → thailint-0.4.0}/CHANGELOG.md +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/LICENSE +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/analyzers/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/analyzers/typescript_base.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/api.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/config.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/base.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/cli_utils.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/config_parser.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/linter_utils.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/registry.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/rule_discovery.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/types.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/core/violation_builder.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linter_config/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linter_config/ignore.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linter_config/loader.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/base_token_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/block_filter.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/block_grouper.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/cache.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/cache_query.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/config.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/config_loader.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/deduplicator.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/duplicate_storage.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/file_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/inline_ignore.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/linter.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/python_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/storage_initializer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/token_hasher.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/typescript_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/violation_builder.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/violation_filter.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/dry/violation_generator.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/config_loader.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/directory_matcher.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/linter.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/path_resolver.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/pattern_matcher.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/pattern_validator.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/rule_checker.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/file_placement/violation_factory.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/magic_numbers/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/magic_numbers/context_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/magic_numbers/linter.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/magic_numbers/python_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/magic_numbers/typescript_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/magic_numbers/violation_builder.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/nesting/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/nesting/config.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/nesting/linter.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/nesting/python_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/nesting/typescript_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/nesting/typescript_function_extractor.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/nesting/violation_builder.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/class_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/config.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/heuristics.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/linter.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/metrics_evaluator.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/python_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/typescript_analyzer.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/typescript_metrics_calculator.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/linters/srp/violation_builder.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/orchestrator/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/orchestrator/core.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/orchestrator/language_detector.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/utils/__init__.py +0 -0
- {thailint-0.3.1 → thailint-0.4.0}/src/utils/project_root.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: thailint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: linter,ai,code-quality,static-analysis,file-placement,governance,multi-language,cli,docker,python
|
|
@@ -35,14 +35,25 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
|
|
36
36
|
[](https://opensource.org/licenses/MIT)
|
|
37
37
|
[](https://www.python.org/downloads/)
|
|
38
|
-
[](tests/)
|
|
39
|
+
[](htmlcov/)
|
|
40
40
|
|
|
41
41
|
The AI Linter - Enterprise-ready linting and governance for AI-generated code across multiple languages.
|
|
42
42
|
|
|
43
43
|
## Overview
|
|
44
44
|
|
|
45
|
-
thailint is a modern, enterprise-ready multi-language linter designed specifically for AI-generated code. It
|
|
45
|
+
thailint is a modern, enterprise-ready multi-language linter designed specifically for AI-generated code. It focuses on common mistakes and anti-patterns that AI coding assistants frequently introduce—issues that existing linters don't catch or don't handle consistently across languages.
|
|
46
|
+
|
|
47
|
+
**Why thailint?**
|
|
48
|
+
|
|
49
|
+
We're not trying to replace the wonderful existing linters like Pylint, ESLint, or Ruff. Instead, thailint fills critical gaps:
|
|
50
|
+
|
|
51
|
+
- **AI-Specific Patterns**: AI assistants have predictable blind spots (excessive nesting, magic numbers, SRP violations) that traditional linters miss
|
|
52
|
+
- **Cross-Language Consistency**: Detects the same anti-patterns across Python, TypeScript, and JavaScript with unified rules
|
|
53
|
+
- **No Existing Solutions**: Issues like excessive nesting depth, file placement violations, and cross-project code duplication lack comprehensive multi-language detection
|
|
54
|
+
- **Governance Layer**: Enforces project-wide structure and organization patterns that AI can't infer from local context
|
|
55
|
+
|
|
56
|
+
thailint complements your existing linting stack by catching the patterns AI tools repeatedly miss.
|
|
46
57
|
|
|
47
58
|
## Features
|
|
48
59
|
|
|
@@ -917,49 +928,96 @@ def test_no_violations():
|
|
|
917
928
|
### Setup Development Environment
|
|
918
929
|
|
|
919
930
|
```bash
|
|
920
|
-
# Install
|
|
921
|
-
|
|
931
|
+
# Install dependencies and activate virtualenv
|
|
932
|
+
just init
|
|
922
933
|
|
|
923
|
-
#
|
|
924
|
-
|
|
934
|
+
# Or manually:
|
|
935
|
+
poetry install
|
|
936
|
+
source $(poetry env info --path)/bin/activate
|
|
925
937
|
```
|
|
926
938
|
|
|
927
939
|
### Running Tests
|
|
928
940
|
|
|
929
941
|
```bash
|
|
930
|
-
# Run all tests
|
|
931
|
-
|
|
942
|
+
# Run all tests (parallel mode - fast)
|
|
943
|
+
just test
|
|
932
944
|
|
|
933
|
-
# Run with coverage
|
|
934
|
-
|
|
945
|
+
# Run with coverage (serial mode)
|
|
946
|
+
just test-coverage
|
|
935
947
|
|
|
936
948
|
# Run specific test
|
|
937
|
-
pytest tests/test_cli.py::test_hello_command
|
|
949
|
+
poetry run pytest tests/test_cli.py::test_hello_command -v
|
|
938
950
|
```
|
|
939
951
|
|
|
940
952
|
### Code Quality
|
|
941
953
|
|
|
942
954
|
```bash
|
|
943
|
-
#
|
|
944
|
-
|
|
955
|
+
# Fast linting (Ruff only - use during development)
|
|
956
|
+
just lint
|
|
957
|
+
|
|
958
|
+
# Comprehensive linting (Ruff + Pylint + Flake8 + MyPy)
|
|
959
|
+
just lint-all
|
|
960
|
+
|
|
961
|
+
# Security scanning
|
|
962
|
+
just lint-security
|
|
963
|
+
|
|
964
|
+
# Complexity analysis (Radon + Xenon + Nesting)
|
|
965
|
+
just lint-complexity
|
|
966
|
+
|
|
967
|
+
# SOLID principles (SRP)
|
|
968
|
+
just lint-solid
|
|
969
|
+
|
|
970
|
+
# DRY principles (duplicate code detection)
|
|
971
|
+
just lint-dry
|
|
972
|
+
|
|
973
|
+
# ALL quality checks (runs everything)
|
|
974
|
+
just lint-full
|
|
975
|
+
|
|
976
|
+
# Auto-fix formatting issues
|
|
977
|
+
just format
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
### Dogfooding (Lint Our Own Code)
|
|
981
|
+
|
|
982
|
+
```bash
|
|
983
|
+
# Lint file placement
|
|
984
|
+
just lint-placement
|
|
945
985
|
|
|
946
|
-
#
|
|
947
|
-
|
|
986
|
+
# Check nesting depth
|
|
987
|
+
just lint-nesting
|
|
948
988
|
|
|
949
|
-
#
|
|
950
|
-
|
|
989
|
+
# Check for magic numbers
|
|
990
|
+
poetry run thai-lint magic-numbers src/
|
|
951
991
|
```
|
|
952
992
|
|
|
953
|
-
### Building
|
|
993
|
+
### Building and Publishing
|
|
954
994
|
|
|
955
995
|
```bash
|
|
956
996
|
# Build Python package
|
|
957
997
|
poetry build
|
|
958
998
|
|
|
959
|
-
# Build Docker image locally
|
|
999
|
+
# Build Docker image locally
|
|
960
1000
|
docker build -t washad/thailint:latest .
|
|
1001
|
+
|
|
1002
|
+
# Publish to PyPI and Docker Hub (runs tests + linting + version bump)
|
|
1003
|
+
just publish
|
|
961
1004
|
```
|
|
962
1005
|
|
|
1006
|
+
### Quick Development Workflows
|
|
1007
|
+
|
|
1008
|
+
```bash
|
|
1009
|
+
# Make changes, then run quality checks
|
|
1010
|
+
just lint-full
|
|
1011
|
+
|
|
1012
|
+
# Share changes for collaboration (skips hooks)
|
|
1013
|
+
just share "WIP: feature description"
|
|
1014
|
+
|
|
1015
|
+
# Clean up cache and artifacts
|
|
1016
|
+
just clean
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
See `just --list` or `just help` for all available commands.
|
|
1020
|
+
|
|
963
1021
|
## Docker Usage
|
|
964
1022
|
|
|
965
1023
|
```bash
|
|
@@ -2,14 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
|
-
[](tests/)
|
|
6
|
+
[](htmlcov/)
|
|
7
7
|
|
|
8
8
|
The AI Linter - Enterprise-ready linting and governance for AI-generated code across multiple languages.
|
|
9
9
|
|
|
10
10
|
## Overview
|
|
11
11
|
|
|
12
|
-
thailint is a modern, enterprise-ready multi-language linter designed specifically for AI-generated code. It
|
|
12
|
+
thailint is a modern, enterprise-ready multi-language linter designed specifically for AI-generated code. It focuses on common mistakes and anti-patterns that AI coding assistants frequently introduce—issues that existing linters don't catch or don't handle consistently across languages.
|
|
13
|
+
|
|
14
|
+
**Why thailint?**
|
|
15
|
+
|
|
16
|
+
We're not trying to replace the wonderful existing linters like Pylint, ESLint, or Ruff. Instead, thailint fills critical gaps:
|
|
17
|
+
|
|
18
|
+
- **AI-Specific Patterns**: AI assistants have predictable blind spots (excessive nesting, magic numbers, SRP violations) that traditional linters miss
|
|
19
|
+
- **Cross-Language Consistency**: Detects the same anti-patterns across Python, TypeScript, and JavaScript with unified rules
|
|
20
|
+
- **No Existing Solutions**: Issues like excessive nesting depth, file placement violations, and cross-project code duplication lack comprehensive multi-language detection
|
|
21
|
+
- **Governance Layer**: Enforces project-wide structure and organization patterns that AI can't infer from local context
|
|
22
|
+
|
|
23
|
+
thailint complements your existing linting stack by catching the patterns AI tools repeatedly miss.
|
|
13
24
|
|
|
14
25
|
## Features
|
|
15
26
|
|
|
@@ -884,49 +895,96 @@ def test_no_violations():
|
|
|
884
895
|
### Setup Development Environment
|
|
885
896
|
|
|
886
897
|
```bash
|
|
887
|
-
# Install
|
|
888
|
-
|
|
898
|
+
# Install dependencies and activate virtualenv
|
|
899
|
+
just init
|
|
889
900
|
|
|
890
|
-
#
|
|
891
|
-
|
|
901
|
+
# Or manually:
|
|
902
|
+
poetry install
|
|
903
|
+
source $(poetry env info --path)/bin/activate
|
|
892
904
|
```
|
|
893
905
|
|
|
894
906
|
### Running Tests
|
|
895
907
|
|
|
896
908
|
```bash
|
|
897
|
-
# Run all tests
|
|
898
|
-
|
|
909
|
+
# Run all tests (parallel mode - fast)
|
|
910
|
+
just test
|
|
899
911
|
|
|
900
|
-
# Run with coverage
|
|
901
|
-
|
|
912
|
+
# Run with coverage (serial mode)
|
|
913
|
+
just test-coverage
|
|
902
914
|
|
|
903
915
|
# Run specific test
|
|
904
|
-
pytest tests/test_cli.py::test_hello_command
|
|
916
|
+
poetry run pytest tests/test_cli.py::test_hello_command -v
|
|
905
917
|
```
|
|
906
918
|
|
|
907
919
|
### Code Quality
|
|
908
920
|
|
|
909
921
|
```bash
|
|
910
|
-
#
|
|
911
|
-
|
|
922
|
+
# Fast linting (Ruff only - use during development)
|
|
923
|
+
just lint
|
|
924
|
+
|
|
925
|
+
# Comprehensive linting (Ruff + Pylint + Flake8 + MyPy)
|
|
926
|
+
just lint-all
|
|
927
|
+
|
|
928
|
+
# Security scanning
|
|
929
|
+
just lint-security
|
|
930
|
+
|
|
931
|
+
# Complexity analysis (Radon + Xenon + Nesting)
|
|
932
|
+
just lint-complexity
|
|
933
|
+
|
|
934
|
+
# SOLID principles (SRP)
|
|
935
|
+
just lint-solid
|
|
936
|
+
|
|
937
|
+
# DRY principles (duplicate code detection)
|
|
938
|
+
just lint-dry
|
|
939
|
+
|
|
940
|
+
# ALL quality checks (runs everything)
|
|
941
|
+
just lint-full
|
|
942
|
+
|
|
943
|
+
# Auto-fix formatting issues
|
|
944
|
+
just format
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
### Dogfooding (Lint Our Own Code)
|
|
948
|
+
|
|
949
|
+
```bash
|
|
950
|
+
# Lint file placement
|
|
951
|
+
just lint-placement
|
|
912
952
|
|
|
913
|
-
#
|
|
914
|
-
|
|
953
|
+
# Check nesting depth
|
|
954
|
+
just lint-nesting
|
|
915
955
|
|
|
916
|
-
#
|
|
917
|
-
|
|
956
|
+
# Check for magic numbers
|
|
957
|
+
poetry run thai-lint magic-numbers src/
|
|
918
958
|
```
|
|
919
959
|
|
|
920
|
-
### Building
|
|
960
|
+
### Building and Publishing
|
|
921
961
|
|
|
922
962
|
```bash
|
|
923
963
|
# Build Python package
|
|
924
964
|
poetry build
|
|
925
965
|
|
|
926
|
-
# Build Docker image locally
|
|
966
|
+
# Build Docker image locally
|
|
927
967
|
docker build -t washad/thailint:latest .
|
|
968
|
+
|
|
969
|
+
# Publish to PyPI and Docker Hub (runs tests + linting + version bump)
|
|
970
|
+
just publish
|
|
928
971
|
```
|
|
929
972
|
|
|
973
|
+
### Quick Development Workflows
|
|
974
|
+
|
|
975
|
+
```bash
|
|
976
|
+
# Make changes, then run quality checks
|
|
977
|
+
just lint-full
|
|
978
|
+
|
|
979
|
+
# Share changes for collaboration (skips hooks)
|
|
980
|
+
just share "WIP: feature description"
|
|
981
|
+
|
|
982
|
+
# Clean up cache and artifacts
|
|
983
|
+
just clean
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
See `just --list` or `just help` for all available commands.
|
|
987
|
+
|
|
930
988
|
## Docker Usage
|
|
931
989
|
|
|
932
990
|
```bash
|
|
@@ -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.4.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"
|
|
@@ -362,6 +362,141 @@ def config_reset(ctx, yes: bool):
|
|
|
362
362
|
sys.exit(1)
|
|
363
363
|
|
|
364
364
|
|
|
365
|
+
@cli.command("init-config")
|
|
366
|
+
@click.option(
|
|
367
|
+
"--preset",
|
|
368
|
+
"-p",
|
|
369
|
+
type=click.Choice(["strict", "standard", "lenient"]),
|
|
370
|
+
default="standard",
|
|
371
|
+
help="Configuration preset",
|
|
372
|
+
)
|
|
373
|
+
@click.option("--non-interactive", is_flag=True, help="Skip interactive prompts (for AI agents)")
|
|
374
|
+
@click.option("--force", is_flag=True, help="Overwrite existing .thailint.yaml file")
|
|
375
|
+
@click.option(
|
|
376
|
+
"--output", "-o", type=click.Path(), default=".thailint.yaml", help="Output file path"
|
|
377
|
+
)
|
|
378
|
+
def init_config(preset: str, non_interactive: bool, force: bool, output: str):
|
|
379
|
+
"""
|
|
380
|
+
Generate a .thailint.yaml configuration file with preset values.
|
|
381
|
+
|
|
382
|
+
Creates a richly-commented configuration file with sensible defaults
|
|
383
|
+
and optional customizations for different strictness levels.
|
|
384
|
+
|
|
385
|
+
For AI agents, use --non-interactive mode:
|
|
386
|
+
thailint init-config --non-interactive --preset lenient
|
|
387
|
+
|
|
388
|
+
Presets:
|
|
389
|
+
strict: Minimal allowed numbers (only -1, 0, 1)
|
|
390
|
+
standard: Balanced defaults (includes 2, 3, 4, 5, 10, 100, 1000)
|
|
391
|
+
lenient: Includes time conversions (adds 60, 3600)
|
|
392
|
+
|
|
393
|
+
Examples:
|
|
394
|
+
|
|
395
|
+
\\b
|
|
396
|
+
# Interactive mode (default, for humans)
|
|
397
|
+
thailint init-config
|
|
398
|
+
|
|
399
|
+
\\b
|
|
400
|
+
# Non-interactive mode (for AI agents)
|
|
401
|
+
thailint init-config --non-interactive
|
|
402
|
+
|
|
403
|
+
\\b
|
|
404
|
+
# Generate with lenient preset
|
|
405
|
+
thailint init-config --preset lenient
|
|
406
|
+
|
|
407
|
+
\\b
|
|
408
|
+
# Overwrite existing config
|
|
409
|
+
thailint init-config --force
|
|
410
|
+
|
|
411
|
+
\\b
|
|
412
|
+
# Custom output path
|
|
413
|
+
thailint init-config --output my-config.yaml
|
|
414
|
+
"""
|
|
415
|
+
output_path = Path(output)
|
|
416
|
+
|
|
417
|
+
# Check if file exists (unless --force)
|
|
418
|
+
if output_path.exists() and not force:
|
|
419
|
+
click.echo(f"Error: {output} already exists", err=True)
|
|
420
|
+
click.echo("", err=True)
|
|
421
|
+
click.echo("Use --force to overwrite:", err=True)
|
|
422
|
+
click.echo(" thailint init-config --force", err=True)
|
|
423
|
+
sys.exit(1)
|
|
424
|
+
|
|
425
|
+
# Interactive mode: Ask user for preferences
|
|
426
|
+
if not non_interactive:
|
|
427
|
+
click.echo("thai-lint Configuration Generator")
|
|
428
|
+
click.echo("=" * 50)
|
|
429
|
+
click.echo("")
|
|
430
|
+
click.echo("This will create a .thailint.yaml configuration file.")
|
|
431
|
+
click.echo("For non-interactive mode (AI agents), use:")
|
|
432
|
+
click.echo(" thailint init-config --non-interactive")
|
|
433
|
+
click.echo("")
|
|
434
|
+
|
|
435
|
+
# Ask for preset
|
|
436
|
+
click.echo("Available presets:")
|
|
437
|
+
click.echo(" strict: Only -1, 0, 1 allowed (strictest)")
|
|
438
|
+
click.echo(" standard: -1, 0, 1, 2, 3, 4, 5, 10, 100, 1000 (balanced)")
|
|
439
|
+
click.echo(" lenient: Includes time conversions 60, 3600 (most permissive)")
|
|
440
|
+
click.echo("")
|
|
441
|
+
|
|
442
|
+
preset = click.prompt(
|
|
443
|
+
"Choose preset", type=click.Choice(["strict", "standard", "lenient"]), default=preset
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Generate config based on preset
|
|
447
|
+
config_content = _generate_config_content(preset)
|
|
448
|
+
|
|
449
|
+
# Write config file
|
|
450
|
+
try:
|
|
451
|
+
output_path.write_text(config_content, encoding="utf-8")
|
|
452
|
+
click.echo("")
|
|
453
|
+
click.echo(f"✓ Created {output}")
|
|
454
|
+
click.echo(f"✓ Preset: {preset}")
|
|
455
|
+
click.echo("")
|
|
456
|
+
click.echo("Next steps:")
|
|
457
|
+
click.echo(f" 1. Review and customize {output}")
|
|
458
|
+
click.echo(" 2. Run: thailint magic-numbers .")
|
|
459
|
+
click.echo(" 3. See docs: https://github.com/your-org/thai-lint")
|
|
460
|
+
except OSError as e:
|
|
461
|
+
click.echo(f"Error writing config file: {e}", err=True)
|
|
462
|
+
sys.exit(1)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _generate_config_content(preset: str) -> str:
|
|
466
|
+
"""Generate config file content based on preset."""
|
|
467
|
+
# Preset configurations
|
|
468
|
+
presets = {
|
|
469
|
+
"strict": {
|
|
470
|
+
"allowed_numbers": "[-1, 0, 1]",
|
|
471
|
+
"max_small_integer": "3",
|
|
472
|
+
"description": "Strict (only universal values)",
|
|
473
|
+
},
|
|
474
|
+
"standard": {
|
|
475
|
+
"allowed_numbers": "[-1, 0, 1, 2, 3, 4, 5, 10, 100, 1000]",
|
|
476
|
+
"max_small_integer": "10",
|
|
477
|
+
"description": "Standard (balanced defaults)",
|
|
478
|
+
},
|
|
479
|
+
"lenient": {
|
|
480
|
+
"allowed_numbers": "[-1, 0, 1, 2, 3, 4, 5, 10, 60, 100, 1000, 3600]",
|
|
481
|
+
"max_small_integer": "10",
|
|
482
|
+
"description": "Lenient (includes time conversions)",
|
|
483
|
+
},
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
config = presets[preset]
|
|
487
|
+
|
|
488
|
+
# Read template
|
|
489
|
+
template_path = Path(__file__).parent / "templates" / "thailint_config_template.yaml"
|
|
490
|
+
template = template_path.read_text(encoding="utf-8")
|
|
491
|
+
|
|
492
|
+
# Replace placeholders
|
|
493
|
+
content = template.replace("{{PRESET}}", config["description"])
|
|
494
|
+
content = content.replace("{{ALLOWED_NUMBERS}}", config["allowed_numbers"])
|
|
495
|
+
content = content.replace("{{MAX_SMALL_INTEGER}}", config["max_small_integer"])
|
|
496
|
+
|
|
497
|
+
return content
|
|
498
|
+
|
|
499
|
+
|
|
365
500
|
@cli.command("file-placement")
|
|
366
501
|
@click.argument("paths", nargs=-1, type=click.Path())
|
|
367
502
|
@click.option("--config", "-c", "config_file", type=click.Path(), help="Path to config file")
|
|
@@ -4,7 +4,7 @@ Purpose: Configuration schema for magic numbers linter
|
|
|
4
4
|
Scope: MagicNumberConfig dataclass with allowed_numbers and max_small_integer settings
|
|
5
5
|
|
|
6
6
|
Overview: Defines configuration schema for magic numbers linter. Provides MagicNumberConfig dataclass
|
|
7
|
-
with allowed_numbers set (default includes common acceptable numbers like 0, 1, 2,
|
|
7
|
+
with allowed_numbers set (default includes common acceptable numbers like -1, 0, 1, 2, 3, 4, 5, 10, 100, 1000)
|
|
8
8
|
and max_small_integer threshold (default 10) for range() contexts. Supports per-file and per-directory
|
|
9
9
|
config overrides through from_dict class method. Validates that configuration values are appropriate
|
|
10
10
|
types. Integrates with orchestrator's configuration system to allow users to customize allowed numbers
|
|
@@ -29,7 +29,9 @@ class MagicNumberConfig:
|
|
|
29
29
|
"""Configuration for magic numbers linter."""
|
|
30
30
|
|
|
31
31
|
enabled: bool = True
|
|
32
|
-
allowed_numbers: set[int | float] = field(
|
|
32
|
+
allowed_numbers: set[int | float] = field(
|
|
33
|
+
default_factory=lambda: {-1, 0, 1, 2, 3, 4, 5, 10, 100, 1000}
|
|
34
|
+
)
|
|
33
35
|
max_small_integer: int = 10
|
|
34
36
|
|
|
35
37
|
def __post_init__(self) -> None:
|
|
@@ -54,14 +56,17 @@ class MagicNumberConfig:
|
|
|
54
56
|
lang_config = config[language]
|
|
55
57
|
allowed_numbers = set(
|
|
56
58
|
lang_config.get(
|
|
57
|
-
"allowed_numbers",
|
|
59
|
+
"allowed_numbers",
|
|
60
|
+
config.get("allowed_numbers", {-1, 0, 1, 2, 3, 4, 5, 10, 100, 1000}),
|
|
58
61
|
)
|
|
59
62
|
)
|
|
60
63
|
max_small_integer = lang_config.get(
|
|
61
64
|
"max_small_integer", config.get("max_small_integer", 10)
|
|
62
65
|
)
|
|
63
66
|
else:
|
|
64
|
-
allowed_numbers = set(
|
|
67
|
+
allowed_numbers = set(
|
|
68
|
+
config.get("allowed_numbers", {-1, 0, 1, 2, 3, 4, 5, 10, 100, 1000})
|
|
69
|
+
)
|
|
65
70
|
max_small_integer = config.get("max_small_integer", 10)
|
|
66
71
|
|
|
67
72
|
return cls(
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# thai-lint Configuration File
|
|
2
|
+
# Generated by: thailint init-config
|
|
3
|
+
#
|
|
4
|
+
# For non-interactive mode (AI agents): thailint init-config --non-interactive
|
|
5
|
+
#
|
|
6
|
+
# Full documentation: https://github.com/your-org/thai-lint
|
|
7
|
+
|
|
8
|
+
# ============================================================================
|
|
9
|
+
# MAGIC NUMBERS LINTER
|
|
10
|
+
# ============================================================================
|
|
11
|
+
# Detects unnamed numeric literals that should be extracted as constants
|
|
12
|
+
#
|
|
13
|
+
# Preset: {{PRESET}}
|
|
14
|
+
#
|
|
15
|
+
magic-numbers:
|
|
16
|
+
enabled: true
|
|
17
|
+
|
|
18
|
+
# Numbers that are acceptable without being named constants
|
|
19
|
+
# Default: [-1, 0, 1, 2, 3, 4, 5, 10, 100, 1000]
|
|
20
|
+
allowed_numbers: {{ALLOWED_NUMBERS}}
|
|
21
|
+
|
|
22
|
+
# Maximum integer allowed in range() or enumerate() without flagging
|
|
23
|
+
# Default: 10
|
|
24
|
+
max_small_integer: {{MAX_SMALL_INTEGER}}
|
|
25
|
+
|
|
26
|
+
# -------------------------------------------------------------------------
|
|
27
|
+
# OPTIONAL: Uncomment to add time conversions (lenient mode)
|
|
28
|
+
# -------------------------------------------------------------------------
|
|
29
|
+
# allowed_numbers: [-1, 0, 1, 2, 3, 4, 5, 10, 60, 100, 1000, 3600]
|
|
30
|
+
|
|
31
|
+
# -------------------------------------------------------------------------
|
|
32
|
+
# OPTIONAL: Uncomment to add common HTTP status codes
|
|
33
|
+
# -------------------------------------------------------------------------
|
|
34
|
+
# allowed_numbers: [-1, 0, 1, 2, 3, 4, 5, 10, 100, 200, 201, 204, 400, 401, 403, 404, 500, 502, 503, 1000]
|
|
35
|
+
|
|
36
|
+
# -------------------------------------------------------------------------
|
|
37
|
+
# OPTIONAL: Uncomment to add decimal proportions (0.0-1.0)
|
|
38
|
+
# -------------------------------------------------------------------------
|
|
39
|
+
# allowed_numbers: [-1, 0, 1, 2, 3, 4, 5, 10, 100, 1000, 0.0, 0.1, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.9, 1.0]
|
|
40
|
+
|
|
41
|
+
# ============================================================================
|
|
42
|
+
# NESTING LINTER
|
|
43
|
+
# ============================================================================
|
|
44
|
+
# Checks for excessive nesting depth (if/for/while/try statements)
|
|
45
|
+
#
|
|
46
|
+
nesting:
|
|
47
|
+
enabled: true
|
|
48
|
+
|
|
49
|
+
# Maximum nesting depth allowed
|
|
50
|
+
# Default: 4
|
|
51
|
+
max_nesting_depth: 4
|
|
52
|
+
|
|
53
|
+
# ============================================================================
|
|
54
|
+
# SINGLE RESPONSIBILITY PRINCIPLE (SRP) LINTER
|
|
55
|
+
# ============================================================================
|
|
56
|
+
# Detects classes that may have too many responsibilities
|
|
57
|
+
#
|
|
58
|
+
srp:
|
|
59
|
+
enabled: true
|
|
60
|
+
|
|
61
|
+
# Maximum methods per class
|
|
62
|
+
# Default: 7
|
|
63
|
+
max_methods: 7
|
|
64
|
+
|
|
65
|
+
# Maximum lines of code per class
|
|
66
|
+
# Default: 200
|
|
67
|
+
max_loc: 200
|
|
68
|
+
|
|
69
|
+
# ============================================================================
|
|
70
|
+
# DRY (DON'T REPEAT YOURSELF) LINTER
|
|
71
|
+
# ============================================================================
|
|
72
|
+
# Detects duplicate code blocks
|
|
73
|
+
#
|
|
74
|
+
dry:
|
|
75
|
+
enabled: true
|
|
76
|
+
|
|
77
|
+
# Minimum lines for a block to be considered duplicate
|
|
78
|
+
# Default: 6
|
|
79
|
+
min_duplicate_lines: 6
|
|
80
|
+
|
|
81
|
+
# Enable SQLite caching for faster incremental scans
|
|
82
|
+
# Default: true
|
|
83
|
+
cache_enabled: true
|
|
84
|
+
|
|
85
|
+
# Cache file location (relative to project root)
|
|
86
|
+
# Default: .thailint-cache/dry.db
|
|
87
|
+
cache_path: .thailint-cache/dry.db
|
|
88
|
+
|
|
89
|
+
# ============================================================================
|
|
90
|
+
# FILE PLACEMENT LINTER
|
|
91
|
+
# ============================================================================
|
|
92
|
+
# Ensures files are in appropriate directories
|
|
93
|
+
#
|
|
94
|
+
file-placement:
|
|
95
|
+
enabled: true
|
|
96
|
+
|
|
97
|
+
# Rules for file placement
|
|
98
|
+
rules:
|
|
99
|
+
# Test files should be in tests/ directory
|
|
100
|
+
- pattern: "test_*.py"
|
|
101
|
+
required_dir: "tests/"
|
|
102
|
+
message: "Test files must be in tests/ directory"
|
|
103
|
+
|
|
104
|
+
# Config files should be in config/ or root
|
|
105
|
+
- pattern: "*config*.py"
|
|
106
|
+
required_dir: ["config/", "./"]
|
|
107
|
+
message: "Config files should be in config/ or project root"
|
|
108
|
+
|
|
109
|
+
# ============================================================================
|
|
110
|
+
# GLOBAL SETTINGS
|
|
111
|
+
# ============================================================================
|
|
112
|
+
#
|
|
113
|
+
# Exclude patterns (files/directories to ignore)
|
|
114
|
+
exclude:
|
|
115
|
+
- ".git/"
|
|
116
|
+
- ".venv/"
|
|
117
|
+
- "venv/"
|
|
118
|
+
- "node_modules/"
|
|
119
|
+
- "__pycache__/"
|
|
120
|
+
- "*.pyc"
|
|
121
|
+
- ".pytest_cache/"
|
|
122
|
+
- "dist/"
|
|
123
|
+
- "build/"
|
|
124
|
+
- ".eggs/"
|
|
125
|
+
|
|
126
|
+
# Output format (text or json)
|
|
127
|
+
# Default: text
|
|
128
|
+
output_format: text
|
|
129
|
+
|
|
130
|
+
# Exit with error code if violations found
|
|
131
|
+
# Default: true
|
|
132
|
+
fail_on_violations: true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|