Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
16 KiB
Etappe 4e – Schleifen-Inhalte, Guides, Security-Audit, Version – Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Der KI-Assistent kann loop_*-Inhalte (z. B. Datum, Kategorien, Autor) in Modulen setzen, die in einem Divi-Loop-Element liegen oder selbst eines sind – mit Kontextprüfung aus dem Blockbaum, passenden Fehlermeldungen und der Kurzform {{loop:<option>}}; lit/divi-dynamic-sources zeigt die passenden Loop-Optionen je Knoten. Außerdem: Guides für alle Etappe-4-Werkzeuge, ein Security-Audit der ganzen Etappe mit Fix-Welle und der Versionssprung auf 0.7.0.
Architecture: Der Schleifen-Kontext wird im Blockbaum bestimmt (LoopContext: nächster Vorfahre-oder-selbst mit module.advanced.loop.desktop.value.enable === "on", Typ aus queryType, Standard post_types) und als reines Wertobjekt LoopScope an VariableRef/DynamicContentRules gegeben; dort erlaubt er nur loop_*-Optionen der passenden Art (Post/Term/Benutzer/Menü). Die Einspeisung liegt in den Abilities (Insert, Update, Move), nicht in den unteren Schichten: DiviComposition::inLoop() liefert eine Komposition mit Kontext. DynamicContentMap behält die loop_*-Optionen (heute verworfen) gruppiert nach Art; Divi-Aufrufe ändern sich nicht (DynamicContentOptions::get_options() liefert sie schon). Guides, Audit und Version sind Dokumentations-/Prozess-Tasks mit klaren Checklisten.
Tech Stack: PHP ≥ 8.1, WordPress 7.x, Divi 5.15.1 (nur über vorhandene Adapter), PHPUnit 9.6 + Brain Monkey + Mockery, phpcs, DDEV + bash/jq-Smokes.
Spec: docs/superpowers/specs/2026-10-16-etappe-4-designsystem-design.md (§9 Schleifen-Inhalte, §12 Audit, §14 Schritt 8–9, §1.3 Nr. 5–6, A3, A10, A11), Entscheidungen docs/superpowers/notes/2026-09-28-etappe-4-entscheidungen-entwurf.md (G11, G12), Recherchen 4e: docs/superpowers/notes/2026-10-19-etappe-4e-recherche-schleifen.md (Divi-Interna; Task 10 legt sie ab, bis dahin …/scratchpad/research-4e-loop-divi.md, …/research-4e-loop-plugin.md, …/research-4e-guides-audit-version.md – die Task-Autoren haben sie gelesen), Pläne 4c/4d als Vorbild; der Code ist maßgeblich, wo er von den Plänen abweicht.
Voraussetzung: Branch etappe-4 (= main), Stand 7678d22 (4a–4d fertig), 3368 Unit-Tests grün (composer test), composer lint sauber.
Teil von Etappe 4: 4a–4d (fertig) → 4e (dieser Plan) = Abschluss von Etappe 4.
Global Constraints
- Dateien nur im Plugin-Verzeichnis
/home/janniclas/Projekte/WordPress/wp-content/plugins/WP-LIT-MCPanlegen oder ändern. Integrationsskripte stellen im EXIT-Trap alles wieder her, was sie ändern (Seiten, Optionen). - PHP ≥ 8.1 ohne 8.2+-Features; WordPress ≥ 6.9; Divi 5.15.1. Namespace
LIT\MCP\→src/, PSR-4, eine Klasse pro Datei, jede Datei mitdefined('ABSPATH') || exit;,declare(strict_types=1);, höchstens 120 Zeichen je Zeile. Nach jedem Taskcomposer testundcomposer lintsauber (composer test=php -d memory_limit=512M vendor/bin/phpunit). - Divi-Grenze (Divi-Absicherung §3): Divi-Aufrufe nur in Adaptern aus
DiviAdapters::CLASSES. 4e braucht keinen neuen Divi-Aufruf:DynamicContentMapruftDynamicContentOptions::get_options()schon und behält jetzt nur mehr von dessen Ergebnis. Ergibt sich doch ein neuer Divi-Aufruf, Task anhalten und melden (NEEDS_CONTEXT).LoopScope,LoopContextund die Kontext-Teile der Regeln sind reines PHP (keine WordPress-/Divi-Aufrufe). - Sicherheitsgrenze Typabgleich: Ein
loop_*-Wert ist nur erlaubt, wenn der Schleifen-Kontext des Knotens (Art) zur Art der Option passt. Das ist keine Komfortmeldung: In Divi liestloop_post_meta_key_*in einer Term-/Benutzer-/Menü-Schleife Post-Meta des Objekts mit derselben ID (und Benutzer-/Term-Meta in einer Post-Schleife umgekehrt) – ein Fehlabgleich würde fremde Daten ausgeben (Recherche 4e, Befund 1). Ohne Schleifen-Kontext ist jedeloop_*-Option abgelehnt. - Rechte/Sperren unverändert: Alle Dynamic-Content-Einstellungen laufen weiter durch
ValueCleaner/VariableRef/DynamicContentRulesmit den Meta-Schlüssel-Regeln S29/S30 (auch fürloop_*_meta_key_) undunfiltered_html-Regeln; die Release-Gate-Fähigkeiten der Tools ändern sich nicht (dynamic-sourcesist schon überall deklariert, wo Inhalte entstehen). - Sprache: Meldungen, Hinweise, Guides, Notizen auf Deutsch mit echten Umlauten; neutral „KI-Assistent“, „MCP-Client“. Tool-Namen: direkte Tools
lit-<name>, indirektelit/<name>(ToolNamingTest). - Tests: Abilities über
LIT\MCP\Tests\Unit\TestCase(Brain Monkey + Mockery,assertToolError); vorbelegte Stubs (wp_json_encode,is_wp_error,absint,wp_slash,wp_unslash,sanitize_text_field) nie mitFunctions\expect()prüfen. Reine Klassen erben vonPHPUnit\Framework\TestCase. TDD: Test zuerst, RED-Lauf, Code, GREEN; ein Test muss den Codepfad erreichen, den sein Name behauptet (keine vakuösen Assertions – Lehre aus 4c/4d). Echte Loop-Optionsnamen aus Divi 5.15.1 verwenden (die bisherigen Testdatenloop_post_meta_keyu. ä. sind erfunden und werden in Task 2 korrigiert). Integration:cd /home/janniclas/Projekte/WordPress && timeout 900 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/<skript>.sh(läuft nur mit deaktivierten Pluginspostlosundasynclistry; vorher Zustand mitddev exec wp plugin listnotieren und danach wiederherstellen). - Kein clean-code-review-Skill je Task (die globale CLAUDE.md-Regel zieht sonst die Antwort des Implementierers an sich); der Gesamtreview folgt am Ende (Task 10).
- Commits:
git commit -m "<Nachricht>" -m "Co-Authored-By: Claude <Modellname> <noreply@anthropic.com>"mit dem Namen des ausführenden Modells; nur die Dateien des Tasks stagen.
Entscheidungen für 4e (Rulings, verbindlich)
- X1 Kontextregel (Divi 5.15.1): Der Schleifen-Kontext eines Knotens ist der des nächsten Vorfahren-oder-selbst, dessen
attrs.module.advanced.loop.desktop.value.enablegenau"on"ist (Tablet/Phone/Hover-Varianten undtruezählen nicht – das liest auch Divi so).queryTypefehlt →post_types. Verschachtelte Schleifen: die innerste gilt für alles darunter, egal welcher Art. Loop-Einstellungen, die nur über ein Preset kommen (LoopPresetAttrsMap), erkennt die Baum-Prüfung nicht – das ist eine dokumentierte Grenze (fail closed: ohne erkannte Schleife werdenloop_*abgelehnt). - X2 Arten:
post(post_types,current_page),term(post_taxonomies),user(user_roles),menu(menus),repeater(repeater_<acf-name>, nur falls die Recherche zeigt, dass es eigene Optionen gibt – sonst fälltrepeater_*unterpostoder wird abgelehnt; der Task-Autor entscheidet anhand der Recherchetabelle und dokumentiert es). Die Zuordnung Option → Art stammt aus dergroupder Divi-Option („Loop Terms“ → term, „Loop Users“ → user, alle übrigenloop_*→ post, Menü nach Tabelle); die drei Meta-Schlüssel-Optionen (loop_post_meta_key_manual_custom_field,loop_user_…,loop_term_…) ordnet der Name zu, weil ihre Gruppennamen übersetzt sind. Gespeichert wird der Array-Schlüssel der Option (loop_term_name), nicht die interneid. - X3 Einspeisung in den Abilities:
VariableRefundDynamicContentRulesbekommen einen optionalen?LoopScope(StandardLoopScope::none()→ verhält sich wie heute, kein bestehender Aufruf bricht).DiviComposition::inLoop(LoopScope)liefert eine Komposition mit Kontext (VorbildforPost()). Insert: Kontext der Zielposition (Eltern-Knoten, alle neuen Knoten eines Aufrufs teilen ihn, solange sie nicht selbst eine Schleife einschalten – ein neuer Knoten mit eigenerloop-Einstellung im Aufruf schaltet den Kontext für seine Nachfahren um: der Task-Autor löst das über die neuen Attribute des Knotens, wenn nötig fail closed = Schleife zählt erst ab dem gespeicherten Zustand und der Fehler nennt das). Update: Kontext des bestehenden Knotens (Vorfahren aus dem Baum, plus eigene neueloop-Attribute desselben Aufrufs); ein Composer je Kontext (Ruling S31 „ein VariableRef je Komposition“ wird entsprechend umformuliert). Move: Kontext am neuen Ziel; ein Move, der vorhandeneloop_*-Inhalte aus einer Schleife herausführt oder in eine Schleife anderer Art, erzeugt eine Warnung (Divi lässt das zu), keine Ablehnung (A11).lit-content-createund Theme-Builder-Anlage haben nie einen Schleifen-Kontext. - X4 Kurzform:
{{loop:<option>}}(Option ohne Präfix, z. B.{{loop:post_date}}→loop_post_date) mit Einstellungen wie{{post:…}}(A10); die TabelleLOOPinPlaceholdersordnet den Kurznamen den Optionen zu; welche Optionen before/after tragen, kommt aus derDynamicContentMap. - X5 Fehlermeldungen (Spec §9): ohne Schleife: „Schleifen-Inhalt "%s" braucht eine Divi-Schleife in diesem Modul oder einem übergeordneten Element (Abschnitt, Zeile, Spalte, Gruppe).“; falsche Art: „… passt nicht zur Schleife dieses Elements (Typ: %s).“ (Typ deutsch: Beiträge, Terme, Benutzer, Menü). Die irreführende alte Meldung „nur innerhalb von Divi-Schleifen“ entfällt; Ruling S36 ist abgelöst.
- X6
lit/divi-dynamic-sources: optionalpost_id+node(Pfad-ID): zusätzlich die zum Schleifen-Kontext dieses Knotens passendenloop_*-Optionen (Feldtypen wie die übrigen Optionen); lesen mit Leserecht auf die Seite (Musterlit-divi-node-get,DiviPageLoader); ohne Schleife im Kontext: Hinweis „kein Schleifen-Kontext“ statt Liste. Der Konstruktor bekommt denDiviPageLoader(Task 9 verdrahtet; die Tool-Fähigkeiten ändern sich nicht). - X7 Grenzen, dokumentiert statt gelöst: (a)
module.advanced.loopist als Attributpfad schreibbar – Schleifen ein-/ausschalten überattrswar schon vorher möglich und bleibt (A3 verbietet nur ein Anlegen/Konfigurieren-Werkzeug); (b) globale Module werden nicht betreten –loop_*in Bibliothekseinträgen hat keinen Schleifen-Kontext und wird abgelehnt; (c)StoredNodeGuard/Kopieren: kopierte Schleifen-Container mitloop_*-Kindern bleiben für Benutzer ohneunfiltered_htmlabgelehnt (fail closed), solange die Prüfung nur einen Kontext je Unterbaum kennt; (d) Presets, die eine Schleife einschalten, erkennt die Baumprüfung nicht (X1); (e) ACF-Wiederholungsfelder verlangen die Einstellungacf_type, die die Einstellungsregeln heute ablehnen – bleibt außen vor, wenn die Task-Autoren keinen sicheren Weg finden. Alle Punkte kommen in die Audit-Notiz (Task 8) und den Guide (Task 7). - X8 Guides (G11): keine Prompts, keine neue Guide-Datei ohne Not; Ablauf „Corporate Design übernehmen“ in
workflows.md;divi-design-system.mdbekommt Farben, Variablen, Theme-Optionen, Schriften, Restore je Bereich, Rechte;overview.mddie vollständige Liste der indirekten Design-Tools und die neuen Fehlercodes (design_rev,rev_mismatch,conflict,in_use,font_*);divi-placeholders.md/divi-shortform.md{{loop:…}}und die Loop-Regeln. Die Inhalte folgen der Checkliste der Recherche (A) und dem echten Code der Tools. - X9 Audit (Task 8): Ein unabhängiger Audit (Opus, Skill
anthropic-skills:security-auditwenn verfügbar, sonst eigenes Vorgehen) über den Umfang von Spec §12 (Preset-attrs/XSS, Font-Upload, Massen-Schreibrechte, Schleifen-Optionen/Meta-Schlüssel/Typabgleich, Snapshot-Restore) im Format der Audits 3a/3b (docs/superpowers/notes/2026-09-27-etappe-3a-security-audit.md); Befunde mit Schwere; kritische/hohe Befunde werden in einer Fix-Welle behoben, mittlere nach Urteil, der Rest steht als Restrisiko in der Notiz. Das DNS-Rebinding-Fenster zwischenallows()und cURL (W13) gehört in den Audit. - X10 Version: 0.6.1 → 0.7.0 (
wp-lit-mcp.php:6,src/Plugin.php:48,readme.txt:7plus Changelog-Abschnitt inreadme.txt); kein Git-Tag, kein Update-Server, keinCHANGELOG;DiviReleases::TESTEDbleibt (5.2.1, 5.14.0, 5.15.1). Spec/§9 nennt 5.14.0 – Task 10 berichtigt auf 5.15.1.
Schnittstellenvertrag (Namen und Signaturen, die spätere Tasks voraussetzen)
Klassen unter src/Integrations/Divi/Compose/ (Namespace LIT\MCP\Integrations\Divi\Compose), Tests unter tests/Unit/Integrations/Divi/Compose/, sofern nicht anders genannt. Task-Autoren dürfen Details verfeinern, aber nur mit Eintrag unter ## ABWEICHUNGEN am Ende ihres Abschnitts.
// LoopScope.php (Task 1) – reines PHP, unveränderlich
public const POST = 'post'; public const TERM = 'term'; public const USER = 'user'; public const MENU = 'menu';
public static function none(): self;
public static function ofQueryType(string $queryType): self; // post_types|current_page → post, post_taxonomies → term, user_roles → user, menus → menu; unbekannt (repeater_… nach X2) → laut Task-Entscheidung
public function active(): bool;
public function kind(): ?string; // null ohne Schleife
public function queryType(): ?string;
public function label(): string; // deutsch für Meldungen: 'Beiträge' | 'Terme' | 'Benutzer' | 'Menü'
// LoopContext.php (Task 1) – reines PHP
/** @param array<string, mixed> $attrs Attribute eines Blocks */
public static function enabled(array $attrs): bool; // desktop.value.enable === 'on' (X1)
public static function ownScope(array $attrs): ?LoopScope; // Schleife an genau diesem Block, sonst null
/** @param list<array<string, mixed>> $attrChain Attribute von der Wurzel bis zum Knoten (inklusive) */
public static function fromChain(array $attrChain): LoopScope; // innerste gewinnt
public static function of(NodeIndex $index, string $nodeId): LoopScope; // Vorfahren-oder-selbst über die Pfad-ID (Namespace BlockTree\NodeIndex)
// DynamicContentMap (Pages/, Task 2) – loop_*-Optionen bleiben erhalten
/** @return array<string, array<string, mixed>> Option => Feldspezifikation, nur loop_*-Optionen der Art $kind */
public function loopFields(string $kind): array; // Name final nach Task 2; Task 3–5 nutzen genau diese Methode
// DynamicContentRules / VariableRef (Task 2) – optionaler Kontext
// VariableRef::__construct(DesignSystem $design, ?array $dynamicContent = null, ?LoopScope $loop = null)
// DynamicContentRules::__construct(DesignSystem $design, ?array $map, ?LoopScope $loop = null)
// DiviComposition (Pages/, Task 4)
public function inLoop(LoopScope $scope): self; // Klon mit Kontext, Vorbild forPost()
// Placeholders (Task 3): {{loop:<kurzname>}} → loop_<name>; Tabelle LOOP wie POST
// DiviDynamicSourcesAbility (Task 5): __construct(…, ?DiviPageLoader $loader = null); Eingabe post_id + node optional
Dateistruktur
Neu: Compose/LoopScope.php, Compose/LoopContext.php, tests/Integration/divi-loop-smoke.sh, ggf. Fixture unter tests/Fixtures/divi/ (echte Loop-Seite), Audit-Notiz docs/superpowers/notes/2026-10-19-etappe-4e-security-audit.md, Recherche-Notiz docs/superpowers/notes/2026-10-19-etappe-4e-recherche-schleifen.md.
Geändert: Compose/{DynamicContentRules,VariableRef,Placeholders}.php, Pages/{DynamicContentMap,DiviComposition,DiviInsertAbility,DiviUpdateAbility,DiviMoveAbility,DiviDynamicSourcesAbility,DiviWriter}.php (je nach Task), DiviIntegration.php (Loader für dynamic-sources), Guides (docs/guides/*.md), wp-lit-mcp.php, src/Plugin.php, readme.txt, Spec/Notizen, Tests der genannten Klassen (inkl. DynamicContentRulesTest, VariableRefTest, DiviFixtures).
Task-Übersicht
| # | Task | Ergebnis |
|---|---|---|
| 1 | LoopScope, LoopContext |
reiner Schleifen-Kontext aus dem Baum |
| 2 | DynamicContentMap behält loop_*; DynamicContentRules/VariableRef mit Kontext; Meldungen; Fixtures korrigieren |
loop_* im passenden Kontext erlaubt, sonst Fehler |
| 3 | {{loop:…}} in Placeholders/Kurzform |
Kurzform |
| 4 | Kontext in Insert, Update, Move; Move-Warnung | Werkzeuge nutzen den Kontext |
| 5 | lit/divi-dynamic-sources mit post_id + node |
Optionen je Knoten |
| 6 | Smoke gegen echtes Divi (Loop-Seite, Speichern, Rendern) | divi-loop-smoke.sh |
| 7 | Guides | Etappe-4-Werkzeuge und Loops dokumentiert |
| 8 | Security-Audit Etappe 4 + Fix-Welle | Audit-Notiz, Fixes |
| 9 | Version 0.7.0, Spec-/Notiz-Nachträge, Verdrahtung | Release-Stand |
| 10 | Gesamtverifikation, Gesamtreview, Memory | Abschluss Etappe 4 |