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.
Files changed (31) hide show
  1. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/PKG-INFO +1 -1
  2. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/pyproject.toml +1 -1
  3. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/mcp/server.py +10 -3
  4. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/api_patterns.py +35 -20
  5. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/PKG-INFO +1 -1
  6. patchwork_conventions-0.1.4/src/patchwork_conventions.egg-info/top_level.txt +1 -0
  7. patchwork_conventions-0.1.2/src/patchwork_conventions.egg-info/top_level.txt +0 -5
  8. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/LICENSE +0 -0
  9. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/README.md +0 -0
  10. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/setup.cfg +0 -0
  11. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/__init__.py +0 -0
  12. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/cli.py +0 -0
  13. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/mcp/__init__.py +0 -0
  14. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/__init__.py +0 -0
  15. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/ast_base.py +0 -0
  16. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/config_detector.py +0 -0
  17. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/error_handling.py +0 -0
  18. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/git_patterns.py +0 -0
  19. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/imports.py +0 -0
  20. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/naming.py +0 -0
  21. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/structure.py +0 -0
  22. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/miners/testing.py +0 -0
  23. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/output/__init__.py +0 -0
  24. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/output/report.py +0 -0
  25. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork/scanner.py +0 -0
  26. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/SOURCES.txt +0 -0
  27. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/dependency_links.txt +0 -0
  28. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/entry_points.txt +0 -0
  29. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/src/patchwork_conventions.egg-info/requires.txt +0 -0
  30. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/tests/test_naming.py +0 -0
  31. {patchwork_conventions-0.1.2 → patchwork_conventions-0.1.4}/tests/test_scanner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchwork-conventions
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Mine your codebase. Generate CONVENTIONS.md. Stop AI agents from making up your style.
5
5
  Author: patchwork contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "patchwork-conventions"
7
- version = "0.1.2"
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=300)
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["name"]
389
- kind = arguments["kind"]
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",
@@ -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
- _ROUTE_COLON = re.compile(r'(?:app|router)\.\w+\(["\'][^"\']*:\w+') # Express :id
39
- _ROUTE_BRACE = re.compile(r'(?:path|url)\s*=\s*["\'][^"\']*\{[a-zA-Z_]+\}') # FastAPI {id}
40
- _ROUTE_ANGLE = re.compile(r'(?:app|blueprint)\.\w+\(["\'][^"\']*<[a-zA-Z_:]+>') # Flask <id>
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
- "Express": [r"require\(['\"]express['\"]", r"app\.use\(", r"router\.get\("],
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
- colon_routes = 0
109
- brace_routes = 0
110
- angle_routes = 0
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
- colon_routes += len(_ROUTE_COLON.findall(text))
131
- brace_routes += len(_ROUTE_BRACE.findall(text))
132
- angle_routes += len(_ROUTE_ANGLE.findall(text))
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
- route_total = colon_routes + brace_routes + angle_routes
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
- if route_total > 0:
182
- best = max(colon_routes, brace_routes, angle_routes)
183
- if colon_routes == best:
184
- route_style = ":id (Express style)"
185
- elif brace_routes == best:
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchwork-conventions
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Mine your codebase. Generate CONVENTIONS.md. Stop AI agents from making up your style.
5
5
  Author: patchwork contributors
6
6
  License: MIT
@@ -1,5 +0,0 @@
1
- analyzers
2
- mcp
3
- miners
4
- output
5
- patchwork