aicheck 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.
aicheck/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """aicheck — Catch AI-generated code issues before they catch you."""
2
+
3
+ __version__ = "0.1.0"
aicheck/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from aicheck.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
aicheck/analyzer.py ADDED
@@ -0,0 +1,91 @@
1
+ import ast
2
+ import importlib
3
+ import sys
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from pathlib import Path
6
+ from typing import Protocol
7
+
8
+ import requests
9
+
10
+ from aicheck.detectors.api_usage import ApiUsageDetector
11
+ from aicheck.detectors.dead_code import DeadCodeDetector
12
+ from aicheck.detectors.hallucination import HallucinationDetector
13
+ from aicheck.models import FileResult, Finding
14
+
15
+
16
+ class DetectorProtocol(Protocol):
17
+ def check(self, tree: ast.AST, source: str) -> list[Finding]:
18
+ ...
19
+
20
+
21
+ class Analyzer:
22
+ def __init__(self, timeout: int = 5):
23
+ self.timeout = timeout
24
+ self._detectors: list[DetectorProtocol] = [
25
+ HallucinationDetector(),
26
+ DeadCodeDetector(),
27
+ ApiUsageDetector(),
28
+ ]
29
+
30
+ def check_file(self, path: Path) -> FileResult:
31
+ with path.open(encoding="utf-8", errors="replace") as f:
32
+ source = f.read()
33
+ try:
34
+ tree = ast.parse(source, filename=str(path))
35
+ except SyntaxError:
36
+ return FileResult(path=path)
37
+ result = FileResult(path=path)
38
+ for detector in self._detectors:
39
+ result.findings.extend(detector.check(tree, source))
40
+ return result
41
+
42
+ def check_path(self, path: Path) -> list[FileResult]:
43
+ if path.is_file() and path.suffix == ".py":
44
+ return [self.check_file(path)]
45
+ results: list[FileResult] = []
46
+ files = list(path.rglob("*.py"))
47
+ with ThreadPoolExecutor(max_workers=8) as pool:
48
+ for r in pool.map(self.check_file, files):
49
+ results.append(r)
50
+ return results
51
+
52
+ @staticmethod
53
+ def known_modules() -> set[str]:
54
+ known = set(sys.builtin_module_names)
55
+ for m in list(sys.modules.keys()):
56
+ parts = m.split(".")
57
+ for i in range(len(parts)):
58
+ known.add(".".join(parts[: i + 1]))
59
+ known.update({
60
+ "os", "sys", "re", "json", "math", "datetime", "pathlib",
61
+ "collections", "functools", "itertools", "typing", "abc",
62
+ "dataclasses", "enum", "hashlib", "random", "statistics",
63
+ "uuid", "inspect", "textwrap", "string", "decimal", "fractions",
64
+ "io", "base64", "binascii", "pickle", "shelve", "sqlite3",
65
+ "csv", "configparser", "logging", "argparse", "subprocess",
66
+ "shutil", "tempfile", "glob", "fnmatch", "linecache",
67
+ "asyncio", "threading", "multiprocessing", "concurrent",
68
+ "http", "urllib", "socket", "ssl", "email", "xml", "html",
69
+ "unittest", "doctest", "pdb", "profile", "timeit",
70
+ "warnings", "contextlib", "atexit", "weakref", "copy",
71
+ "pprint", "reprlib", "enum", "numbers", "stat",
72
+ "calendar", "locale", "gettext", "platform",
73
+ })
74
+ return known
75
+
76
+
77
+ def verify_import(name: str, timeout: int = 3) -> bool:
78
+ top = name.split(".")[0]
79
+ try:
80
+ importlib.import_module(top)
81
+ return True
82
+ except ImportError:
83
+ pass
84
+ try:
85
+ resp = requests.get(
86
+ f"https://pypi.org/pypi/{top}/json",
87
+ timeout=timeout,
88
+ )
89
+ return bool(resp.status_code == 200)
90
+ except requests.RequestException:
91
+ return False
aicheck/cli.py ADDED
@@ -0,0 +1,53 @@
1
+ import argparse
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ from aicheck.analyzer import Analyzer
6
+ from aicheck.reporters.console import get_reporter
7
+
8
+
9
+ def build_parser() -> argparse.ArgumentParser:
10
+ parser = argparse.ArgumentParser(
11
+ prog="aicheck",
12
+ description="Catch AI-generated code issues before they catch you",
13
+ )
14
+ sub = parser.add_subparsers(dest="command")
15
+
16
+ check = sub.add_parser("check", help="Analyze Python files for AI code issues")
17
+ check.add_argument("path", type=str, nargs="?", default=".",
18
+ help="File or directory to check")
19
+ check.add_argument("--format", choices=["console", "json"], default="console",
20
+ help="Output format")
21
+
22
+ sub.add_parser("version", help="Show version")
23
+ return parser
24
+
25
+
26
+ def main(argv: list[str] | None = None) -> int:
27
+ parser = build_parser()
28
+ args = parser.parse_args(argv)
29
+
30
+ if args.command == "version" or not args.command:
31
+ from aicheck import __version__
32
+ print(f"aicheck v{__version__}")
33
+ return 0
34
+
35
+ if args.command == "check":
36
+ target = Path(args.path).resolve()
37
+ if not target.exists():
38
+ print(f"Error: path '{target}' does not exist", file=sys.stderr)
39
+ return 1
40
+ analyzer = Analyzer()
41
+ results = analyzer.check_path(target)
42
+ reporter = get_reporter(args.format)
43
+ output = reporter.report(results)
44
+ print(output)
45
+ all_passed = all(r.passed for r in results)
46
+ return 0 if all_passed else 1
47
+
48
+ parser.print_help()
49
+ return 0
50
+
51
+
52
+ if __name__ == "__main__":
53
+ sys.exit(main())
File without changes
@@ -0,0 +1,76 @@
1
+ import ast
2
+ import sys
3
+
4
+ from aicheck.models import Finding, FindingKind, Severity
5
+
6
+
7
+ class ApiUsageDetector:
8
+ FAKE_FUNCTIONS: dict[str, set[str]] = {
9
+ "os": {"path.join"},
10
+ "sys": {"argv"},
11
+ "re": {"search", "match", "findall", "sub", "split"},
12
+ "json": {"dumps", "loads", "dump", "load"},
13
+ "typing": {"List", "Dict", "Optional", "Tuple", "Union", "Any"},
14
+ }
15
+
16
+ REAL_STDLIB_FUNCTIONS: set[str] = set()
17
+
18
+ def __init__(self) -> None:
19
+ if not self.REAL_STDLIB_FUNCTIONS:
20
+ self._build_stdlib_index()
21
+
22
+ @staticmethod
23
+ def _build_stdlib_index() -> None:
24
+ known: set[str] = set()
25
+ for mod_name in sys.stdlib_module_names:
26
+ try:
27
+ mod = __import__(mod_name)
28
+ for attr in dir(mod):
29
+ known.add(f"{mod_name}.{attr}")
30
+ except (ImportError, AttributeError):
31
+ pass
32
+ ApiUsageDetector.REAL_STDLIB_FUNCTIONS = known
33
+
34
+ def check(self, tree: ast.AST, source: str) -> list[Finding]:
35
+ findings: list[Finding] = []
36
+ self._check_wrong_stdlib_methods(tree, findings)
37
+ self._check_common_mistakes(tree, findings)
38
+ return findings
39
+
40
+ @staticmethod
41
+ def _check_wrong_stdlib_methods(tree: ast.AST, findings: list[Finding]) -> None:
42
+ for node in ast.walk(tree):
43
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
44
+ if isinstance(node.func.value, ast.Name):
45
+ mod = node.func.value.id
46
+ func = f"{mod}.{node.func.attr}"
47
+ if mod in ("os", "sys", "json", "re", "typing"):
48
+ expected = ApiUsageDetector.FAKE_FUNCTIONS.get(mod, set())
49
+ if node.func.attr not in expected:
50
+ findings.append(Finding(
51
+ kind=FindingKind.SUSPICIOUS_API,
52
+ message=f"Suspicious API call: '{func}' — "
53
+ f"uncommon for module '{mod}'",
54
+ line=node.lineno or 0,
55
+ column=node.col_offset or 0,
56
+ severity=Severity.MEDIUM,
57
+ suggestion=f"Verify '{func}' is a valid {mod} function",
58
+ ))
59
+
60
+ @staticmethod
61
+ def _check_common_mistakes(tree: ast.AST, findings: list[Finding]) -> None:
62
+ for node in ast.walk(tree):
63
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
64
+ if isinstance(node.func.value, ast.Name):
65
+ if node.func.value.id == "open" and node.func.attr == "read":
66
+ findings.append(Finding(
67
+ kind=FindingKind.SUSPICIOUS_API,
68
+ message=(
69
+ "Suspicious: 'open(...).read()' — "
70
+ "use 'pathlib.Path.read_text()' instead"
71
+ ),
72
+ line=node.lineno or 0,
73
+ column=node.col_offset or 0,
74
+ severity=Severity.LOW,
75
+ suggestion="Replace with Path.read_text()",
76
+ ))
@@ -0,0 +1,65 @@
1
+ import ast
2
+
3
+ from aicheck.models import Finding, FindingKind, Severity
4
+
5
+
6
+ class DeadCodeDetector:
7
+ def check(self, tree: ast.AST, source: str) -> list[Finding]:
8
+ findings: list[Finding] = []
9
+ self._check_unreachable(tree, findings)
10
+ self._check_unused_vars(tree, findings)
11
+ self._check_dead_after_return(tree, findings)
12
+ return findings
13
+
14
+ @staticmethod
15
+ def _check_unreachable(tree: ast.AST, findings: list[Finding]) -> None:
16
+ for node in ast.walk(tree):
17
+ if isinstance(node, ast.If):
18
+ if isinstance(node.test, ast.Constant) and node.test.value is False:
19
+ findings.append(Finding(
20
+ kind=FindingKind.UNREACHABLE_CODE,
21
+ message="Unreachable branch: condition is always False",
22
+ line=node.lineno or 0,
23
+ column=node.col_offset or 0,
24
+ severity=Severity.MEDIUM,
25
+ suggestion="Remove dead branch or update condition",
26
+ ))
27
+
28
+ @staticmethod
29
+ def _check_unused_vars(tree: ast.AST, findings: list[Finding]) -> None:
30
+ assigns: dict[str, ast.Name] = {}
31
+ for node in ast.walk(tree):
32
+ if isinstance(node, ast.Assign):
33
+ for t in node.targets:
34
+ if isinstance(t, ast.Name) and not t.id.startswith("_"):
35
+ assigns[t.id] = t
36
+ for var, node in assigns.items():
37
+ count = sum(1 for n in ast.walk(tree)
38
+ if isinstance(n, ast.Name) and n.id == var)
39
+ if count <= 1:
40
+ findings.append(Finding(
41
+ kind=FindingKind.DEAD_CODE,
42
+ message=f"Possibly unused variable: '{var}' (assigned but never read)",
43
+ line=node.lineno or 0,
44
+ column=node.col_offset or 0,
45
+ severity=Severity.LOW,
46
+ suggestion=f"Remove '{var}' or use it elsewhere",
47
+ ))
48
+
49
+ @staticmethod
50
+ def _check_dead_after_return(tree: ast.AST, findings: list[Finding]) -> None:
51
+ for node in ast.walk(tree):
52
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
53
+ stmts = node.body
54
+ for i, stmt in enumerate(stmts[:-1]):
55
+ if isinstance(stmt, (ast.Return, ast.Raise)):
56
+ next_stmt = stmts[i + 1]
57
+ findings.append(Finding(
58
+ kind=FindingKind.DEAD_CODE,
59
+ message=f"Dead code after {type(stmt).__name__.lower()} "
60
+ f"on line {next_stmt.lineno}",
61
+ line=next_stmt.lineno or 0,
62
+ column=next_stmt.col_offset or 0,
63
+ severity=Severity.MEDIUM,
64
+ suggestion="Remove unreachable statement or reorder logic",
65
+ ))
@@ -0,0 +1,44 @@
1
+ import ast
2
+
3
+ from aicheck.models import Finding, FindingKind, Severity
4
+
5
+
6
+ class HallucinationDetector:
7
+ FAKE_STDLIB_MODULES: set[str] = {
8
+ "utils", "helpers", "common", "tools", "misc",
9
+ "extras", "general", "core", "base", "utilities",
10
+ "datatools", "fileutils", "strutils", "validators",
11
+ }
12
+
13
+ def check(self, tree: ast.AST, source: str) -> list[Finding]:
14
+ findings: list[Finding] = []
15
+ for node in ast.walk(tree):
16
+ if isinstance(node, ast.Import):
17
+ for alias in node.names:
18
+ top = alias.name.split(".")[0]
19
+ if top in self.FAKE_STDLIB_MODULES:
20
+ findings.append(Finding(
21
+ kind=FindingKind.HALLUCINATED_IMPORT,
22
+ message=f"Potentially hallucinated module: '{alias.name}' — "
23
+ f"'{top}' is a common LLM-invented name",
24
+ line=node.lineno or 0,
25
+ column=node.col_offset or 0,
26
+ severity=Severity.HIGH,
27
+ suggestion=(
28
+ f"Verify '{alias.name}' exists; "
29
+ "check PyPI or project dependencies"
30
+ ),
31
+ ))
32
+ elif isinstance(node, ast.ImportFrom):
33
+ if node.module:
34
+ top = node.module.split(".")[0]
35
+ if top in self.FAKE_STDLIB_MODULES and node.level is None:
36
+ findings.append(Finding(
37
+ kind=FindingKind.FAKE_STDLIB,
38
+ message=f"Import from potentially fake module: '{node.module}'",
39
+ line=node.lineno or 0,
40
+ column=node.col_offset or 0,
41
+ severity=Severity.HIGH,
42
+ suggestion=f"Ensure '{node.module}' is a real package",
43
+ ))
44
+ return findings
aicheck/models.py ADDED
@@ -0,0 +1,51 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import Enum
3
+ from pathlib import Path
4
+
5
+
6
+ class Severity(Enum):
7
+ LOW = "low"
8
+ MEDIUM = "medium"
9
+ HIGH = "high"
10
+ CRITICAL = "critical"
11
+
12
+
13
+ class FindingKind(Enum):
14
+ HALLUCINATED_IMPORT = "hallucinated_import"
15
+ DEAD_CODE = "dead_code"
16
+ SUSPICIOUS_API = "suspicious_api"
17
+ UNREACHABLE_CODE = "unreachable_code"
18
+ FAKE_STDLIB = "fake_stdlib"
19
+
20
+
21
+ @dataclass
22
+ class Finding:
23
+ kind: FindingKind
24
+ message: str
25
+ line: int
26
+ column: int
27
+ severity: Severity
28
+ suggestion: str = ""
29
+
30
+
31
+ @dataclass
32
+ class FileResult:
33
+ path: Path
34
+ findings: list[Finding] = field(default_factory=list)
35
+
36
+ @property
37
+ def score(self) -> float:
38
+ if not self.findings:
39
+ return 100.0
40
+ penalties = {
41
+ Severity.LOW: 2,
42
+ Severity.MEDIUM: 5,
43
+ Severity.HIGH: 15,
44
+ Severity.CRITICAL: 30,
45
+ }
46
+ total_penalty = sum(penalties[f.severity] for f in self.findings)
47
+ return max(0.0, 100.0 - total_penalty)
48
+
49
+ @property
50
+ def passed(self) -> bool:
51
+ return self.score >= 70.0
File without changes
@@ -0,0 +1,65 @@
1
+ import json
2
+
3
+ from aicheck.models import FileResult
4
+
5
+
6
+ class ConsoleReporter:
7
+ def report(self, results: list[FileResult]) -> str:
8
+ lines: list[str] = []
9
+ passed = 0
10
+ failed = 0
11
+ for r in results:
12
+ status = "PASS" if r.passed else "FAIL"
13
+ if r.passed:
14
+ passed += 1
15
+ else:
16
+ failed += 1
17
+ lines.append(f"[{status}] {r.path} (score: {r.score:.1f})")
18
+ for f in r.findings:
19
+ lines.append(
20
+ f" {f.severity.value:>8} L{f.line}:{f.column} "
21
+ f"[{f.kind.value}] {f.message}"
22
+ )
23
+ if f.suggestion:
24
+ lines.append(f" \u21b3 {f.suggestion}")
25
+ if not results:
26
+ lines.append("No Python files found.")
27
+ lines.append("")
28
+ total = len(results)
29
+ lines.append(f"Files: {total} Passed: {passed} Failed: {failed}")
30
+ if total:
31
+ avg = sum(r.score for r in results) / total
32
+ lines.append(f"Average confidence score: {avg:.1f}/100")
33
+ return "\n".join(lines)
34
+
35
+
36
+ class JsonReporter:
37
+ def report(self, results: list[FileResult]) -> str:
38
+ data = []
39
+ for r in results:
40
+ data.append({
41
+ "path": str(r.path),
42
+ "score": r.score,
43
+ "passed": r.passed,
44
+ "findings": [
45
+ {
46
+ "kind": f.kind.value,
47
+ "severity": f.severity.value,
48
+ "line": f.line,
49
+ "column": f.column,
50
+ "message": f.message,
51
+ "suggestion": f.suggestion,
52
+ }
53
+ for f in r.findings
54
+ ],
55
+ })
56
+ return json.dumps({"files": data}, indent=2)
57
+
58
+
59
+ def get_reporter(format: str) -> ConsoleReporter | JsonReporter:
60
+ reporters: dict[str, type[ConsoleReporter] | type[JsonReporter]] = {
61
+ "console": ConsoleReporter,
62
+ "json": JsonReporter,
63
+ }
64
+ cls = reporters.get(format, ConsoleReporter)
65
+ return cls()
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: aicheck
3
+ Version: 0.1.0
4
+ Summary: Catch AI-generated code issues before they catch you
5
+ Author-email: Mahesh Makvana <maheshmakvana@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/maheshmakvana/ai-check
8
+ Project-URL: Repository, https://github.com/maheshmakvana/ai-check
9
+ Project-URL: BugTracker, https://github.com/maheshmakvana/ai-check/issues
10
+ Keywords: ai verification,code review,hallucination detection,llm code,ai code quality,code analysis,static analysis,python
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: requests>=2.31.0
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=7.0; extra == "test"
27
+ Requires-Dist: ruff>=0.3.0; extra == "test"
28
+ Requires-Dist: mypy>=1.8.0; extra == "test"
29
+ Dynamic: license-file
30
+
31
+ # aicheck
32
+
33
+ **Catch AI-generated code issues before they catch you.**
34
+
35
+ `aicheck` is a static analysis toolkit that detects common failure patterns in AI-generated Python code:
36
+
37
+ - **Hallucinated imports** — modules LLMs frequently invent (e.g. `utils`, `helpers`, `misc`)
38
+ - **Dead code** — unused variables, unreachable branches, code after `return`/`raise`
39
+ - **Suspicious API usage** — wrong method names for stdlib modules, `open().read()` patterns
40
+ - **Confidence scoring** — each file gets a 0–100 score based on findings severity
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install aicheck
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ```bash
51
+ # Check a single file
52
+ aicheck check my_file.py
53
+
54
+ # Check an entire project
55
+ aicheck check src/
56
+
57
+ # JSON output for CI integration
58
+ aicheck check src/ --format json
59
+ ```
60
+
61
+ ## Sample Output
62
+
63
+ ```
64
+ [FAIL] src/suspicious.py (score: 62.0)
65
+ high L1:0 [hallucinated_import] Potentially hallucinated module: 'utils'
66
+ ↳ Verify 'utils' exists; check PyPI or project dependencies
67
+ medium L14:4 [unreachable_code] Unreachable branch: condition is always False
68
+ low L11:4 [dead_code] Possibly unused variable: 'unused_var'
69
+
70
+ Files: 1 Passed: 0 Failed: 1
71
+ Average confidence score: 62.0/100
72
+ ```
73
+
74
+ ## CLI Reference
75
+
76
+ | Command | Description |
77
+ |---|---|
78
+ | `aicheck check <path>` | Analyze a file or directory |
79
+ | `aicheck check <path> --format json` | Output as JSON |
80
+ | `aicheck version` | Show version |
81
+
82
+ ## Score Interpretation
83
+
84
+ | Score | Meaning |
85
+ |---|---|
86
+ | 100–90 | Clean |
87
+ | 89–70 | Minor issues |
88
+ | 69–50 | Moderate issues — review recommended |
89
+ | <50 | Critical — do not commit without review |
90
+
91
+ ## Development
92
+
93
+ ```bash
94
+ git clone https://github.com/maheshmakvana/ai-check.git
95
+ cd ai-check
96
+ python -m venv venv && source venv/bin/activate
97
+ pip install -e ".[test]"
98
+ pytest tests/ -v
99
+ ruff check src/
100
+ mypy src/
101
+ ```
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,17 @@
1
+ aicheck/__init__.py,sha256=Nou2fyj0YdJwS0Ac7uylkqMsewiW2PBU6-U_DJ7zwYk,95
2
+ aicheck/__main__.py,sha256=qO9J--ZracllvI82Yvm5VbPwJ6HMOvQwr16xMr0Qr2w,68
3
+ aicheck/analyzer.py,sha256=3b_FLWM_slOlgs14m03O7l92i1GS4GELIgU5fJEnZw0,3239
4
+ aicheck/cli.py,sha256=qCSW1Z0bMzo3f4D9p1WrFhSkrR5ZH8LOXTwNMhNBlzA,1624
5
+ aicheck/models.py,sha256=EF49aSmTvdwHT1PKXAaUV3ghtRXWv04lWtoO8UgA2yQ,1117
6
+ aicheck/detectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ aicheck/detectors/api_usage.py,sha256=cIDRpHcoMwpRtguLDPSzSL-15QhLyB9XNcM5KAbIJeg,3302
8
+ aicheck/detectors/dead_code.py,sha256=4CumefKRCrLmXYWi7RvLslBc_NfiCKyVSE29grg9v-4,2992
9
+ aicheck/detectors/hallucination.py,sha256=I5E047KdgsR6jMcBRFQgqMkFojiD6lk_yGcycKTh3aI,2039
10
+ aicheck/reporters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ aicheck/reporters/console.py,sha256=cGJe6PxLudbjmMjPelj-_hHLHPdTTtpW7eKqVhz-zFU,2153
12
+ aicheck-0.1.0.dist-info/licenses/LICENSE,sha256=0zsWw8F2DlZR_O7d0Q5nbKLHMtR6TLFmmPg2ydwiQhw,1070
13
+ aicheck-0.1.0.dist-info/METADATA,sha256=tE5Sd2UFAyoABvuk9X1yy_heBUWPHaDXIEpREN8izKk,3302
14
+ aicheck-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
+ aicheck-0.1.0.dist-info/entry_points.txt,sha256=LtifEuMHrM2bGF9CI2cr-3p3neHfBYN7DIpK-MdvuaQ,45
16
+ aicheck-0.1.0.dist-info/top_level.txt,sha256=YudTcArgQTnZ2GWpH5h5UAvkbcyXBMNVhuWXDmqI8F4,8
17
+ aicheck-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ aicheck = aicheck.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mahesh Makvana
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ aicheck