suitable-loop 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.
- suitable_loop/__init__.py +3 -0
- suitable_loop/__main__.py +5 -0
- suitable_loop/analyzers/__init__.py +1 -0
- suitable_loop/analyzers/code_analyzer.py +652 -0
- suitable_loop/analyzers/git_analyzer.py +510 -0
- suitable_loop/analyzers/log_analyzer.py +663 -0
- suitable_loop/config.py +60 -0
- suitable_loop/db.py +497 -0
- suitable_loop/graph/__init__.py +1 -0
- suitable_loop/graph/engine.py +341 -0
- suitable_loop/models.py +131 -0
- suitable_loop/server.py +46 -0
- suitable_loop/tools/__init__.py +1 -0
- suitable_loop/tools/code_tools.py +104 -0
- suitable_loop/tools/git_tools.py +52 -0
- suitable_loop/tools/log_tools.py +53 -0
- suitable_loop/tools/util_tools.py +49 -0
- suitable_loop-0.1.0.dist-info/METADATA +12 -0
- suitable_loop-0.1.0.dist-info/RECORD +21 -0
- suitable_loop-0.1.0.dist-info/WHEEL +4 -0
- suitable_loop-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""MCP tool handlers for log analysis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from mcp.server.fastmcp import FastMCP
|
|
8
|
+
|
|
9
|
+
from ..analyzers.log_analyzer import LogAnalyzer
|
|
10
|
+
from ..config import SuitableLoopConfig
|
|
11
|
+
from ..db import Database
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def register_log_tools(mcp: FastMCP, db: Database, config: SuitableLoopConfig):
|
|
17
|
+
analyzer = LogAnalyzer(db, config)
|
|
18
|
+
|
|
19
|
+
@mcp.tool()
|
|
20
|
+
def ingest_logs(path: str) -> dict:
|
|
21
|
+
"""Parse and store a log file or directory of log files. Auto-detects format
|
|
22
|
+
(Python stdlib logging or JSON-per-line). Extracts errors, groups them by
|
|
23
|
+
signature, and maps stack frames to indexed code."""
|
|
24
|
+
return analyzer.ingest_logs(path)
|
|
25
|
+
|
|
26
|
+
@mcp.tool()
|
|
27
|
+
def get_error_groups(limit: int = 20) -> dict:
|
|
28
|
+
"""List all distinct error groups sorted by frequency. Each group includes
|
|
29
|
+
exception type, occurrence count, and links to affected code."""
|
|
30
|
+
groups = analyzer.get_error_groups(limit=limit)
|
|
31
|
+
return {"error_group_count": len(groups), "error_groups": groups}
|
|
32
|
+
|
|
33
|
+
@mcp.tool()
|
|
34
|
+
def error_detail(error_group_id: int) -> dict:
|
|
35
|
+
"""Get full detail on an error group: traceback, affected functions,
|
|
36
|
+
sample log entries, and timeline."""
|
|
37
|
+
detail = analyzer.error_detail(error_group_id)
|
|
38
|
+
if not detail:
|
|
39
|
+
return {"error": f"Error group {error_group_id} not found."}
|
|
40
|
+
return detail
|
|
41
|
+
|
|
42
|
+
@mcp.tool()
|
|
43
|
+
def correlate_error(error_text: str) -> dict:
|
|
44
|
+
"""Given a raw error or traceback text, find the relevant code paths in the
|
|
45
|
+
indexed codebase. Useful for investigating production errors."""
|
|
46
|
+
return analyzer.correlate_error(error_text)
|
|
47
|
+
|
|
48
|
+
@mcp.tool()
|
|
49
|
+
def error_timeline(days: int = 7) -> dict:
|
|
50
|
+
"""Show error frequency over time, grouped by error type. Useful for spotting
|
|
51
|
+
trends and regressions."""
|
|
52
|
+
timeline = analyzer.error_timeline(days=days)
|
|
53
|
+
return {"days": days, "timeline": timeline}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""MCP tool handlers for utility operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from mcp.server.fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from ..analyzers.code_analyzer import CodeAnalyzer
|
|
11
|
+
from ..config import SuitableLoopConfig
|
|
12
|
+
from ..db import Database
|
|
13
|
+
from ..graph.engine import GraphEngine
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register_util_tools(mcp: FastMCP, db: Database, config: SuitableLoopConfig):
|
|
19
|
+
analyzer = CodeAnalyzer(db, config)
|
|
20
|
+
graph = GraphEngine(db)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def status() -> dict:
|
|
24
|
+
"""Get Suitable Loop server status: indexed projects, database size, entity counts,
|
|
25
|
+
and last index time."""
|
|
26
|
+
stats = db.get_stats()
|
|
27
|
+
return {
|
|
28
|
+
"status": "running",
|
|
29
|
+
"db_path": str(config.db_path),
|
|
30
|
+
"db_size_bytes": stats.get("db_size_bytes", 0),
|
|
31
|
+
"entities": {
|
|
32
|
+
"files": stats.get("files", 0),
|
|
33
|
+
"functions": stats.get("functions", 0),
|
|
34
|
+
"classes": stats.get("classes", 0),
|
|
35
|
+
"call_edges": stats.get("call_edges", 0),
|
|
36
|
+
"commits": stats.get("commits", 0),
|
|
37
|
+
"error_groups": stats.get("error_groups", 0),
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
def reindex(path: str) -> dict:
|
|
43
|
+
"""Re-index only changed files in a project (uses content hashing to detect changes).
|
|
44
|
+
Faster than a full index_codebase with force=True."""
|
|
45
|
+
start = time.time()
|
|
46
|
+
result = analyzer.index_codebase(path, force=False)
|
|
47
|
+
graph.build_graph(path)
|
|
48
|
+
result["duration_seconds"] = round(time.time() - start, 2)
|
|
49
|
+
return result
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: suitable-loop
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local production engineering platform — semantic code analysis, git risk scoring, and log correlation via MCP
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: gitpython>=3.1
|
|
7
|
+
Requires-Dist: mcp>=1.0.0
|
|
8
|
+
Requires-Dist: networkx>=3.0
|
|
9
|
+
Requires-Dist: radon>=6.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
suitable_loop/__init__.py,sha256=f3tlDiLb6PLDEsHsQPkXb_NVu4ELDpbdAB6RXYRvwVQ,86
|
|
2
|
+
suitable_loop/__main__.py,sha256=SuLtzOjn4Y_YKA1BW5ZSQkqVPxTiYVt2yqUb7PFT_Yg,83
|
|
3
|
+
suitable_loop/config.py,sha256=ho8H-i6T6s-5PjSqT20rayNwZt2Ke7bEwFG5jT83wP4,1639
|
|
4
|
+
suitable_loop/db.py,sha256=k-l1Or8hA-yfgVj9BOFUBBaSVN4-eCxIBf16z71eN8c,18593
|
|
5
|
+
suitable_loop/models.py,sha256=bmydJcoVkrfgS64jFXzJqQYzKACUa5GG-0Jkj4YvpMg,2749
|
|
6
|
+
suitable_loop/server.py,sha256=38-x-2TCAfIb3s6v09MH6ke7yLI5YabBP4BPhlJtF2o,1156
|
|
7
|
+
suitable_loop/analyzers/__init__.py,sha256=-XHcZF2qn3Jh-NPJIX2z-m7oa5h_1-JiSHu6bHKtGsM,38
|
|
8
|
+
suitable_loop/analyzers/code_analyzer.py,sha256=M56qDHg4zF50_2j05t3ZCLk-GPqdpXQ8l-KXUf758Ag,23927
|
|
9
|
+
suitable_loop/analyzers/git_analyzer.py,sha256=oBqVMkoq5yZdY1462o_dFJT0KFTcXqQRMvpts4JqpCc,19716
|
|
10
|
+
suitable_loop/analyzers/log_analyzer.py,sha256=hiZxFhEgd_9q8Y-I0iM5cFkgJOY8Rh1g2Mp-l7TCGYU,24705
|
|
11
|
+
suitable_loop/graph/__init__.py,sha256=mo1pUt0o0cKWQAZL4kqD9mXEthcv26GOK9PcXOYydns,34
|
|
12
|
+
suitable_loop/graph/engine.py,sha256=Ith6YsoQdxB4V2vEsSBf8aPTVYUAlGqv6EqpTZkHjB4,12635
|
|
13
|
+
suitable_loop/tools/__init__.py,sha256=p8zryNWB5He5MGgQ_-3tZtrXR0eEXDsIcbgGUesQvF0,39
|
|
14
|
+
suitable_loop/tools/code_tools.py,sha256=E6w3c7llLZv-K-nlj5MbFMyoR_ENSliqSxSjHzhOrHc,3990
|
|
15
|
+
suitable_loop/tools/git_tools.py,sha256=iIRoegCwm69i5YdKt3rDBJbnWi8X4zGecx-40kW_MDk,1943
|
|
16
|
+
suitable_loop/tools/log_tools.py,sha256=QH_o7rLoLEbjqvSuIx08kb37Ij2EYpUozN2BLz7vT1I,2020
|
|
17
|
+
suitable_loop/tools/util_tools.py,sha256=N2KKYVctOv3xzlTNWZcpV_AOcl6mDI6gzZyoy4kPuYg,1647
|
|
18
|
+
suitable_loop-0.1.0.dist-info/METADATA,sha256=kEUcwYDx2XnvL353VTDum9XAXr3eT1X9lW6luut3IQI,428
|
|
19
|
+
suitable_loop-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
20
|
+
suitable_loop-0.1.0.dist-info/entry_points.txt,sha256=xSUkV2TXls7Gp9bcPcUOLUvWDcYuhSGPTV55b9hoA9U,60
|
|
21
|
+
suitable_loop-0.1.0.dist-info/RECORD,,
|