Skip to content

Latest commit

 

History

History
176 lines (141 loc) · 8.68 KB

CLAUDE.md

File metadata and controls

176 lines (141 loc) · 8.68 KB

CLAUDE.md

Was ist PluginForge?

Anwendungsunabhaengiges Python-Plugin-Framework. Baut auf pluggy auf und ergaenzt: YAML-Config, Lifecycle, Enable/Disable, Abhaengigkeiten, FastAPI-Integration, i18n, Extension Points, Hot-Reload, Health Checks, Config Schema Validation, Security, Application-Identity-Gating (v0.7.0 + v0.9.0 hard-filter), Testing-Helpers (v0.8.0), Lifecycle-Visibility mit inspect_plugin + Event-Hooks (v0.9.0), Config-Overlay via merge_app_config + refresh_config(notify=) (v0.10.0).

Repository: https://github.com/astrapi69/pluginforge Architektur: docs/ARCHITECTURE.md (lesen vor jeder Aenderung) Wiki: https://github.com/astrapi69/pluginforge/wiki Lizenz: MIT Aktuelle Version: siehe pyproject.toml (poetry version -s fuer die aktuelle Nummer; vermeidet hartcodierte Versions-Drift).

Workflow-Regeln

Vor jeder nicht-trivialen Aenderung: .claude/rules/ lesen (ai-workflow, coding-standards, code-hygiene, quality-checks, release-workflow, lessons-learned). .claude/README.md ist der Einstiegspunkt.

Tech Stack

  • Python 3.11+
  • pluggy (Hook-System, NICHT selbst bauen - ab 1.1.0 new-style Hook Wrapper mit explizitem wrapper=True)
  • PyYAML (Konfiguration)
  • packaging (SemVer-Vergleiche fuer min_app_version)
  • FastAPI (optional, nur wenn importiert)
  • Alembic (optional, nur wenn importiert)
  • Poetry (Paketmanagement, PEP 621 [project] table)
  • pytest + pytest-cov (Tests)
  • ruff (Linting + Formatierung)
  • pre-commit (Hooks vor jedem Commit)

Paketstruktur

pluginforge/
├── pluginforge/
│   ├── __init__.py          # Public API exportieren
│   ├── base.py              # BasePlugin
│   ├── manager.py           # PluginManager
│   ├── config.py            # YAML-Config laden/mergen
│   ├── discovery.py         # Entry Point Discovery + Dependency Resolution
│   ├── lifecycle.py         # init/activate/deactivate Steuerung
│   ├── state.py             # DiscoveryResult, DiscoveryDiff, PluginState, PluginError, PluginInspection
│   ├── security.py          # Plugin-Name-Validierung, Path Traversal Prevention
│   ├── utils.py             # Hilfsfunktionen
│   ├── fastapi_ext.py       # FastAPI-Router mounten (optional)
│   ├── alembic_ext.py       # Alembic-Migrations sammeln (optional)
│   └── i18n.py              # Mehrsprachige Strings aus YAML
├── tests/
├── docs/
│   ├── ARCHITECTURE.md
│   ├── design/              # Design-Dokumente pro Release
│   └── ...
├── CHANGELOG.md             # Aenderungen ab v0.6.0
├── pyproject.toml
├── README.md
├── LICENSE
├── Makefile
└── .gitignore

Public API

Stand v0.10.0, definiert in pluginforge/__init__.py.__all__:

  • BasePlugin - Basisklasse fuer Plugins (Lifecycle, Hooks, Metadaten).
  • PluginManager - Verwaltet Discovery, Aktivierung, Hooks, Routes.
  • DiscoveryResult - Per-Plugin-Status nach Discovery (states, activated, errors). Helper by_filter_reason(reason).
  • DiscoveryDiff - Delta zwischen zwei Discovery-Laeufen. Helper by_filter_reason(reason) (v0.10.0 parity mit DiscoveryResult).
  • PluginState - Plugin-Lifecycle-Zustand (+ v0.9.0: activated_at, last_config_change, source).
  • PluginError - Strukturierter Fehler mit Phase + Severity.
  • PluginInspection - Aggregierter Snapshot (state, config, health, hooks, routes, ...) via inspect_plugin(name) (v0.9.0).
  • FilterReason - Literal-Alias mit 10 Werten (v0.10.0 am Package-Root re-exportiert; war zuvor nur via pluginforge.state erreichbar).
  • ErrorPhase - Literal-Alias mit 7 Werten (v0.10.0 am Package-Root re-exportiert).
  • config_diff - Helper (old, new) -> {key: (old, new)} fuer on_config_changed-Konsumenten (v0.10.0 am Package-Root re-exportiert; via pluginforge.utils ebenfalls weiter erreichbar).
  • CircularDependencyError - Wenn depends_on einen Zyklus enthaelt.
  • InvalidPluginNameError - Wenn Plugin-Name die Sicherheits-Validierung scheitert.

Konventionen

  • Typehints ueberall, kein Any wo ein konkreter Typ moeglich ist
  • Keine Em-Dashes (--), stattdessen Bindestriche (-) oder Kommata
  • Commit Messages: Englisch, konventionell (feat/fix/refactor/docs/test/chore)
  • Docstrings: Google-Style (siehe .claude/rules/code-hygiene.md)
  • Logging: logging.getLogger(__name__), keine eigene Log-Config
  • Optional Dependencies: Lazy Import mit klarer Fehlermeldung wenn nicht installiert
  • Keine hartcodierten Strings, alles via Config
  • Plugin-Namen werden validiert (security.py) - keine Pfad-Separatoren, max 64 Zeichen
  • Keine Co-Authored-By-Trailer fuer KI-Mitwirkende (siehe coding-standards.md)

BasePlugin + pluggy Hook-Integration

Ein Plugin ist beides gleichzeitig: eine BasePlugin-Subklasse (Lifecycle: init, activate, deactivate, on_config_changed) und ein Objekt mit @hookimpl-dekorierten Methoden (pluggy Hook-System). PluginForge managed den Lifecycle, pluggy managed die Hooks. Worked example mit allen relevanten Attributen siehe docs/guides/plugin-author.md.

Wichtig: Ab pluggy 1.1.0 brauchen Hook Wrapper explizit wrapper=True.

PluginManager Ablauf

__init__(config_path, pre_activate=None, api_version="1", app_version=None, api_version_severity="warning", app_version_severity="error", app_id=None)

discover_plugins() / register_plugins([classes]) / register_plugin(instance): laedt Entry Points oder direkte Klassen, filtert nach enabled/disabled, validiert Identity (v0.7.0: wrong_application bei Mismatch; v0.9.0: missing_target_application wenn Plugin target_application fehlt und Host app_id hat) und Version (min_app_version vs app_version, v0.6.0), sortiert depends_on topologisch, ruft pro Plugin: YAML laden, init + config-schema, optional pre_activate, pluggy register, activate. Liefert DiscoveryResult(states, activated, errors).

Weitere oeffentliche Methoden:

  • call_hook / call_hook_safe: Hook-Dispatch via pluggy.
  • get_extensions(ExtensionPoint): aktive Plugins fuer einen Typ.
  • mount_routes(app, prefix="/api"): FastAPI-Router mounten (idempotent seit v0.8.0).
  • reload_plugin(name): deactivate -> re-import -> re-init -> activate.
  • rediscover() -> DiscoveryDiff: Delta zwischen Laeufen.
  • refresh_config(new=None, *, notify=True) -> list[PluginError]: app-config Snapshot ersetzen + on_config_changed (v0.6.0; notify kwarg v0.10.0).
  • merge_app_config(overlay, *, notify=True) -> list[PluginError]: Overlay rekursiv in den Snapshot mergen (Dicts mergen, Listen/Skalare ersetzen). Loest den manager._app_config = ...-Hack ab (v0.10.0; privates Attribut bleibt durch v0.10.0 funktional, geplant fuer Entfernung in v1.0.0).
  • health_check(): Aggregiert plugin.health() ueber alle aktiven Plugins.
  • get_last_discovery_result() -> DiscoveryResult | None: letzter Lauf (v0.6.0).
  • inspect_plugin(name) -> PluginInspection | None: Aggregator-Snapshot (v0.9.0).
  • on_plugin_activated/deactivated/on_config_refreshed(cb): Event-Hooks mit Deregistrations-Closure (v0.9.0).
  • deactivate_all(): umgekehrte Reihenfolge.

Config-Merge-Logik

App-Config (config/app.yaml)
  + Plugin-Config (config/plugins/{name}.yaml)  # merged in plugin.config
  + i18n (config/i18n/{lang}.yaml)               # separat, via get_text()

Fehlende Config-Dateien sind kein Fehler, nur leere Defaults. Config-Schema wird validiert wenn config_schema auf dem Plugin definiert ist.

Makefile Targets

install / install-dev / test / test-fast / lint / format / ci (lint+format-check+test) / bump-patch / bump-minor / build / publish (ci+build+publish, siehe release-workflow.md) / clean. make help listet alles.

Tests

  • Jede Komponente hat eigene Testdatei (siehe tests/).
  • conftest.py: Fixtures fuer temporaere YAML-Configs und Sample-Plugins.
  • Sample-Plugins als Klassen in tests/ definieren (nicht als Entry Points).
  • Fuer Entry Point Tests: pm.register() direkt nutzen.
  • Coverage-Ziel: >= 90% (gemaess .claude/rules/quality-checks.md).
  • Aktuelle Test-Zahlen NICHT hier hartkodieren; per Befehl verifizieren (siehe ai-workflow.md "Numeric claims verification").

Session-Start

Per .claude/rules/ai-workflow.md: ROADMAP lesen, git log -10, make test gruen, dann Aufgabe.

Kontext

Bibliogon und AdaptivLearner bauen darauf auf. PluginForge muss anwendungsunabhaengig bleiben. Bibliogon-spezifisches gehoert ins Bibliogon-Repo als Plugin oder Hook-Spec.

For AI agents integrating PluginForge into a consumer app

See docs/guides/consumer-integration.md (Quick Reference section).

For AI agents writing a PluginForge plugin

See docs/guides/plugin-author.md (Quick Reference section).