thailint 0.1.5__py3-none-any.whl → 0.5.0__py3-none-any.whl

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 (91) hide show
  1. src/__init__.py +7 -2
  2. src/analyzers/__init__.py +23 -0
  3. src/analyzers/typescript_base.py +148 -0
  4. src/api.py +1 -1
  5. src/cli.py +1111 -144
  6. src/config.py +12 -33
  7. src/core/base.py +102 -5
  8. src/core/cli_utils.py +206 -0
  9. src/core/config_parser.py +126 -0
  10. src/core/linter_utils.py +168 -0
  11. src/core/registry.py +17 -92
  12. src/core/rule_discovery.py +132 -0
  13. src/core/violation_builder.py +122 -0
  14. src/linter_config/ignore.py +112 -40
  15. src/linter_config/loader.py +3 -13
  16. src/linters/dry/__init__.py +23 -0
  17. src/linters/dry/base_token_analyzer.py +76 -0
  18. src/linters/dry/block_filter.py +265 -0
  19. src/linters/dry/block_grouper.py +59 -0
  20. src/linters/dry/cache.py +172 -0
  21. src/linters/dry/cache_query.py +61 -0
  22. src/linters/dry/config.py +134 -0
  23. src/linters/dry/config_loader.py +44 -0
  24. src/linters/dry/deduplicator.py +120 -0
  25. src/linters/dry/duplicate_storage.py +63 -0
  26. src/linters/dry/file_analyzer.py +90 -0
  27. src/linters/dry/inline_ignore.py +140 -0
  28. src/linters/dry/linter.py +163 -0
  29. src/linters/dry/python_analyzer.py +668 -0
  30. src/linters/dry/storage_initializer.py +42 -0
  31. src/linters/dry/token_hasher.py +169 -0
  32. src/linters/dry/typescript_analyzer.py +592 -0
  33. src/linters/dry/violation_builder.py +74 -0
  34. src/linters/dry/violation_filter.py +94 -0
  35. src/linters/dry/violation_generator.py +174 -0
  36. src/linters/file_header/__init__.py +24 -0
  37. src/linters/file_header/atemporal_detector.py +87 -0
  38. src/linters/file_header/config.py +66 -0
  39. src/linters/file_header/field_validator.py +69 -0
  40. src/linters/file_header/linter.py +313 -0
  41. src/linters/file_header/python_parser.py +86 -0
  42. src/linters/file_header/violation_builder.py +78 -0
  43. src/linters/file_placement/config_loader.py +86 -0
  44. src/linters/file_placement/directory_matcher.py +80 -0
  45. src/linters/file_placement/linter.py +262 -471
  46. src/linters/file_placement/path_resolver.py +61 -0
  47. src/linters/file_placement/pattern_matcher.py +55 -0
  48. src/linters/file_placement/pattern_validator.py +106 -0
  49. src/linters/file_placement/rule_checker.py +229 -0
  50. src/linters/file_placement/violation_factory.py +177 -0
  51. src/linters/magic_numbers/__init__.py +48 -0
  52. src/linters/magic_numbers/config.py +82 -0
  53. src/linters/magic_numbers/context_analyzer.py +247 -0
  54. src/linters/magic_numbers/linter.py +516 -0
  55. src/linters/magic_numbers/python_analyzer.py +76 -0
  56. src/linters/magic_numbers/typescript_analyzer.py +218 -0
  57. src/linters/magic_numbers/violation_builder.py +98 -0
  58. src/linters/nesting/__init__.py +6 -2
  59. src/linters/nesting/config.py +17 -4
  60. src/linters/nesting/linter.py +81 -168
  61. src/linters/nesting/typescript_analyzer.py +39 -102
  62. src/linters/nesting/typescript_function_extractor.py +130 -0
  63. src/linters/nesting/violation_builder.py +139 -0
  64. src/linters/print_statements/__init__.py +53 -0
  65. src/linters/print_statements/config.py +83 -0
  66. src/linters/print_statements/linter.py +430 -0
  67. src/linters/print_statements/python_analyzer.py +155 -0
  68. src/linters/print_statements/typescript_analyzer.py +135 -0
  69. src/linters/print_statements/violation_builder.py +98 -0
  70. src/linters/srp/__init__.py +99 -0
  71. src/linters/srp/class_analyzer.py +113 -0
  72. src/linters/srp/config.py +82 -0
  73. src/linters/srp/heuristics.py +89 -0
  74. src/linters/srp/linter.py +234 -0
  75. src/linters/srp/metrics_evaluator.py +47 -0
  76. src/linters/srp/python_analyzer.py +72 -0
  77. src/linters/srp/typescript_analyzer.py +75 -0
  78. src/linters/srp/typescript_metrics_calculator.py +90 -0
  79. src/linters/srp/violation_builder.py +117 -0
  80. src/orchestrator/core.py +54 -9
  81. src/templates/thailint_config_template.yaml +158 -0
  82. src/utils/__init__.py +4 -0
  83. src/utils/project_root.py +203 -0
  84. thailint-0.5.0.dist-info/METADATA +1286 -0
  85. thailint-0.5.0.dist-info/RECORD +96 -0
  86. {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info}/WHEEL +1 -1
  87. src/.ai/layout.yaml +0 -48
  88. thailint-0.1.5.dist-info/METADATA +0 -629
  89. thailint-0.1.5.dist-info/RECORD +0 -28
  90. {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info}/entry_points.txt +0 -0
  91. {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,61 @@
1
+ """
2
+ Purpose: Path resolution and normalization for file placement linter
3
+
4
+ Scope: Handles path operations including relative path calculation and normalization
5
+
6
+ Overview: Provides path resolution utilities for the file placement linter. Converts
7
+ absolute paths to paths relative to project root, normalizes path separators for
8
+ cross-platform compatibility, and handles edge cases like paths outside project root.
9
+ Isolates path manipulation logic from rule checking and pattern matching.
10
+
11
+ Dependencies: pathlib
12
+
13
+ Exports: PathResolver
14
+
15
+ Interfaces: get_relative_path(file_path) -> Path, normalize_path_string(path) -> str
16
+
17
+ Implementation: Uses pathlib for robust path operations, handles ValueError for out-of-tree paths
18
+ """
19
+
20
+ from pathlib import Path
21
+
22
+
23
+ class PathResolver:
24
+ """Resolves and normalizes file paths for file placement linter."""
25
+
26
+ def __init__(self, project_root: Path):
27
+ """Initialize path resolver.
28
+
29
+ Args:
30
+ project_root: Project root directory
31
+ """
32
+ self.project_root = project_root
33
+
34
+ def get_relative_path(self, file_path: Path) -> Path:
35
+ """Get path relative to project root.
36
+
37
+ Args:
38
+ file_path: File path to convert
39
+
40
+ Returns:
41
+ Path relative to project root, or original path if outside project
42
+ """
43
+ try:
44
+ if file_path.is_absolute():
45
+ return file_path.relative_to(self.project_root)
46
+ return file_path
47
+ except ValueError:
48
+ # If path is outside project root, return it as-is
49
+ # This allows detection of absolute paths in global_deny patterns
50
+ return file_path
51
+
52
+ def normalize_path_string(self, path: Path) -> str:
53
+ """Normalize path to forward slashes for cross-platform consistency.
54
+
55
+ Args:
56
+ path: Path to normalize
57
+
58
+ Returns:
59
+ Path string with forward slashes
60
+ """
61
+ return str(path).replace("\\", "/")
@@ -0,0 +1,55 @@
1
+ """
2
+ Purpose: Pattern matching utilities for file placement linter
3
+
4
+ Scope: Handles regex pattern matching for allow/deny file placement rules
5
+
6
+ Overview: Provides pattern matching functionality for the file placement linter. Matches
7
+ file paths against regex patterns for both allow and deny lists. Supports case-insensitive
8
+ matching and extracts denial reasons from configuration. Isolates pattern matching logic
9
+ from rule checking and configuration validation.
10
+
11
+ Dependencies: re
12
+
13
+ Exports: PatternMatcher
14
+
15
+ Interfaces: match_deny_patterns(path, patterns) -> (bool, reason), match_allow_patterns(path, patterns) -> bool
16
+
17
+ Implementation: Uses re.search() for pattern matching with IGNORECASE flag
18
+ """
19
+
20
+ import re
21
+
22
+
23
+ class PatternMatcher:
24
+ """Handles regex pattern matching for file paths."""
25
+
26
+ def match_deny_patterns(
27
+ self, path_str: str, deny_patterns: list[dict[str, str]]
28
+ ) -> tuple[bool, str | None]:
29
+ """Check if path matches any deny patterns.
30
+
31
+ Args:
32
+ path_str: File path to check
33
+ deny_patterns: List of deny pattern dicts with 'pattern' and 'reason'
34
+
35
+ Returns:
36
+ Tuple of (is_denied, reason)
37
+ """
38
+ for deny_item in deny_patterns:
39
+ pattern = deny_item["pattern"]
40
+ if re.search(pattern, path_str, re.IGNORECASE):
41
+ reason = deny_item.get("reason", "File not allowed in this location")
42
+ return True, reason
43
+ return False, None
44
+
45
+ def match_allow_patterns(self, path_str: str, allow_patterns: list[str]) -> bool:
46
+ """Check if path matches any allow patterns.
47
+
48
+ Args:
49
+ path_str: File path to check
50
+ allow_patterns: List of regex patterns
51
+
52
+ Returns:
53
+ True if path matches any pattern
54
+ """
55
+ return any(re.search(pattern, path_str, re.IGNORECASE) for pattern in allow_patterns)
@@ -0,0 +1,106 @@
1
+ """
2
+ Purpose: Regex pattern validation for file placement linter configuration
3
+
4
+ Scope: Validates regex patterns in configuration files to ensure they are compilable
5
+
6
+ Overview: Provides validation functionality for regex patterns used in file placement rules.
7
+ Validates patterns in allow/deny lists, directory-specific rules, and global patterns.
8
+ Raises descriptive errors when patterns are invalid, helping users debug configuration
9
+ issues early. Isolates pattern validation logic from rule checking and config loading.
10
+
11
+ Dependencies: re, typing
12
+
13
+ Exports: PatternValidator
14
+
15
+ Interfaces: validate_config(config) -> None (raises ValueError on invalid patterns)
16
+
17
+ Implementation: Uses re.compile() to test pattern validity, provides detailed error messages
18
+ """
19
+
20
+ import re
21
+ from typing import Any
22
+
23
+
24
+ class PatternValidator:
25
+ """Validates regex patterns in file placement configuration."""
26
+
27
+ def validate_config(self, config: dict[str, Any]) -> None:
28
+ """Validate all regex patterns in configuration.
29
+
30
+ Args:
31
+ config: Full configuration dict
32
+
33
+ Raises:
34
+ ValueError: If any regex pattern is invalid
35
+ """
36
+ fp_config = config.get("file-placement", {})
37
+ self._validate_directory_patterns(fp_config)
38
+ self._validate_global_patterns(fp_config)
39
+ self._validate_global_deny_patterns(fp_config)
40
+
41
+ def _validate_pattern(self, pattern: str) -> None:
42
+ """Validate a single regex pattern.
43
+
44
+ Args:
45
+ pattern: Regex pattern to validate
46
+
47
+ Raises:
48
+ ValueError: If pattern is invalid
49
+ """
50
+ try:
51
+ re.compile(pattern)
52
+ except re.error as e:
53
+ raise ValueError(f"Invalid regex pattern '{pattern}': {e}") from e
54
+
55
+ def _validate_allow_patterns(self, rules: dict[str, Any]) -> None:
56
+ """Validate allow patterns in a rules dict.
57
+
58
+ Args:
59
+ rules: Rules dictionary containing allow patterns
60
+ """
61
+ if "allow" in rules:
62
+ for pattern in rules["allow"]:
63
+ self._validate_pattern(pattern)
64
+
65
+ def _validate_deny_patterns(self, rules: dict[str, Any]) -> None:
66
+ """Validate deny patterns in a rules dict.
67
+
68
+ Args:
69
+ rules: Rules dictionary containing deny patterns
70
+ """
71
+ if "deny" in rules:
72
+ for deny_item in rules["deny"]:
73
+ pattern = deny_item.get("pattern", "")
74
+ self._validate_pattern(pattern)
75
+
76
+ def _validate_directory_patterns(self, fp_config: dict[str, Any]) -> None:
77
+ """Validate all directory-specific patterns.
78
+
79
+ Args:
80
+ fp_config: File placement configuration section
81
+ """
82
+ if "directories" in fp_config:
83
+ for _dir_path, rules in fp_config["directories"].items():
84
+ self._validate_allow_patterns(rules)
85
+ self._validate_deny_patterns(rules)
86
+
87
+ def _validate_global_patterns(self, fp_config: dict[str, Any]) -> None:
88
+ """Validate global patterns section.
89
+
90
+ Args:
91
+ fp_config: File placement configuration section
92
+ """
93
+ if "global_patterns" in fp_config:
94
+ self._validate_allow_patterns(fp_config["global_patterns"])
95
+ self._validate_deny_patterns(fp_config["global_patterns"])
96
+
97
+ def _validate_global_deny_patterns(self, fp_config: dict[str, Any]) -> None:
98
+ """Validate global_deny patterns.
99
+
100
+ Args:
101
+ fp_config: File placement configuration section
102
+ """
103
+ if "global_deny" in fp_config:
104
+ for deny_item in fp_config["global_deny"]:
105
+ pattern = deny_item.get("pattern", "")
106
+ self._validate_pattern(pattern)
@@ -0,0 +1,229 @@
1
+ """
2
+ Purpose: Rule checking logic for file placement linter
3
+
4
+ Scope: Executes file placement rules including directory-specific and global patterns
5
+
6
+ Overview: Provides core rule checking functionality for the file placement linter. Checks
7
+ files against directory-specific allow/deny rules, global patterns, and global deny lists.
8
+ Uses pattern matcher for regex matching, directory matcher for finding rules, and violation
9
+ factory for creating violations. Implements deny-takes-precedence logic. Isolates rule
10
+ execution logic from configuration, validation, and violation creation.
11
+
12
+ Dependencies: pathlib, typing, dataclasses, PatternMatcher, DirectoryMatcher, ViolationFactory, src.core.types
13
+
14
+ Exports: RuleChecker
15
+
16
+ Interfaces: check_all_rules(path_str, rel_path, fp_config) -> list[Violation]
17
+
18
+ Implementation: Checks deny before allow, delegates directory matching to DirectoryMatcher,
19
+ uses RuleCheckContext dataclass to reduce parameter duplication
20
+ """
21
+
22
+ from dataclasses import dataclass
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ from src.core.types import Violation
27
+
28
+ from .directory_matcher import DirectoryMatcher
29
+ from .pattern_matcher import PatternMatcher
30
+ from .violation_factory import ViolationFactory
31
+
32
+
33
+ @dataclass
34
+ class RuleCheckContext:
35
+ """Context information for rule checking.
36
+
37
+ Attributes:
38
+ path_str: Normalized path string
39
+ rel_path: Relative path object
40
+ dir_rule: Directory rule configuration
41
+ matched_path: Matched directory path
42
+ """
43
+
44
+ path_str: str
45
+ rel_path: Path
46
+ dir_rule: dict[str, Any]
47
+ matched_path: str
48
+
49
+
50
+ class RuleChecker:
51
+ """Checks file placement rules and returns violations."""
52
+
53
+ def __init__(self, pattern_matcher: PatternMatcher, violation_factory: ViolationFactory):
54
+ """Initialize rule checker.
55
+
56
+ Args:
57
+ pattern_matcher: Pattern matcher for regex matching
58
+ violation_factory: Factory for creating violations
59
+ """
60
+ self.pattern_matcher = pattern_matcher
61
+ self.violation_factory = violation_factory
62
+ self.directory_matcher = DirectoryMatcher()
63
+
64
+ def check_all_rules(
65
+ self, path_str: str, rel_path: Path, fp_config: dict[str, Any]
66
+ ) -> list[Violation]:
67
+ """Check all file placement rules.
68
+
69
+ Args:
70
+ path_str: Normalized path string
71
+ rel_path: Relative path object
72
+ fp_config: File placement configuration
73
+
74
+ Returns:
75
+ List of violations found
76
+ """
77
+ violations: list[Violation] = []
78
+
79
+ if "directories" in fp_config:
80
+ dir_violations = self._check_directory_rules(
81
+ path_str, rel_path, fp_config["directories"]
82
+ )
83
+ violations.extend(dir_violations)
84
+
85
+ if "global_deny" in fp_config:
86
+ deny_violations = self._check_global_deny(path_str, rel_path, fp_config["global_deny"])
87
+ violations.extend(deny_violations)
88
+
89
+ if "global_patterns" in fp_config:
90
+ global_violations = self._check_global_patterns(
91
+ path_str, rel_path, fp_config["global_patterns"]
92
+ )
93
+ violations.extend(global_violations)
94
+
95
+ return violations
96
+
97
+ def _check_directory_deny_rules(self, ctx: RuleCheckContext) -> Violation | None:
98
+ """Check directory deny rules.
99
+
100
+ Args:
101
+ ctx: Rule check context with file and rule information
102
+
103
+ Returns:
104
+ Violation if denied, None otherwise
105
+ """
106
+ if "deny" not in ctx.dir_rule:
107
+ return None
108
+
109
+ is_denied, reason = self.pattern_matcher.match_deny_patterns(
110
+ ctx.path_str, ctx.dir_rule["deny"]
111
+ )
112
+ if is_denied:
113
+ return self.violation_factory.create_deny_violation(
114
+ ctx.rel_path, ctx.matched_path, reason or "Pattern denied"
115
+ )
116
+ return None
117
+
118
+ def _check_directory_allow_rules(self, ctx: RuleCheckContext) -> Violation | None:
119
+ """Check directory allow rules.
120
+
121
+ Args:
122
+ ctx: Rule check context with file and rule information
123
+
124
+ Returns:
125
+ Violation if not allowed, None otherwise
126
+ """
127
+ if "allow" not in ctx.dir_rule:
128
+ return None
129
+
130
+ is_allowed = self.pattern_matcher.match_allow_patterns(ctx.path_str, ctx.dir_rule["allow"])
131
+ if not is_allowed:
132
+ return self.violation_factory.create_allow_violation(ctx.rel_path, ctx.matched_path)
133
+ return None
134
+
135
+ def _check_directory_rules(
136
+ self, path_str: str, rel_path: Path, directories: dict[str, Any]
137
+ ) -> list[Violation]:
138
+ """Check file against directory-specific rules.
139
+
140
+ Args:
141
+ path_str: Normalized path string
142
+ rel_path: Relative path object
143
+ directories: Directory rules config
144
+
145
+ Returns:
146
+ List of violations
147
+ """
148
+ dir_rule, matched_path = self.directory_matcher.find_matching_rule(path_str, directories)
149
+ if not dir_rule or not matched_path:
150
+ return []
151
+
152
+ ctx = RuleCheckContext(
153
+ path_str=path_str,
154
+ rel_path=rel_path,
155
+ dir_rule=dir_rule,
156
+ matched_path=matched_path,
157
+ )
158
+
159
+ # Check deny patterns first (takes precedence)
160
+ deny_violation = self._check_directory_deny_rules(ctx)
161
+ if deny_violation:
162
+ return self._wrap_violation(deny_violation)
163
+
164
+ # Check allow patterns
165
+ allow_violation = self._check_directory_allow_rules(ctx)
166
+ return self._wrap_violation(allow_violation)
167
+
168
+ def _wrap_violation(self, violation: Violation | None) -> list[Violation]:
169
+ """Wrap single violation in list, or return empty list if None.
170
+
171
+ Args:
172
+ violation: Violation to wrap, or None
173
+
174
+ Returns:
175
+ List containing violation, or empty list
176
+ """
177
+ return [violation] if violation else []
178
+
179
+ def _check_global_deny(
180
+ self, path_str: str, rel_path: Path, global_deny: list[dict[str, str]]
181
+ ) -> list[Violation]:
182
+ """Check file against global deny patterns.
183
+
184
+ Args:
185
+ path_str: Normalized path string
186
+ rel_path: Relative path object
187
+ global_deny: Global deny patterns
188
+
189
+ Returns:
190
+ List of violations
191
+ """
192
+ is_denied, reason = self.pattern_matcher.match_deny_patterns(path_str, global_deny)
193
+ if is_denied:
194
+ violation = self.violation_factory.create_global_deny_violation(rel_path, reason)
195
+ return self._wrap_violation(violation)
196
+ return []
197
+
198
+ def _check_global_patterns(
199
+ self, path_str: str, rel_path: Path, global_patterns: dict[str, Any]
200
+ ) -> list[Violation]:
201
+ """Check file against global patterns.
202
+
203
+ Args:
204
+ path_str: Normalized path string
205
+ rel_path: Relative path object
206
+ global_patterns: Global patterns config
207
+
208
+ Returns:
209
+ List of violations
210
+ """
211
+ # Check deny patterns first
212
+ if "deny" in global_patterns:
213
+ is_denied, reason = self.pattern_matcher.match_deny_patterns(
214
+ path_str, global_patterns["deny"]
215
+ )
216
+ if is_denied:
217
+ violation = self.violation_factory.create_global_deny_violation(rel_path, reason)
218
+ return self._wrap_violation(violation)
219
+
220
+ # Check allow patterns
221
+ if "allow" in global_patterns:
222
+ is_allowed = self.pattern_matcher.match_allow_patterns(
223
+ path_str, global_patterns["allow"]
224
+ )
225
+ if not is_allowed:
226
+ violation = self.violation_factory.create_global_allow_violation(rel_path)
227
+ return self._wrap_violation(violation)
228
+
229
+ return []
@@ -0,0 +1,177 @@
1
+ """
2
+ Purpose: Violation creation with helpful suggestions for file placement linter
3
+
4
+ Scope: Creates Violation objects with contextual messages and actionable suggestions
5
+
6
+ Overview: Provides violation creation functionality for the file placement linter. Generates
7
+ descriptive error messages, creates helpful suggestions based on file type and location,
8
+ and encapsulates all violation construction logic. Separates violation building from
9
+ rule checking to maintain single responsibility and improve message consistency.
10
+
11
+ Dependencies: pathlib, src.core.types (Violation, Severity), src.core.violation_builder
12
+
13
+ Exports: ViolationFactory
14
+
15
+ Interfaces: create_deny_violation, create_allow_violation, create_global_deny_violation
16
+
17
+ Implementation: Uses file extension and name patterns to generate context-aware suggestions,
18
+ extends BaseViolationBuilder for consistent violation construction
19
+ """
20
+
21
+ from pathlib import Path
22
+
23
+ from src.core.types import Severity, Violation
24
+ from src.core.violation_builder import BaseViolationBuilder
25
+
26
+
27
+ class ViolationFactory(BaseViolationBuilder):
28
+ """Creates violations with helpful suggestions for file placement linter."""
29
+
30
+ # Suggestion lookup by file type
31
+ _SUGGESTIONS = {
32
+ "test": "Move to tests/ directory",
33
+ "component": "Move to src/components/",
34
+ "source": "Move to src/",
35
+ "temp": "Move to debug/logs/ or add to .gitignore",
36
+ }
37
+ _DEFAULT_SUGGESTION = "Review file organization and move to appropriate directory"
38
+
39
+ def create_deny_violation(self, rel_path: Path, matched_path: str, reason: str) -> Violation:
40
+ """Create violation for denied file.
41
+
42
+ Args:
43
+ rel_path: Relative path to file
44
+ matched_path: Directory path that matched
45
+ reason: Reason file is denied
46
+
47
+ Returns:
48
+ Violation with message and suggestion
49
+ """
50
+ message = f"File '{rel_path}' not allowed in {matched_path}: {reason}"
51
+ suggestion = self._get_suggestion(rel_path.name)
52
+ return self.build_from_params(
53
+ rule_id="file-placement",
54
+ file_path=str(rel_path),
55
+ line=1,
56
+ column=0,
57
+ message=message,
58
+ severity=Severity.ERROR,
59
+ suggestion=suggestion,
60
+ )
61
+
62
+ def create_allow_violation(self, rel_path: Path, matched_path: str) -> Violation:
63
+ """Create violation for file not matching allow patterns.
64
+
65
+ Args:
66
+ rel_path: Relative path to file
67
+ matched_path: Directory path that matched
68
+
69
+ Returns:
70
+ Violation with message and suggestion
71
+ """
72
+ message = f"File '{rel_path}' does not match allowed patterns for {matched_path}"
73
+ suggestion = f"Move to {matched_path} or ensure file type is allowed"
74
+ return self.build_from_params(
75
+ rule_id="file-placement",
76
+ file_path=str(rel_path),
77
+ line=1,
78
+ column=0,
79
+ message=message,
80
+ severity=Severity.ERROR,
81
+ suggestion=suggestion,
82
+ )
83
+
84
+ def create_global_deny_violation(self, rel_path: Path, reason: str | None) -> Violation:
85
+ """Create violation for global deny pattern match.
86
+
87
+ Args:
88
+ rel_path: Relative path to file
89
+ reason: Reason file is denied (optional)
90
+
91
+ Returns:
92
+ Violation with message and suggestion
93
+ """
94
+ message = reason or f"File '{rel_path}' matches denied pattern"
95
+ suggestion = self._get_suggestion(rel_path.name)
96
+ return self.build_from_params(
97
+ rule_id="file-placement",
98
+ file_path=str(rel_path),
99
+ line=1,
100
+ column=0,
101
+ message=message,
102
+ severity=Severity.ERROR,
103
+ suggestion=suggestion,
104
+ )
105
+
106
+ def create_global_allow_violation(self, rel_path: Path) -> Violation:
107
+ """Create violation for file not matching global allow patterns.
108
+
109
+ Args:
110
+ rel_path: Relative path to file
111
+
112
+ Returns:
113
+ Violation with message and suggestion
114
+ """
115
+ message = f"File '{rel_path}' does not match any allowed patterns"
116
+ suggestion = "Ensure file matches project structure patterns"
117
+ return self.build_from_params(
118
+ rule_id="file-placement",
119
+ file_path=str(rel_path),
120
+ line=1,
121
+ column=0,
122
+ message=message,
123
+ severity=Severity.ERROR,
124
+ suggestion=suggestion,
125
+ )
126
+
127
+ def _is_temp_file(self, filename: str) -> bool:
128
+ """Check if file is a temporary or utility file.
129
+
130
+ Args:
131
+ filename: File name
132
+
133
+ Returns:
134
+ True if file is temporary/utility file
135
+ """
136
+ return filename.startswith(("debug", "temp")) or filename.endswith(".log")
137
+
138
+ def _classify_file_type(self, filename: str) -> str | None:
139
+ """Classify file type based on filename patterns.
140
+
141
+ Args:
142
+ filename: File name
143
+
144
+ Returns:
145
+ File type classification key, or None if no pattern matches
146
+ """
147
+ filename_lower = filename.lower()
148
+
149
+ # Check keyword-based patterns
150
+ if "test" in filename_lower:
151
+ return "test"
152
+ if "component" in filename_lower:
153
+ return "component"
154
+
155
+ # Check extension-based patterns
156
+ if filename.endswith((".ts", ".tsx", ".jsx", ".py")):
157
+ return "source"
158
+
159
+ # Check temp/utility patterns
160
+ if self._is_temp_file(filename):
161
+ return "temp"
162
+
163
+ return None
164
+
165
+ def _get_suggestion(self, filename: str) -> str:
166
+ """Get suggestion for file placement based on filename patterns.
167
+
168
+ Args:
169
+ filename: File name
170
+
171
+ Returns:
172
+ Suggestion string with actionable guidance
173
+ """
174
+ file_type = self._classify_file_type(filename)
175
+ if file_type is None:
176
+ return self._DEFAULT_SUGGESTION
177
+ return self._SUGGESTIONS.get(file_type, self._DEFAULT_SUGGESTION)
@@ -0,0 +1,48 @@
1
+ """
2
+ Purpose: Magic numbers linter package exports and convenience functions
3
+
4
+ Scope: Public API for magic numbers linter module
5
+
6
+ Overview: Provides the public interface for the magic numbers linter package. Exports main
7
+ MagicNumberRule class for use by the orchestrator and MagicNumberConfig for configuration.
8
+ Includes lint() convenience function that provides a simple API for running the magic numbers
9
+ linter on a file or directory without directly interacting with the orchestrator. This module
10
+ serves as the entry point for users of the magic numbers linter, hiding implementation details
11
+ and exposing only the essential components needed for linting operations.
12
+
13
+ Dependencies: .linter for MagicNumberRule, .config for MagicNumberConfig
14
+
15
+ Exports: MagicNumberRule class, MagicNumberConfig dataclass, lint() convenience function
16
+
17
+ Interfaces: lint(path, config) -> list[Violation] for simple linting operations
18
+
19
+ Implementation: Module-level exports with __all__ definition, convenience function wrapper
20
+ """
21
+
22
+ from .config import MagicNumberConfig
23
+ from .linter import MagicNumberRule
24
+
25
+ __all__ = ["MagicNumberRule", "MagicNumberConfig", "lint"]
26
+
27
+
28
+ def lint(file_path: str, config: dict | None = None) -> list:
29
+ """Convenience function for linting a file for magic numbers.
30
+
31
+ Args:
32
+ file_path: Path to the file to lint
33
+ config: Optional configuration dictionary
34
+
35
+ Returns:
36
+ List of violations found
37
+ """
38
+ from pathlib import Path
39
+
40
+ from src.orchestrator.core import FileLintContext
41
+
42
+ rule = MagicNumberRule()
43
+ context = FileLintContext(
44
+ path=Path(file_path),
45
+ lang="python",
46
+ )
47
+
48
+ return rule.check(context)