patchwork-conventions 0.1.2__tar.gz → 0.1.4__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.
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/PKG-INFO +1 -1
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/pyproject.toml +1 -1
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/mcp/server.py +10 -3
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/api_patterns.py +35 -20
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/PKG-INFO +1 -1
- patchwork_conventions-0.1.4/src/patchwork_conventions.egg-info/top_level.txt +1 -0
- patchwork_conventions-0.1.2/src/patchwork_conventions.egg-info/top_level.txt +0 -5
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/LICENSE +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/README.md +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/setup.cfg +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/__init__.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/cli.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/mcp/__init__.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/__init__.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/ast_base.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/config_detector.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/error_handling.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/git_patterns.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/imports.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/naming.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/structure.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/testing.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/output/__init__.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/output/report.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/scanner.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/SOURCES.txt +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/dependency_links.txt +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/entry_points.txt +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/requires.txt +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/tests/test_naming.py +0 -0
- {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/tests/test_scanner.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "patchwork-conventions"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Mine your codebase. Generate CONVENTIONS.md. Stop AI agents from making up your style."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -36,7 +36,7 @@ def _get_or_scan(root: Path) -> ConventionReport:
|
|
|
36
36
|
cached = _CACHE.get(key)
|
|
37
37
|
if cached is not None:
|
|
38
38
|
return cached
|
|
39
|
-
opts = ScanOptions(root=root, max_files=
|
|
39
|
+
opts = ScanOptions(root=root, max_files=500)
|
|
40
40
|
report = do_scan(opts)
|
|
41
41
|
_CACHE[key] = report
|
|
42
42
|
return report
|
|
@@ -330,6 +330,8 @@ async def run_server(root: Path, port: int = 3742, stdio: bool = True) -> None:
|
|
|
330
330
|
lines.append(f" logging: {er.logging_framework}")
|
|
331
331
|
if er.propagation_style:
|
|
332
332
|
lines.append(f" propagation: {er.propagation_style}")
|
|
333
|
+
if er.custom_exceptions:
|
|
334
|
+
lines.append(f" custom exceptions: {', '.join(er.custom_exceptions[:6])}")
|
|
333
335
|
for note in er.notes:
|
|
334
336
|
lines.append(f" note: {note}")
|
|
335
337
|
return [types.TextContent(type="text", text="\n".join(lines) or "No error patterns found.")]
|
|
@@ -385,8 +387,13 @@ async def run_server(root: Path, port: int = 3742, stdio: bool = True) -> None:
|
|
|
385
387
|
return [types.TextContent(type="text", text="\n".join(lines))]
|
|
386
388
|
|
|
387
389
|
elif name == "patchwork_check":
|
|
388
|
-
sym = arguments
|
|
389
|
-
kind = arguments
|
|
390
|
+
sym = arguments.get("name")
|
|
391
|
+
kind = arguments.get("kind")
|
|
392
|
+
if not sym or not kind:
|
|
393
|
+
return [types.TextContent(
|
|
394
|
+
type="text",
|
|
395
|
+
text="Error: 'name' and 'kind' are required arguments.",
|
|
396
|
+
)]
|
|
390
397
|
lang = arguments.get("language", "")
|
|
391
398
|
return [types.TextContent(
|
|
392
399
|
type="text",
|
{patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/api_patterns.py
RENAMED
|
@@ -34,10 +34,23 @@ _RESP_DATA_ERROR = re.compile(r'["\'](?:data|error)["\']', re.IGNORECASE)
|
|
|
34
34
|
_RESP_SUCCESS_DATA = re.compile(r'["\']success["\'].*["\']data["\']', re.DOTALL | re.IGNORECASE)
|
|
35
35
|
_RESP_RESULT = re.compile(r'["\']result["\']', re.IGNORECASE)
|
|
36
36
|
|
|
37
|
-
# Route parameter styles
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
# Route parameter styles — match route strings passed to known route-registration calls.
|
|
38
|
+
# We look for the route string ONLY when it appears as an argument to a route decorator
|
|
39
|
+
# or router method, to avoid matching f-strings, format strings, and other /path usages.
|
|
40
|
+
|
|
41
|
+
# FastAPI/Starlette: @app.get("/items/{item_id}") or @router.post("/x/{id:int}")
|
|
42
|
+
_ROUTE_BRACE = re.compile(
|
|
43
|
+
r'(?:@?\w+\.(?:get|post|put|patch|delete|websocket|route|add_api_route)\s*\()'
|
|
44
|
+
r'\s*[f]?["\'][^"\']*\{[a-zA-Z_]\w*(?::[a-zA-Z]+)?\}'
|
|
45
|
+
)
|
|
46
|
+
# Flask: @app.route("/items/<int:item_id>")
|
|
47
|
+
_ROUTE_ANGLE = re.compile(
|
|
48
|
+
r'(?:@?\w+\.route\s*\()\s*[f]?["\'][^"\']*<(?:[a-zA-Z_]+:)?[a-zA-Z_]\w*>'
|
|
49
|
+
)
|
|
50
|
+
# Express: router.get("/:id") or app.use("/items/:id")
|
|
51
|
+
_ROUTE_COLON = re.compile(
|
|
52
|
+
r'(?:\w+\.(?:get|post|put|patch|delete|use|all)\s*\()\s*["\'][^"\']*(?<!\{):(?!\w*\})[a-zA-Z_]\w*'
|
|
53
|
+
)
|
|
41
54
|
|
|
42
55
|
# ORM signals
|
|
43
56
|
_ORM_SIGNALS = {
|
|
@@ -57,7 +70,8 @@ _FRAMEWORK_SIGNALS = {
|
|
|
57
70
|
"FastAPI": [r"from fastapi import", r"@app\.get\(", r"@router\."],
|
|
58
71
|
"Flask": [r"from flask import", r"@app\.route\(", r"Blueprint\("],
|
|
59
72
|
"Django": [r"from django", r"urlpatterns\s*=", r"HttpResponse"],
|
|
60
|
-
|
|
73
|
+
# For Express: require both the require() AND usage — router.get alone is too generic
|
|
74
|
+
"Express": [r"require\(['\"]express['\"]\)", r"express\(\)"],
|
|
61
75
|
"Fastify": [r"require\(['\"]fastify['\"]", r"fastify\.register"],
|
|
62
76
|
"Hono": [r"from ['\"]hono['\"]", r"new Hono\("],
|
|
63
77
|
"Gin": [r"\bgin\b.*\bDefault\(\)", r"r\.GET\(", r"c\.JSON\("],
|
|
@@ -105,9 +119,10 @@ def _detect_apis(paths: list[Path], lang: str) -> APIResult:
|
|
|
105
119
|
data_error = 0
|
|
106
120
|
success_data = 0
|
|
107
121
|
result_shape = 0
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
# Track route style per FILE (not per match) to avoid single-file anomalies
|
|
123
|
+
colon_route_files = 0
|
|
124
|
+
brace_route_files = 0
|
|
125
|
+
angle_route_files = 0
|
|
111
126
|
# Track per-file hits (not per-match) to avoid false positives from
|
|
112
127
|
# code that merely mentions framework names in strings, comments, or docs.
|
|
113
128
|
orm_files: Counter[str] = Counter()
|
|
@@ -127,9 +142,9 @@ def _detect_apis(paths: list[Path], lang: str) -> APIResult:
|
|
|
127
142
|
success_data += len(_RESP_SUCCESS_DATA.findall(text))
|
|
128
143
|
result_shape += len(_RESP_RESULT.findall(text))
|
|
129
144
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
if _ROUTE_COLON.search(text): colon_route_files += 1
|
|
146
|
+
if _ROUTE_BRACE.search(text): brace_route_files += 1
|
|
147
|
+
if _ROUTE_ANGLE.search(text): angle_route_files += 1
|
|
133
148
|
|
|
134
149
|
# Skip files that are themselves pattern-definition files (e.g. this miner)
|
|
135
150
|
# to avoid self-matching on our own regex strings.
|
|
@@ -175,17 +190,17 @@ def _detect_apis(paths: list[Path], lang: str) -> APIResult:
|
|
|
175
190
|
elif result_shape > 3:
|
|
176
191
|
response_shape = "{result}"
|
|
177
192
|
|
|
178
|
-
# Route param style
|
|
179
|
-
|
|
193
|
+
# Route param style — pick the style seen in the most files.
|
|
194
|
+
# Require at least 2 files to avoid single-file anomalies.
|
|
180
195
|
route_style = None
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if
|
|
184
|
-
route_style = "
|
|
185
|
-
elif
|
|
186
|
-
route_style = "{id} (FastAPI style)"
|
|
187
|
-
else:
|
|
196
|
+
best = max(brace_route_files, angle_route_files, colon_route_files)
|
|
197
|
+
if best >= 2:
|
|
198
|
+
if brace_route_files == best:
|
|
199
|
+
route_style = "{id} (FastAPI/Starlette style)"
|
|
200
|
+
elif angle_route_files == best:
|
|
188
201
|
route_style = "<id> (Flask style)"
|
|
202
|
+
else:
|
|
203
|
+
route_style = ":id (Express style)"
|
|
189
204
|
|
|
190
205
|
return APIResult(
|
|
191
206
|
response_shape=response_shape,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
patchwork
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/__init__.py
RENAMED
|
File without changes
|
{patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/ast_base.py
RENAMED
|
File without changes
|
{patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/config_detector.py
RENAMED
|
File without changes
|
{patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/error_handling.py
RENAMED
|
File without changes
|
{patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/git_patterns.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/structure.py
RENAMED
|
File without changes
|
|
File without changes
|
{patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/output/__init__.py
RENAMED
|
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
|