autoai-codetrust 0.1.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.
@@ -0,0 +1,8 @@
1
+ """AICodeTrustScore -- Continuous trust scoring for AI-generated code.
2
+
3
+ Analyzes code produced by Cursor, Copilot, Claude, and other AI tools,
4
+ assigning a 0-100 trust score based on correctness, safety, test coverage,
5
+ dependency risk, consistency, and hallucination detection.
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,29 @@
1
+ """AICodeTrustScore analyzers -- each module detects a specific class of trust issue."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .correctness import CorrectnessAnalyzer
6
+ from .safety import SafetyAnalyzer
7
+ from .test_coverage import TestCoverageAnalyzer
8
+ from .dependency import DependencyAnalyzer
9
+ from .consistency import ConsistencyAnalyzer
10
+ from .hallucination import HallucinationAnalyzer
11
+
12
+ ALL_ANALYZERS = [
13
+ CorrectnessAnalyzer,
14
+ SafetyAnalyzer,
15
+ TestCoverageAnalyzer,
16
+ DependencyAnalyzer,
17
+ ConsistencyAnalyzer,
18
+ HallucinationAnalyzer,
19
+ ]
20
+
21
+ __all__ = [
22
+ "CorrectnessAnalyzer",
23
+ "SafetyAnalyzer",
24
+ "TestCoverageAnalyzer",
25
+ "DependencyAnalyzer",
26
+ "ConsistencyAnalyzer",
27
+ "HallucinationAnalyzer",
28
+ "ALL_ANALYZERS",
29
+ ]
@@ -0,0 +1,255 @@
1
+ """Consistency analyzer -- checks if AI code matches existing project style.
2
+
3
+ Checks:
4
+ - Naming convention mismatch with existing code
5
+ - Different error handling patterns than the project uses
6
+ - Import style inconsistency
7
+ - Comment style mismatch
8
+ - Architecture layer violations
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from pathlib import Path
15
+
16
+ from ..types import TrustIssue, IssueCategory, Severity
17
+
18
+
19
+ _CODE_EXTENSIONS = {".py", ".ts", ".tsx", ".js", ".jsx", ".go"}
20
+
21
+
22
+ class ConsistencyAnalyzer:
23
+ """Detects style and pattern inconsistencies in AI-generated code."""
24
+
25
+ name = "consistency"
26
+
27
+ @classmethod
28
+ def analyze_file(cls, file_path: Path, content: str) -> list[TrustIssue]:
29
+ """Analyse a file for consistency issues."""
30
+ findings: list[TrustIssue] = []
31
+
32
+ if file_path.suffix not in _CODE_EXTENSIONS:
33
+ return findings
34
+
35
+ lines = content.splitlines()
36
+ rel_path = str(file_path)
37
+ is_python = file_path.suffix == ".py"
38
+ is_js_ts = file_path.suffix in {".ts", ".tsx", ".js", ".jsx"}
39
+
40
+ # Naming convention checks
41
+ if is_python:
42
+ findings.extend(cls._check_python_naming(rel_path, lines))
43
+ findings.extend(cls._check_python_import_style(rel_path, lines))
44
+ findings.extend(cls._check_python_string_style(rel_path, lines))
45
+
46
+ if is_js_ts:
47
+ findings.extend(cls._check_js_naming(rel_path, lines))
48
+ findings.extend(cls._check_js_import_style(rel_path, lines))
49
+
50
+ # Mixed tab/space indentation
51
+ findings.extend(cls._check_indentation(rel_path, lines))
52
+
53
+ return findings
54
+
55
+ @classmethod
56
+ def _check_python_naming(cls, rel_path: str, lines: list[str]) -> list[TrustIssue]:
57
+ """Check Python naming conventions (PEP 8)."""
58
+ findings: list[TrustIssue] = []
59
+ camel_case_var = re.compile(r'^\s*([a-z]+[A-Z]\w*)\s*=')
60
+
61
+ camel_count = 0
62
+ for i, line in enumerate(lines, 1):
63
+ m = camel_case_var.match(line)
64
+ if m:
65
+ var_name = m.group(1)
66
+ # Exclude common abbreviations
67
+ if not re.match(r'^(is|has|can|should|will)', var_name):
68
+ camel_count += 1
69
+ if camel_count <= 3: # Report first few only
70
+ findings.append(TrustIssue(
71
+ analyzer=cls.name,
72
+ category=IssueCategory.CONSISTENCY,
73
+ severity=Severity.LOW,
74
+ title=f"camelCase variable '{var_name}' in Python",
75
+ description="Python convention is snake_case for variables and functions (PEP 8).",
76
+ file_path=rel_path,
77
+ line_start=i,
78
+ suggestion=f"Rename to '{cls._to_snake_case(var_name)}'.",
79
+ code_snippet=line.strip()[:200],
80
+ ))
81
+
82
+ # Classes not in PascalCase
83
+ class_pattern = re.compile(r'^\s*class\s+(\w+)')
84
+ for i, line in enumerate(lines, 1):
85
+ m = class_pattern.match(line)
86
+ if m:
87
+ name = m.group(1)
88
+ if "_" in name and not name.startswith("_"):
89
+ findings.append(TrustIssue(
90
+ analyzer=cls.name,
91
+ category=IssueCategory.CONSISTENCY,
92
+ severity=Severity.LOW,
93
+ title=f"Class '{name}' uses snake_case instead of PascalCase",
94
+ description="Python convention is PascalCase for class names (PEP 8).",
95
+ file_path=rel_path,
96
+ line_start=i,
97
+ suggestion=f"Rename to PascalCase.",
98
+ code_snippet=line.strip()[:200],
99
+ ))
100
+
101
+ return findings
102
+
103
+ @classmethod
104
+ def _check_python_import_style(cls, rel_path: str, lines: list[str]) -> list[TrustIssue]:
105
+ """Check for inconsistent import styles."""
106
+ findings: list[TrustIssue] = []
107
+
108
+ has_wildcard = False
109
+ wildcard_line = 0
110
+ for i, line in enumerate(lines, 1):
111
+ stripped = line.strip()
112
+ if re.match(r'from\s+\w+\s+import\s+\*', stripped):
113
+ has_wildcard = True
114
+ wildcard_line = i
115
+
116
+ if has_wildcard:
117
+ findings.append(TrustIssue(
118
+ analyzer=cls.name,
119
+ category=IssueCategory.CONSISTENCY,
120
+ severity=Severity.MEDIUM,
121
+ title="Wildcard import (from x import *)",
122
+ description="Wildcard imports pollute the namespace and make it unclear what's available.",
123
+ file_path=rel_path,
124
+ line_start=wildcard_line,
125
+ suggestion="Import specific names: from module import Class1, func1",
126
+ ))
127
+
128
+ return findings
129
+
130
+ @classmethod
131
+ def _check_python_string_style(cls, rel_path: str, lines: list[str]) -> list[TrustIssue]:
132
+ """Check for mixed single/double quotes (inconsistency indicator)."""
133
+ findings: list[TrustIssue] = []
134
+ single_count = 0
135
+ double_count = 0
136
+
137
+ for line in lines:
138
+ stripped = line.strip()
139
+ if stripped.startswith("#"):
140
+ continue
141
+ # Count string literal starts (simple heuristic)
142
+ single_count += len(re.findall(r"(?<![\"\\])'(?!'')", stripped))
143
+ double_count += len(re.findall(r'(?<![\'\\])"(?!"")', stripped))
144
+
145
+ total = single_count + double_count
146
+ if total > 10: # Only check if there are enough strings
147
+ ratio = min(single_count, double_count) / max(single_count, double_count, 1)
148
+ if 0.3 < ratio < 0.7:
149
+ findings.append(TrustIssue(
150
+ analyzer=cls.name,
151
+ category=IssueCategory.CONSISTENCY,
152
+ severity=Severity.INFO,
153
+ title="Inconsistent string quote style",
154
+ description=f"Mixed use of single ({single_count}) and double ({double_count}) quotes. "
155
+ "Pick one style and use it consistently.",
156
+ file_path=rel_path,
157
+ line_start=1,
158
+ suggestion="Use a formatter like black (Python) or prettier (JS) to enforce consistent quotes.",
159
+ ))
160
+
161
+ return findings
162
+
163
+ @classmethod
164
+ def _check_js_naming(cls, rel_path: str, lines: list[str]) -> list[TrustIssue]:
165
+ """Check JS/TS naming conventions."""
166
+ findings: list[TrustIssue] = []
167
+ snake_case_var = re.compile(r'^\s*(?:const|let|var)\s+([a-z]+_[a-z_]+)\s*=')
168
+
169
+ snake_count = 0
170
+ for i, line in enumerate(lines, 1):
171
+ m = snake_case_var.match(line)
172
+ if m:
173
+ var_name = m.group(1)
174
+ snake_count += 1
175
+ if snake_count <= 3:
176
+ findings.append(TrustIssue(
177
+ analyzer=cls.name,
178
+ category=IssueCategory.CONSISTENCY,
179
+ severity=Severity.LOW,
180
+ title=f"snake_case variable '{var_name}' in JS/TS",
181
+ description="JavaScript/TypeScript convention is camelCase for variables.",
182
+ file_path=rel_path,
183
+ line_start=i,
184
+ suggestion=f"Rename to '{cls._to_camel_case(var_name)}'.",
185
+ code_snippet=line.strip()[:200],
186
+ ))
187
+
188
+ return findings
189
+
190
+ @classmethod
191
+ def _check_js_import_style(cls, rel_path: str, lines: list[str]) -> list[TrustIssue]:
192
+ """Check for mixed require/import in JS/TS."""
193
+ findings: list[TrustIssue] = []
194
+ has_import = False
195
+ has_require = False
196
+
197
+ for line in lines:
198
+ stripped = line.strip()
199
+ if re.match(r'import\s+', stripped):
200
+ has_import = True
201
+ if re.search(r'require\s*\(', stripped):
202
+ has_require = True
203
+
204
+ if has_import and has_require:
205
+ findings.append(TrustIssue(
206
+ analyzer=cls.name,
207
+ category=IssueCategory.CONSISTENCY,
208
+ severity=Severity.LOW,
209
+ title="Mixed import and require() statements",
210
+ description="File uses both ES module imports and CommonJS require() -- pick one style.",
211
+ file_path=rel_path,
212
+ line_start=1,
213
+ suggestion="Use ES modules (import/export) consistently in modern JS/TS projects.",
214
+ ))
215
+
216
+ return findings
217
+
218
+ @classmethod
219
+ def _check_indentation(cls, rel_path: str, lines: list[str]) -> list[TrustIssue]:
220
+ """Check for mixed tabs and spaces."""
221
+ findings: list[TrustIssue] = []
222
+ has_tabs = False
223
+ has_spaces = False
224
+
225
+ for line in lines:
226
+ if line.startswith("\t"):
227
+ has_tabs = True
228
+ elif line.startswith(" "):
229
+ has_spaces = True
230
+
231
+ if has_tabs and has_spaces:
232
+ findings.append(TrustIssue(
233
+ analyzer=cls.name,
234
+ category=IssueCategory.CONSISTENCY,
235
+ severity=Severity.MEDIUM,
236
+ title="Mixed tabs and spaces for indentation",
237
+ description="File uses both tabs and spaces for indentation -- this causes subtle bugs.",
238
+ file_path=rel_path,
239
+ line_start=1,
240
+ suggestion="Configure your editor to use consistent indentation (spaces recommended).",
241
+ ))
242
+
243
+ return findings
244
+
245
+ @staticmethod
246
+ def _to_snake_case(name: str) -> str:
247
+ """Convert camelCase to snake_case."""
248
+ s = re.sub(r'([A-Z])', r'_\1', name).lower()
249
+ return s.lstrip("_")
250
+
251
+ @staticmethod
252
+ def _to_camel_case(name: str) -> str:
253
+ """Convert snake_case to camelCase."""
254
+ parts = name.split("_")
255
+ return parts[0] + "".join(p.capitalize() for p in parts[1:])
@@ -0,0 +1,330 @@
1
+ """Correctness analyzer -- detects common AI-generated code bugs.
2
+
3
+ Checks:
4
+ - Off-by-one patterns (loop bounds, array indexing)
5
+ - Null/undefined checks missing
6
+ - Type mismatches (comparing string to int)
7
+ - Unreachable conditions (always-true/false)
8
+ - Return type inconsistencies
9
+ - Error handling gaps (try without catch, catch without handling)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from pathlib import Path
16
+
17
+ from ..types import TrustIssue, IssueCategory, Severity
18
+
19
+
20
+ _CODE_EXTENSIONS = {".py", ".ts", ".tsx", ".js", ".jsx", ".go"}
21
+
22
+
23
+ # Off-by-one patterns
24
+ _OFF_BY_ONE_PATTERNS = [
25
+ # range(len(x)) when should be range(len(x) - 1) or vice versa
26
+ (re.compile(r'for\s+\w+\s+in\s+range\s*\(\s*len\s*\(\s*\w+\s*\)\s*\+\s*1\s*\)'),
27
+ "range(len(x) + 1) likely off-by-one -- will index past end of list"),
28
+ # arr[len(arr)] -- always out of bounds
29
+ (re.compile(r'\w+\[\s*len\s*\(\s*\w+\s*\)\s*\]'),
30
+ "Indexing at len(x) is always out of bounds -- last valid index is len(x)-1"),
31
+ # <= in range-based for loop (JS/TS/Go)
32
+ (re.compile(r'for\s*\(.*;\s*\w+\s*<=\s*\w+\.length\s*;'),
33
+ "Loop condition uses <= length instead of < length -- off-by-one error"),
34
+ ]
35
+
36
+ # Missing null/undefined checks
37
+ _NULL_CHECK_PATTERNS = [
38
+ # Chained property access without optional chaining (JS/TS)
39
+ (re.compile(r'(?<![\?\.])\.\w+\.\w+\.\w+\.\w+'),
40
+ "Deep property access without null checks -- may throw on undefined intermediate"),
41
+ # .get() result used without None check (Python)
42
+ (re.compile(r'(\w+)\.get\([^)]+\)\.\w+'),
43
+ "Calling method on .get() result without None check -- .get() can return None"),
44
+ ]
45
+
46
+ # Type mismatch patterns
47
+ _TYPE_MISMATCH_PATTERNS = [
48
+ # Comparing string to number without conversion
49
+ (re.compile(r'==\s*["\'][0-9]+["\']'),
50
+ "Comparing with string-encoded number -- may need int() or parseInt() conversion"),
51
+ # parseInt without radix
52
+ (re.compile(r'parseInt\s*\(\s*\w+\s*\)(?!\s*,)'),
53
+ "parseInt() without radix parameter -- always specify radix (e.g., parseInt(x, 10))"),
54
+ ]
55
+
56
+ # Unreachable code patterns
57
+ _UNREACHABLE_PATTERNS = [
58
+ # if True / if False
59
+ (re.compile(r'if\s+True\s*:'),
60
+ "Condition is always True -- dead code branch or debugging leftover"),
61
+ (re.compile(r'if\s+False\s*:'),
62
+ "Condition is always False -- code block is unreachable"),
63
+ # while True without break (simple heuristic)
64
+ (re.compile(r'if\s+\w+\s*(?:==|!=)\s*\w+\s*and\s+\w+\s*(?:==|!=)\s*\w+.*:\s*$'),
65
+ None), # placeholder, checked by _check_tautology
66
+ ]
67
+
68
+ # Error handling gaps
69
+ _ERROR_HANDLING_PATTERNS_PY = [
70
+ # bare except
71
+ (re.compile(r'except\s*:'),
72
+ "Bare except catches all exceptions including SystemExit and KeyboardInterrupt"),
73
+ # except Exception: pass
74
+ (re.compile(r'except\s+\w+.*:\s*\n\s*pass\s*$', re.MULTILINE),
75
+ "Exception caught but silently ignored with pass -- errors will be hidden"),
76
+ # try without except (Python)
77
+ (re.compile(r'try\s*:\s*\n(?:(?!\s*except|\s*finally).)*\n\s*finally', re.MULTILINE),
78
+ None), # try/finally is valid, skip
79
+ ]
80
+
81
+ _ERROR_HANDLING_PATTERNS_JS = [
82
+ # empty catch block
83
+ (re.compile(r'catch\s*\([^)]*\)\s*\{\s*\}'),
84
+ "Empty catch block -- errors are silently swallowed"),
85
+ # catch with only console.log
86
+ (re.compile(r'catch\s*\([^)]*\)\s*\{\s*console\.log'),
87
+ "Catch block only logs the error but does not handle or re-throw it"),
88
+ ]
89
+
90
+ # Return type inconsistency
91
+ _RETURN_PATTERNS = [
92
+ # Function with both return value and bare return
93
+ (re.compile(r'def\s+\w+[^:]*:.*?\n(?:.*\n)*?\s+return\s+\S+.*\n(?:.*\n)*?\s+return\s*$', re.MULTILINE),
94
+ "Function has both value-returning and bare return statements -- inconsistent return type"),
95
+ ]
96
+
97
+
98
+ class CorrectnessAnalyzer:
99
+ """Detects correctness issues common in AI-generated code."""
100
+
101
+ name = "correctness"
102
+
103
+ @classmethod
104
+ def analyze_file(cls, file_path: Path, content: str) -> list[TrustIssue]:
105
+ """Analyse a single file for correctness issues."""
106
+ findings: list[TrustIssue] = []
107
+
108
+ if file_path.suffix not in _CODE_EXTENSIONS:
109
+ return findings
110
+
111
+ lines = content.splitlines()
112
+ rel_path = str(file_path)
113
+ is_python = file_path.suffix == ".py"
114
+ is_js_ts = file_path.suffix in {".ts", ".tsx", ".js", ".jsx"}
115
+
116
+ for i, line in enumerate(lines, 1):
117
+ stripped = line.strip()
118
+
119
+ # Skip comments
120
+ if stripped.startswith("#") or stripped.startswith("//") or stripped.startswith("*"):
121
+ continue
122
+
123
+ # Off-by-one patterns
124
+ for pattern, desc in _OFF_BY_ONE_PATTERNS:
125
+ if pattern.search(line):
126
+ findings.append(TrustIssue(
127
+ analyzer=cls.name,
128
+ category=IssueCategory.CORRECTNESS,
129
+ severity=Severity.HIGH,
130
+ title="Potential off-by-one error",
131
+ description=desc,
132
+ file_path=rel_path,
133
+ line_start=i,
134
+ line_end=i,
135
+ suggestion="Check array/list bounds carefully. Last valid index is length-1.",
136
+ code_snippet=stripped[:200],
137
+ ))
138
+ break
139
+
140
+ # Null check patterns
141
+ for pattern, desc in _NULL_CHECK_PATTERNS:
142
+ if pattern.search(line):
143
+ findings.append(TrustIssue(
144
+ analyzer=cls.name,
145
+ category=IssueCategory.CORRECTNESS,
146
+ severity=Severity.MEDIUM,
147
+ title="Missing null/undefined check",
148
+ description=desc,
149
+ file_path=rel_path,
150
+ line_start=i,
151
+ line_end=i,
152
+ suggestion="Add null checks or use optional chaining (?.) before accessing nested properties.",
153
+ code_snippet=stripped[:200],
154
+ ))
155
+ break
156
+
157
+ # Type mismatch patterns
158
+ for pattern, desc in _TYPE_MISMATCH_PATTERNS:
159
+ if pattern.search(line):
160
+ findings.append(TrustIssue(
161
+ analyzer=cls.name,
162
+ category=IssueCategory.CORRECTNESS,
163
+ severity=Severity.MEDIUM,
164
+ title="Potential type mismatch",
165
+ description=desc,
166
+ file_path=rel_path,
167
+ line_start=i,
168
+ line_end=i,
169
+ suggestion="Ensure types match before comparison. Use explicit conversion.",
170
+ code_snippet=stripped[:200],
171
+ ))
172
+ break
173
+
174
+ # Unreachable code (if True / if False)
175
+ if re.search(r'if\s+True\s*:', stripped):
176
+ findings.append(TrustIssue(
177
+ analyzer=cls.name,
178
+ category=IssueCategory.CORRECTNESS,
179
+ severity=Severity.LOW,
180
+ title="Always-true condition",
181
+ description="Condition is always True -- dead code branch or debugging leftover",
182
+ file_path=rel_path,
183
+ line_start=i,
184
+ line_end=i,
185
+ suggestion="Remove the always-true condition or replace with the actual logic.",
186
+ code_snippet=stripped[:200],
187
+ ))
188
+ elif re.search(r'if\s+False\s*:', stripped):
189
+ findings.append(TrustIssue(
190
+ analyzer=cls.name,
191
+ category=IssueCategory.CORRECTNESS,
192
+ severity=Severity.MEDIUM,
193
+ title="Unreachable code block",
194
+ description="Condition is always False -- code block is unreachable",
195
+ file_path=rel_path,
196
+ line_start=i,
197
+ line_end=i,
198
+ suggestion="Remove the dead code block.",
199
+ code_snippet=stripped[:200],
200
+ ))
201
+
202
+ # Error handling -- Python
203
+ if is_python:
204
+ if re.match(r'except\s*:', stripped):
205
+ findings.append(TrustIssue(
206
+ analyzer=cls.name,
207
+ category=IssueCategory.CORRECTNESS,
208
+ severity=Severity.HIGH,
209
+ title="Bare except clause",
210
+ description="Bare except catches all exceptions including SystemExit and KeyboardInterrupt",
211
+ file_path=rel_path,
212
+ line_start=i,
213
+ line_end=i,
214
+ suggestion="Use 'except Exception:' to avoid catching system-level exceptions.",
215
+ code_snippet=stripped[:200],
216
+ ))
217
+
218
+ # Error handling -- JS/TS
219
+ if is_js_ts:
220
+ for pattern, desc in _ERROR_HANDLING_PATTERNS_JS:
221
+ if pattern.search(line):
222
+ findings.append(TrustIssue(
223
+ analyzer=cls.name,
224
+ category=IssueCategory.CORRECTNESS,
225
+ severity=Severity.MEDIUM,
226
+ title="Error handling gap",
227
+ description=desc,
228
+ file_path=rel_path,
229
+ line_start=i,
230
+ line_end=i,
231
+ suggestion="Handle errors properly: log, re-throw, or return an error state.",
232
+ code_snippet=stripped[:200],
233
+ ))
234
+ break
235
+
236
+ # Multi-line checks: except ... pass pattern
237
+ if is_python:
238
+ findings.extend(cls._check_swallowed_exceptions(rel_path, content, lines))
239
+
240
+ # Return type consistency
241
+ if is_python:
242
+ findings.extend(cls._check_return_consistency(rel_path, content, lines))
243
+
244
+ return findings
245
+
246
+ @classmethod
247
+ def _check_swallowed_exceptions(
248
+ cls, rel_path: str, content: str, lines: list[str]
249
+ ) -> list[TrustIssue]:
250
+ """Detect except blocks that silently swallow errors with pass."""
251
+ findings: list[TrustIssue] = []
252
+ for i, line in enumerate(lines, 1):
253
+ stripped = line.strip()
254
+ if re.match(r'except\s+\w+', stripped) and i < len(lines):
255
+ next_line = lines[i].strip() if i < len(lines) else ""
256
+ if next_line == "pass":
257
+ findings.append(TrustIssue(
258
+ analyzer=cls.name,
259
+ category=IssueCategory.CORRECTNESS,
260
+ severity=Severity.MEDIUM,
261
+ title="Swallowed exception",
262
+ description="Exception caught but silently ignored with pass -- errors will be hidden",
263
+ file_path=rel_path,
264
+ line_start=i,
265
+ line_end=i + 1,
266
+ suggestion="At minimum, log the exception. Consider re-raising or returning an error.",
267
+ code_snippet=stripped[:200],
268
+ ))
269
+ return findings
270
+
271
+ @classmethod
272
+ def _check_return_consistency(
273
+ cls, rel_path: str, content: str, lines: list[str]
274
+ ) -> list[TrustIssue]:
275
+ """Detect functions with inconsistent return types (value + bare return)."""
276
+ findings: list[TrustIssue] = []
277
+ func_pattern = re.compile(r'^(\s*)def\s+(\w+)')
278
+ in_func = False
279
+ func_name = ""
280
+ func_indent = ""
281
+ func_line = 0
282
+ has_value_return = False
283
+ has_bare_return = False
284
+
285
+ for i, line in enumerate(lines, 1):
286
+ m = func_pattern.match(line)
287
+ if m:
288
+ # Evaluate previous function
289
+ if in_func and has_value_return and has_bare_return:
290
+ findings.append(TrustIssue(
291
+ analyzer=cls.name,
292
+ category=IssueCategory.CORRECTNESS,
293
+ severity=Severity.MEDIUM,
294
+ title="Inconsistent return type",
295
+ description=f"Function '{func_name}' has both value-returning and bare return statements",
296
+ file_path=rel_path,
297
+ line_start=func_line,
298
+ line_end=i - 1,
299
+ suggestion="Ensure all return paths return the same type or use Optional.",
300
+ ))
301
+ in_func = True
302
+ func_name = m.group(2)
303
+ func_indent = m.group(1)
304
+ func_line = i
305
+ has_value_return = False
306
+ has_bare_return = False
307
+ continue
308
+
309
+ if in_func:
310
+ stripped = line.strip()
311
+ if stripped.startswith("return ") and stripped != "return":
312
+ has_value_return = True
313
+ elif stripped == "return":
314
+ has_bare_return = True
315
+
316
+ # Check last function
317
+ if in_func and has_value_return and has_bare_return:
318
+ findings.append(TrustIssue(
319
+ analyzer=cls.name,
320
+ category=IssueCategory.CORRECTNESS,
321
+ severity=Severity.MEDIUM,
322
+ title="Inconsistent return type",
323
+ description=f"Function '{func_name}' has both value-returning and bare return statements",
324
+ file_path=rel_path,
325
+ line_start=func_line,
326
+ line_end=len(lines),
327
+ suggestion="Ensure all return paths return the same type or use Optional.",
328
+ ))
329
+
330
+ return findings