fix(d5): Marathon-Scanner-Triage — trace_api_contracts 859→218 (-75%, Router-Präfixe/Multi-Router/leere Pfade/Template-Literals gefixt), trace_plugins 27→0 (-100%, Inline-Manifest-Konvention erkannt); 371 HIGH-Fehlalarme eliminiert (OpenAPI-verifiziert); ~12 echte API-Bugs als Follow-up dokumentiert (ai/sessions ×5, policies ×4, mail ×4)
This commit is contained in:
@@ -9,16 +9,27 @@ APP = ROOT / "app"
|
||||
FRONTEND = ROOT / "frontend" / "src"
|
||||
|
||||
def find_plugin_manifests() -> list[dict]:
|
||||
"""Find plugin manifests.
|
||||
|
||||
Project convention: manifests are defined inline in plugin.py via
|
||||
``PluginManifest(...)`` — no separate manifest.py file exists.
|
||||
"""
|
||||
plugins = []
|
||||
builtins = APP / "plugins" / "builtins"
|
||||
if not builtins.exists(): return plugins
|
||||
for d in builtins.iterdir():
|
||||
if not d.is_dir() or d.name.startswith("_"): continue
|
||||
manifest_file = d / "manifest.py"
|
||||
plugin_info = {"name": d.name, "path": str(d.relative_to(ROOT)), "has_manifest": manifest_file.exists()}
|
||||
if manifest_file.exists():
|
||||
try: content = manifest_file.read_text()
|
||||
plugin_file = d / "plugin.py"
|
||||
if not plugin_file.exists():
|
||||
# Not a plugin directory (e.g. migrations/, tests/) — skip silently
|
||||
continue
|
||||
has_manifest = False
|
||||
plugin_info = {"name": d.name, "path": str(d.relative_to(ROOT)), "has_manifest": False}
|
||||
if plugin_file.exists():
|
||||
try: content = plugin_file.read_text()
|
||||
except: continue
|
||||
has_manifest = bool(re.search(r'PluginManifest\s*\(', content))
|
||||
plugin_info["has_manifest"] = has_manifest
|
||||
for m in re.finditer(r'menu_items\s*[=:]\s*\[', content): plugin_info["has_menu_items"] = True
|
||||
for m in re.finditer(r'page_routes\s*[=:]\s*\[', content): plugin_info["has_page_routes"] = True
|
||||
for m in re.finditer(r'settings_pages\s*[=:]\s*\[', content): plugin_info["has_settings_pages"] = True
|
||||
@@ -47,6 +58,16 @@ def find_plugin_routes() -> list[dict]:
|
||||
routes.append({"method": m.group(1).upper(), "path": m.group(2), "file": rel})
|
||||
return routes
|
||||
|
||||
|
||||
def _has_dynamic_consumption() -> bool:
|
||||
"""True if the frontend consumes plugin manifests dynamically.
|
||||
|
||||
The frontend fetches menu_items/page_routes via the active-manifests API
|
||||
(frontend/src/api/pluginManifests.ts) and renders them through pluginStore —
|
||||
static '@/plugins/<name>' imports are therefore not required for wiring.
|
||||
"""
|
||||
return (FRONTEND / "api" / "pluginManifests.ts").exists()
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("TRACE PLUGINS — Plugin→Manifest→Frontend Verkabelungsfehler")
|
||||
@@ -57,8 +78,12 @@ def main():
|
||||
issues = []
|
||||
for p in plugins:
|
||||
if not p["has_manifest"]:
|
||||
issues.append({"plugin": p["name"], "issue": "No manifest.py", "severity": "HIGH"})
|
||||
if p.get("has_menu_items") and p["name"] not in frontend_refs:
|
||||
issues.append({"plugin": p["name"], "issue": "No manifest (PluginManifest in plugin.py expected)", "severity": "HIGH"})
|
||||
# NOTE: menu_items are consumed dynamically by the frontend via the
|
||||
# active-manifests API (frontend/src/api/pluginManifests.ts + pluginStore),
|
||||
# NOT via static '@/plugins/<name>' imports — a missing static import is
|
||||
# therefore not a wiring error.
|
||||
if p.get("has_menu_items") and p["name"] not in frontend_refs and not _has_dynamic_consumption():
|
||||
issues.append({"plugin": p["name"], "issue": "Has menu_items but no frontend reference", "severity": "MEDIUM"})
|
||||
print(f"\nPlugins found: {len(plugins)}")
|
||||
print(f"Plugin routes: {len(plugin_routes)}")
|
||||
|
||||
Reference in New Issue
Block a user