docs: name tools and error codes the way the client sees them
Indirect abilities are written lit/<name> (via mcp-adapter-execute-ability) in guides, descriptions and hints, direct tools lit-<name>; guides use the client-visible codes (write_failed, partial_write) and say exactly which tools return a revision_id. ToolNamingTest keeps guides and sources consistent with Ability::isDirect() and checks the overview's direct-tool list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# Inhalte, Felder und Status
|
||||
|
||||
Werkzeugnamen: `lit-…` sind direkt aufrufbare Tools; `lit/…` sind Abilities, die über
|
||||
`mcp-adapter-execute-ability` (Name in `ability_name`, Eingaben in `parameters`) aufgerufen werden.
|
||||
|
||||
## Inhaltstypen erkennen
|
||||
|
||||
- `lit-content-types` ohne Parameter listet alle Inhaltstypen (Seiten, Beiträge, eigene Typen) mit
|
||||
@@ -32,20 +35,27 @@
|
||||
- Begriffe: `terms: {"category": [4, "Neuer Begriff"]}` ersetzt die Zuordnung dieser Taxonomie (IDs oder
|
||||
Namen; unbekannte Namen werden angelegt, falls erlaubt).
|
||||
- Felder leeren: Wert `null`. Pflichtfelder können nicht geleert werden.
|
||||
- Beziehungsfelder erwarten Eintrags-IDs, Datei-/Bildfelder Anhang-IDs aus der Mediathek.
|
||||
- Beziehungsfelder erwarten IDs von Einträgen, die der Nutzer lesen darf; Datei-/Bildfelder Anhang-IDs aus der
|
||||
Mediathek.
|
||||
- Einträge im Papierkorb werden nicht geändert (auch nicht veröffentlicht, geplant oder wiederhergestellt);
|
||||
zuerst in WordPress aus dem Papierkorb holen.
|
||||
- Ist ein geplanter Eintrag über seinen Veröffentlichungszeitpunkt hinaus (verpasster Termin), würde jedes
|
||||
Speichern ihn sofort veröffentlichen – `lit-content-update`, `lit/revision-restore` und
|
||||
`lit/post-replace-with-copy` verweigern das dann. Mit dem Nutzer klären: jetzt veröffentlichen
|
||||
(`lit-post-publish`) oder neu planen (`lit/post-schedule`).
|
||||
|
||||
## Status
|
||||
|
||||
- Veröffentlichen, Zurückziehen und Planen sind eigene Werkzeuge (`lit-post-publish`, `lit-post-unpublish`,
|
||||
`lit-post-schedule` mit `date` = „JJJJ-MM-TT HH:MM“ in der Zeitzone der Website). Nur auf ausdrücklichen
|
||||
- Veröffentlichen, Zurückziehen und Planen sind eigene Werkzeuge (`lit-post-publish`, `lit/post-unpublish`,
|
||||
`lit/post-schedule` mit `date` = „JJJJ-MM-TT HH:MM“ in der Zeitzone der Website). Nur auf ausdrücklichen
|
||||
Wunsch des Nutzers verwenden.
|
||||
- `lit-post-trash` legt in den Papierkorb (nie endgültig) und braucht `confirm: true` nach Rückfrage.
|
||||
- `lit/post-trash` legt in den Papierkorb (nie endgültig) und braucht `confirm: true` nach Rückfrage.
|
||||
|
||||
## Sicher überarbeiten
|
||||
|
||||
1. `lit-post-duplicate` → Entwurfskopie; die Antwort enthält die `rev` der Kopie für `lit-content-update`.
|
||||
1. `lit/post-duplicate` → Entwurfskopie; die Antwort enthält die `rev` der Kopie für `lit-content-update`.
|
||||
2. Kopie mit `lit-content-update` (oder den Divi-Tools) bearbeiten und dem Nutzer die Vorschau zeigen.
|
||||
3. `lit-post-replace-with-copy` übernimmt die Kopie ins Original (ID, URL, Status und Menüeinträge bleiben).
|
||||
3. `lit/post-replace-with-copy` übernimmt die Kopie ins Original (ID, URL, Status und Menüeinträge bleiben).
|
||||
Voraussetzungen: die Kopie muss noch Entwurf oder ausstehend sein, `rev` des Originals aus
|
||||
`lit-content-get` ist Pflicht (sie deckt Titel, Inhalt, Auszug und Status ab – Felder/Meta, die direkt am
|
||||
Original geändert wurden, werden dabei überschrieben), und erst nach Rückfrage mit `confirm: true`
|
||||
@@ -53,14 +63,16 @@
|
||||
Papierkorb (`backup_id` in der Antwort); danach wandert die Kopie ebenfalls in den Papierkorb. Ist der
|
||||
Papierkorb der Website deaktiviert, wird der Austausch verweigert, statt die Kopie endgültig zu löschen.
|
||||
|
||||
Jede Änderung liefert `revision_id` (Stand vorher) und `log_id`. Rückgängig: `lit-revisions-list` →
|
||||
`lit-revision-restore` mit der `rev` des Eintrags (aus `lit-content-get`) und `confirm: true`. Revisionen
|
||||
umfassen Titel, Inhalt und Auszug – Felder und Begriffe nicht.
|
||||
Jede Änderung liefert `log_id`. Eine `revision_id` (Revision mit dem Stand vorher) liefern nur
|
||||
`lit-content-update`, `lit/post-replace-with-copy` und `lit/revision-restore`; bei allen anderen Tools ist sie
|
||||
`null`. Rückgängig: `lit/revisions-list` → `lit/revision-restore` mit der `rev` des Eintrags (aus
|
||||
`lit-content-get`) und `confirm: true`. Revisionen umfassen Titel, Inhalt und Auszug – Felder und Begriffe
|
||||
nicht (dafür bei `lit/post-replace-with-copy` die Sicherung `backup_id`).
|
||||
|
||||
## Wenn ein Schreibvorgang fehlschlägt
|
||||
|
||||
Schlägt ein Schreibvorgang nach ersten Änderungen fehl, meldet das Tool `lit_write_failed` (alles wurde
|
||||
zurückgenommen, nichts wurde gespeichert) oder `lit_partial_write` (die Rücknahme war unvollständig). Beide
|
||||
Fehler stecken als JSON (`code`, `message`, `hint`, ggf. weitere Felder) in der Fehlermeldung. Bei
|
||||
`lit_partial_write` vor einem erneuten Versuch immer erst den aktuellen Stand prüfen (`lit-content-get`,
|
||||
`lit-menu-get` o. Ä.) statt den Aufruf einfach zu wiederholen – dem `hint` folgen.
|
||||
Fehler stecken als JSON (`code`, `message`, `hint`, ggf. weitere Felder) in der Fehlermeldung. Schlägt ein
|
||||
Schreibvorgang nach ersten Änderungen fehl, ist `code` = `write_failed` (alles wurde zurückgenommen, nichts
|
||||
wurde gespeichert) oder `partial_write` (die Rücknahme war unvollständig). Bei `partial_write` vor einem
|
||||
erneuten Versuch immer erst den aktuellen Stand prüfen (`lit-content-get`,
|
||||
`lit/menu-get` o. Ä.) statt den Aufruf einfach zu wiederholen – dem `hint` folgen.
|
||||
|
||||
@@ -20,7 +20,8 @@ des verbundenen WordPress-Benutzers.
|
||||
Direkt sichtbar: `lit-guide`, `lit-site-status`, `lit-content-types`, `lit-content-query`,
|
||||
`lit-content-get`, `lit-content-create`, `lit-content-update`, `lit-post-publish`.
|
||||
|
||||
Über `mcp-adapter-discover-abilities` / `mcp-adapter-execute-ability` (Ability-Namen `lit/…`):
|
||||
Über `mcp-adapter-discover-abilities` / `mcp-adapter-execute-ability` (Ability-Namen `lit/…`; in Guides und
|
||||
Hinweisen immer so geschrieben):
|
||||
Status (`lit/post-unpublish`, `lit/post-schedule`, `lit/post-trash`), Kopien (`lit/post-duplicate`,
|
||||
`lit/post-replace-with-copy`), Revisionen (`lit/revisions-list`, `lit/revision-restore`), Menüs
|
||||
(`lit/menu-list`, `lit/menu-get`, `lit/menu-create`, `lit/menu-set-items`, `lit/menu-assign-location`),
|
||||
@@ -30,6 +31,8 @@ Weitere Themen: `lit-guide` mit `topic` = `content-types` oder `workflows`.
|
||||
|
||||
## Rückmeldungen
|
||||
|
||||
- Erfolg: `ok`, `summary`, `changed`, `warnings`, `revision_id` (Stand vorher), `log_id`, URLs.
|
||||
- Fehler: JSON mit `code`, `message`, `hint` (und ggf. `fields`). Dem `hint` folgen.
|
||||
- Erfolg: `ok`, `summary`, `changed`, `warnings`, `rev`, `revision_id` (Revision mit dem Stand vorher, nur bei
|
||||
Tools, die Inhalt überschreiben, sonst `null`), `log_id`, URLs.
|
||||
- Fehler: JSON mit `code` (z. B. `validation_failed`, `rev_mismatch`, `write_failed`, `partial_write`),
|
||||
`message`, `hint` (und ggf. `fields`). Dem `hint` folgen.
|
||||
- `confirm_required`: Nutzer fragen, dann mit `confirm: true` wiederholen.
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
## Neue Website aufsetzen
|
||||
|
||||
1. `lit-site-status` und `lit-content-types` lesen.
|
||||
2. Grundeinstellungen: `lit-wp-settings-get`, dann `lit-wp-settings-update` (Titel, Untertitel, Sprache,
|
||||
2. Grundeinstellungen: `lit/wp-settings-get`, dann `lit/wp-settings-update` (Titel, Untertitel, Sprache,
|
||||
Zeitzone, Datumsformat, Permalinks `/%postname%/`). `previous` in der Antwort enthält die alten Werte.
|
||||
3. Seiten als Entwürfe anlegen (`lit-content-create`), Hierarchie über `parent`.
|
||||
4. Startseite: Seite veröffentlichen (nach Rückfrage), dann
|
||||
`{"settings": {"page_on_front": <ID>, "show_on_front": "page"}}`.
|
||||
5. Menü: `lit-menu-create` (optional mit `location` aus `lit-menu-list`), Einträge mit `lit-menu-set-items`.
|
||||
5. Menü: `lit/menu-create` (optional mit `location` aus `lit/menu-list`), Einträge mit `lit/menu-set-items`.
|
||||
6. Veröffentlichen erst, wenn der Nutzer es ausdrücklich möchte.
|
||||
|
||||
## Menüs pflegen
|
||||
|
||||
- `lit-menu-get` liefert den Baum. Für größere Umbauten den Baum verändern und vollständig als `items` an
|
||||
`lit-menu-set-items` senden (bestehende Einträge mit `id`, neue ohne `id`). Wird der Baum unverändert
|
||||
- `lit/menu-get` liefert den Baum. Für größere Umbauten den Baum verändern und vollständig als `items` an
|
||||
`lit/menu-set-items` senden (bestehende Einträge mit `id`, neue ohne `id`). Wird der Baum unverändert
|
||||
zurückgeschickt, meldet das Tool „keine Änderung“ und schreibt nichts.
|
||||
- Für kleine Änderungen `operations`: `{op: "add", item, parent_id?, position?}`,
|
||||
`{op: "move", id, parent_id?, position?}`, `{op: "remove", id}` (Kinder eines entfernten Eintrags rücken
|
||||
|
||||
@@ -144,8 +144,9 @@ final class Errors
|
||||
trim('Speichern fehlgeschlagen: ' . $reason) . ' Die Änderung konnte nicht vollständig zurückgenommen '
|
||||
. 'werden. ' . $state,
|
||||
$hint ?? 'Den Aufruf nicht einfach wiederholen. Zuerst den aktuellen Stand prüfen (lit-content-get, bei '
|
||||
. 'einem neuen Entwurf lit-content-query) und ggf. über lit-revisions-list den vorherigen Stand '
|
||||
. 'wiederherstellen; dem Nutzer Bescheid geben.',
|
||||
. 'einem neuen Entwurf lit-content-query) und ggf. mit lit/revisions-list und lit/revision-restore '
|
||||
. '(über mcp-adapter-execute-ability) den vorherigen Stand wiederherstellen; dem Nutzer Bescheid '
|
||||
. 'geben.',
|
||||
500,
|
||||
$details
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ final class MenuAssignLocationAbility implements Ability
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Weist einer Theme-Position (siehe lit-menu-list) ein Menü zu; menu_id 0 entfernt die Zuweisung. '
|
||||
return 'Weist einer Theme-Position (siehe lit/menu-list) ein Menü zu; menu_id 0 entfernt die Zuweisung. '
|
||||
. 'Das vorher zugewiesene Menü steht in previous_menu_id.';
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ final class MenuAssignLocationAbility implements Ability
|
||||
}
|
||||
|
||||
if ($menuId > 0 && ! is_object(wp_get_nav_menu_object($menuId))) {
|
||||
return Errors::notFound(sprintf('Menü %d nicht gefunden.', $menuId), 'Menüs über lit-menu-list abrufen.');
|
||||
return Errors::notFound(sprintf('Menü %d nicht gefunden.', $menuId), 'Menüs über lit/menu-list abrufen.');
|
||||
}
|
||||
|
||||
$previous = MenuLocations::assigned()[$location] ?? 0;
|
||||
|
||||
@@ -34,7 +34,7 @@ final class MenuCreateAbility implements Ability
|
||||
public function description(): string
|
||||
{
|
||||
return 'Legt ein leeres Menü an und weist es optional einer Theme-Position zu. Einträge danach mit '
|
||||
. 'lit-menu-set-items setzen.';
|
||||
. 'lit/menu-set-items setzen.';
|
||||
}
|
||||
|
||||
public function inputSchema(): array
|
||||
@@ -43,7 +43,7 @@ final class MenuCreateAbility implements Ability
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'name' => ['type' => 'string', 'minLength' => 1],
|
||||
'location' => ['type' => 'string', 'description' => 'Theme-Position aus lit-menu-list.'],
|
||||
'location' => ['type' => 'string', 'description' => 'Theme-Position aus lit/menu-list.'],
|
||||
],
|
||||
'required' => ['name'],
|
||||
'additionalProperties' => false,
|
||||
@@ -88,7 +88,7 @@ final class MenuCreateAbility implements Ability
|
||||
return Errors::invalidInput(
|
||||
'Menü konnte nicht angelegt werden: ' . $menuId->get_error_message(),
|
||||
['name' => 'bereits vergeben oder ungültig'],
|
||||
'Vorhandene Menüs über lit-menu-list abrufen.'
|
||||
'Vorhandene Menüs über lit/menu-list abrufen.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ final class MenuGetAbility implements Ability
|
||||
return 'Liefert ein Menü als Baum (id, title, type post/taxonomy/custom, object/object_id, url, '
|
||||
. 'children). object_id und url sind bei post/taxonomy-Einträgen nur informativ (WordPress leitet '
|
||||
. 'sie aus dem verlinkten Objekt ab); nur bei custom-Einträgen ist url maßgeblich. Der Baum kann '
|
||||
. 'bearbeitet an lit-menu-set-items zurückgegeben werden.';
|
||||
. 'bearbeitet an lit/menu-set-items zurückgegeben werden.';
|
||||
}
|
||||
|
||||
public function inputSchema(): array
|
||||
@@ -78,7 +78,7 @@ final class MenuGetAbility implements Ability
|
||||
$menuId = (int) ($input['menu_id'] ?? 0);
|
||||
$menu = wp_get_nav_menu_object($menuId);
|
||||
if (! is_object($menu)) {
|
||||
return Errors::notFound(sprintf('Menü %d nicht gefunden.', $menuId), 'Menüs über lit-menu-list abrufen.');
|
||||
return Errors::notFound(sprintf('Menü %d nicht gefunden.', $menuId), 'Menüs über lit/menu-list abrufen.');
|
||||
}
|
||||
|
||||
$items = wp_get_nav_menu_items($menuId);
|
||||
|
||||
@@ -44,7 +44,7 @@ final class MenuSetItemsAbility implements Ability
|
||||
return 'Setzt die Einträge eines Menüs. Entweder items = vollständiger Zielbaum oder operations = Liste aus '
|
||||
. '{op: add, item, parent_id?, position?}, {op: move, id, parent_id?, position?}, {op: remove, id} '
|
||||
. '(Kinder eines entfernten Eintrags rücken an seine Stelle; position 0-basiert, ohne = ans Ende). '
|
||||
. 'Der Baum aus lit-menu-get kann bearbeitet unverändert zurückgegeben werden: bestehende Einträge '
|
||||
. 'Der Baum aus lit/menu-get kann bearbeitet unverändert zurückgegeben werden: bestehende Einträge '
|
||||
. 'werden über id erkannt, type/object/object_id und bei post/taxonomy auch url sind nur informativ '
|
||||
. 'und werden ignoriert; title wird nur bei Änderung zur eigenen Beschriftung, url nur bei custom '
|
||||
. 'geändert. Neue Einträge ohne id: type post|taxonomy mit object_id oder custom mit url (/pfad, '
|
||||
@@ -70,7 +70,7 @@ final class MenuSetItemsAbility implements Ability
|
||||
'items' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'object'],
|
||||
'description' => 'Vollständiger Zielbaum (Knoten wie in lit-menu-get, neue Einträge ohne id).',
|
||||
'description' => 'Vollständiger Zielbaum (Knoten wie in lit/menu-get, neue Einträge ohne id).',
|
||||
],
|
||||
'operations' => [
|
||||
'type' => 'array',
|
||||
@@ -132,7 +132,7 @@ final class MenuSetItemsAbility implements Ability
|
||||
$menuId = (int) ($input['menu_id'] ?? 0);
|
||||
$menu = wp_get_nav_menu_object($menuId);
|
||||
if (! is_object($menu)) {
|
||||
return Errors::notFound(sprintf('Menü %d nicht gefunden.', $menuId), 'Menüs über lit-menu-list abrufen.');
|
||||
return Errors::notFound(sprintf('Menü %d nicht gefunden.', $menuId), 'Menüs über lit/menu-list abrufen.');
|
||||
}
|
||||
|
||||
if (array_key_exists('items', $input) === array_key_exists('operations', $input)) {
|
||||
@@ -203,7 +203,7 @@ final class MenuSetItemsAbility implements Ability
|
||||
$after = FollowUp::run(
|
||||
fn () => MenuTree::build(array_values($this->currentItems($menuId))),
|
||||
$this->name() . ' re-read',
|
||||
'Das Menü ist gespeichert, der neue Stand konnte aber nicht gelesen werden; mit lit-menu-get abrufen.',
|
||||
'Das Menü ist gespeichert, der neue Stand konnte aber nicht gelesen werden; mit lit/menu-get abrufen.',
|
||||
$warnings
|
||||
);
|
||||
|
||||
|
||||
@@ -462,9 +462,10 @@ class MenuWriter
|
||||
$cause,
|
||||
$detail,
|
||||
sprintf('Menü %d ist möglicherweise teilweise geändert worden.', $menuId),
|
||||
'Den Aufruf nicht einfach wiederholen. Zuerst den aktuellen Stand mit lit-menu-get prüfen und ihn ggf. '
|
||||
. 'mit lit-menu-set-items anhand von previous_items (Stand vorher) wiederherstellen; entfernte '
|
||||
. 'Einträge müssen dabei neu angelegt werden. Dem Nutzer Bescheid geben.',
|
||||
'Den Aufruf nicht einfach wiederholen. Zuerst den aktuellen Stand mit lit/menu-get prüfen und ihn ggf. '
|
||||
. 'mit lit/menu-set-items (beide über mcp-adapter-execute-ability) anhand von previous_items (Stand '
|
||||
. 'vorher) wiederherstellen; entfernte Einträge müssen dabei neu angelegt werden. Dem Nutzer Bescheid '
|
||||
. 'geben.',
|
||||
['menu_id' => $menuId, 'previous_items' => MenuTree::build(array_values($current))]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ final class RevisionRestoreAbility implements Ability
|
||||
$revisionId = PostGuard::id($input['revision_id'] ?? null);
|
||||
$revision = $revisionId === null ? null : get_post($revisionId);
|
||||
if (! $revision instanceof WP_Post || $revision->post_type !== 'revision') {
|
||||
return Errors::notFound('Revision nicht gefunden.', 'Revisionen über lit-revisions-list abrufen.');
|
||||
return Errors::notFound('Revision nicht gefunden.', 'Revisionen über lit/revisions-list abrufen.');
|
||||
}
|
||||
|
||||
// field 'revision_id': post_parent 0 (an orphaned revision) is the only id this
|
||||
|
||||
@@ -41,7 +41,7 @@ final class RevisionsListAbility implements Ability
|
||||
{
|
||||
return 'Listet die gespeicherten Revisionen eines Eintrags (neueste zuerst) mit Datum, Autor und '
|
||||
. 'Textauszug. Revisionen von Divi-Inhalten sind mit divi: true markiert; ihr excerpt ist dann '
|
||||
. 'Shortcode-/Block-Markup, kein lesbarer Text. Wiederherstellen mit lit-revision-restore.';
|
||||
. 'Shortcode-/Block-Markup, kein lesbarer Text. Wiederherstellen mit lit/revision-restore.';
|
||||
}
|
||||
|
||||
public function inputSchema(): array
|
||||
|
||||
@@ -27,7 +27,7 @@ final class WpSettingsGetAbility implements Ability
|
||||
public function description(): string
|
||||
{
|
||||
return 'Liest die freigegebenen WordPress-Einstellungen (Allgemein, Lesen, Diskussion, Permalinks, '
|
||||
. 'Website-Icon) mit Typ und Beschreibung. Ändern mit lit-wp-settings-update.';
|
||||
. 'Website-Icon) mit Typ und Beschreibung. Ändern mit lit/wp-settings-update.';
|
||||
}
|
||||
|
||||
public function inputSchema(): array
|
||||
|
||||
@@ -44,7 +44,7 @@ final class WpSettingsUpdateAbility implements Ability
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Ändert freigegebene WordPress-Einstellungen (Schlüssel siehe lit-wp-settings-get). Alle Werte '
|
||||
return 'Ändert freigegebene WordPress-Einstellungen (Schlüssel siehe lit/wp-settings-get). Alle Werte '
|
||||
. 'werden vorher geprüft – bei einem Fehler wird nichts gespeichert. previous enthält die alten Werte. '
|
||||
. 'Gesperrt: admin_email, users_can_register, default_role, siteurl, home.';
|
||||
}
|
||||
@@ -98,7 +98,7 @@ final class WpSettingsUpdateAbility implements Ability
|
||||
return Errors::invalidInput(
|
||||
'Einstellungen ungültig; nichts wurde gespeichert.',
|
||||
$errors,
|
||||
'Werte gemäß fields korrigieren; aktuelle Werte über lit-wp-settings-get.'
|
||||
'Werte gemäß fields korrigieren; aktuelle Werte über lit/wp-settings-get.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -255,7 +255,8 @@ final class WpSettingsUpdateAbility implements Ability
|
||||
$cause,
|
||||
$cause->getMessage() . '; not restored: ' . implode(', ', $stuck),
|
||||
sprintf('Möglicherweise auf dem neuen, nicht vollständigen Stand: %s.', implode(', ', $stuck)),
|
||||
'Aktuelle Werte über lit/wp-settings-get prüfen und ggf. erneut auf die gewünschten Werte setzen.',
|
||||
'Aktuelle Werte über lit/wp-settings-get (mcp-adapter-execute-ability) prüfen und ggf. erneut auf die '
|
||||
. 'gewünschten Werte setzen.',
|
||||
['keys' => $stuck]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ final class PostDuplicateAbility implements Ability
|
||||
{
|
||||
return 'Legt eine Entwurfskopie eines Eintrags an (Inhalt, Felder/Meta, Begriffe, Beitragsbild, Template, '
|
||||
. 'Divi-Einstellungen). Typischer Ablauf: Kopie bearbeiten, prüfen, dann mit '
|
||||
. 'lit-post-replace-with-copy ins Original übernehmen – so bleibt die veröffentlichte Seite bis dahin '
|
||||
. 'lit/post-replace-with-copy ins Original übernehmen – so bleibt die veröffentlichte Seite bis dahin '
|
||||
. 'unverändert. Die Antwort enthält die rev der Kopie für lit-content-update.';
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ final class PostReplaceWithCopyAbility implements Ability
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Übernimmt Titel, Inhalt, Felder/Meta und Begriffe einer mit lit-post-duplicate erstellten Kopie '
|
||||
return 'Übernimmt Titel, Inhalt, Felder/Meta und Begriffe einer mit lit/post-duplicate erstellten Kopie '
|
||||
. 'ins Original. ID, URL, Status und Menüeinträge des Originals bleiben; vorher entsteht eine '
|
||||
. 'Revision und eine vollständige Sicherung des Originals (im Papierkorb, backup_id), die Kopie kommt '
|
||||
. 'danach in den Papierkorb. Die Kopie muss Entwurf oder ausstehend sein. rev des Originals aus '
|
||||
@@ -165,8 +165,8 @@ final class PostReplaceWithCopyAbility implements Ability
|
||||
if ($copy->post_type !== $original->post_type || $copyOf !== $originalId || $copyId === $originalId) {
|
||||
return Errors::invalidInput(
|
||||
'Die Kopie gehört nicht zu diesem Original.',
|
||||
['copy_id' => sprintf('keine mit lit-post-duplicate erstellte Kopie von %d', $originalId)],
|
||||
'Kopie mit lit-post-duplicate aus diesem Original erstellen.'
|
||||
['copy_id' => sprintf('keine mit lit/post-duplicate erstellte Kopie von %d', $originalId)],
|
||||
'Kopie mit lit/post-duplicate aus diesem Original erstellen.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ final class PostReplaceWithCopyAbility implements Ability
|
||||
return Errors::invalidInput(
|
||||
sprintf('Die Kopie hat den Status "%s"; übernommen wird nur ein Entwurf.', $copy->post_status),
|
||||
['copy_id' => 'Kopie muss Entwurf oder ausstehend sein'],
|
||||
'Mit lit-post-duplicate eine neue Kopie erstellen (bzw. die Kopie in WordPress wiederherstellen).'
|
||||
'Mit lit/post-duplicate eine neue Kopie erstellen (bzw. die Kopie in WordPress wiederherstellen).'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -290,9 +290,9 @@ final class PostReplaceWithCopyAbility implements Ability
|
||||
$backupId
|
||||
),
|
||||
'Den Aufruf nicht einfach wiederholen. Zuerst den Stand mit lit-content-get prüfen; Titel, Inhalt '
|
||||
. 'und Auszug lassen sich mit lit/revision-restore (revision_id) zurückholen, Felder/Meta und '
|
||||
. 'Begriffe stehen in der Sicherung backup_id (in WordPress aus dem Papierkorb holen). Dem '
|
||||
. 'Nutzer Bescheid geben.',
|
||||
. 'und Auszug lassen sich mit lit/revision-restore (revision_id, über '
|
||||
. 'mcp-adapter-execute-ability) zurückholen, Felder/Meta und Begriffe stehen in der Sicherung '
|
||||
. 'backup_id (in WordPress aus dem Papierkorb holen). Dem Nutzer Bescheid geben.',
|
||||
$details
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,8 @@ final class ErrorsTest extends TestCase
|
||||
self::assertStringContainsString('Eintrag 5', $payload['message']);
|
||||
self::assertStringContainsString('Revision 811', $payload['message']);
|
||||
self::assertStringContainsString('lit-content-get', $payload['hint']);
|
||||
self::assertStringContainsString('lit-revisions-list', $payload['hint']);
|
||||
self::assertStringContainsString('lit/revisions-list', $payload['hint']);
|
||||
self::assertStringContainsString('mcp-adapter-execute-ability', $payload['hint']);
|
||||
self::assertStringContainsString('nicht einfach wiederholen', $payload['hint']);
|
||||
self::assertSame(5, $payload['post_id']);
|
||||
self::assertSame(811, $payload['revision_id']);
|
||||
|
||||
@@ -36,14 +36,14 @@ final class WriteFailedTest extends TestCase
|
||||
'Menü nicht gespeichert.',
|
||||
false,
|
||||
state: 'Das Menü ist teilweise geändert.',
|
||||
hint: 'Mit lit-menu-get prüfen.',
|
||||
hint: 'Mit lit/menu-get prüfen.',
|
||||
details: ['menu_id' => 3]
|
||||
))->toError();
|
||||
|
||||
self::assertSame('lit_partial_write', $error->get_error_code());
|
||||
self::assertStringContainsString('Das Menü ist teilweise geändert.', $error->get_error_message());
|
||||
self::assertStringNotContainsString('Entwurf', $error->get_error_message());
|
||||
self::assertSame('Mit lit-menu-get prüfen.', $error->get_error_data()['hint']);
|
||||
self::assertSame('Mit lit/menu-get prüfen.', $error->get_error_data()['hint']);
|
||||
self::assertSame(3, $error->get_error_data()['menu_id']);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Guides;
|
||||
|
||||
use LIT\MCP\Abilities\Ability;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Guides, descriptions and hints must name tools the way the client sees them: direct
|
||||
* tools as lit-<name>, indirect ones as lit/<name> (reached via mcp-adapter-execute-ability),
|
||||
* and error codes without the internal lit_ prefix of the WP_Error code.
|
||||
*/
|
||||
final class ToolNamingTest extends TestCase
|
||||
{
|
||||
/** @return array<string, bool> Ability name without "lit/" => isDirect(). */
|
||||
private static function abilities(): array
|
||||
{
|
||||
$root = dirname(__DIR__, 3);
|
||||
$abilities = [];
|
||||
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root . '/src'));
|
||||
foreach ($files as $file) {
|
||||
if (! str_ends_with((string) $file, 'Ability.php')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = 'LIT\\MCP\\' . str_replace(['/', '.php'], ['\\', ''], substr((string) $file, strlen($root . '/src/')));
|
||||
$reflection = new ReflectionClass($class);
|
||||
if ($reflection->isInterface() || ! $reflection->implementsInterface(Ability::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var Ability $ability */
|
||||
$ability = $reflection->newInstanceWithoutConstructor();
|
||||
$abilities[substr($ability->name(), strlen('lit/'))] = $ability->isDirect();
|
||||
}
|
||||
|
||||
ksort($abilities);
|
||||
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
/** @return array<string, string> Path => content of the guides and all PHP sources. */
|
||||
private static function texts(): array
|
||||
{
|
||||
$root = dirname(__DIR__, 3);
|
||||
$texts = [];
|
||||
foreach (glob($root . '/docs/guides/*.md') ?: [] as $file) {
|
||||
$texts[$file] = (string) file_get_contents($file);
|
||||
}
|
||||
|
||||
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root . '/src'));
|
||||
foreach ($files as $file) {
|
||||
if (str_ends_with((string) $file, '.php')) {
|
||||
$texts[(string) $file] = (string) file_get_contents((string) $file);
|
||||
}
|
||||
}
|
||||
|
||||
return $texts;
|
||||
}
|
||||
|
||||
public function testIndirectToolsAreNeverNamedLikeDirectOnes(): void
|
||||
{
|
||||
$abilities = self::abilities();
|
||||
self::assertArrayHasKey('menu-set-items', $abilities);
|
||||
|
||||
foreach (self::texts() as $path => $text) {
|
||||
preg_match_all('/\blit-([a-z]+(?:-[a-z]+)*)/', $text, $matches);
|
||||
foreach ($matches[1] as $name) {
|
||||
self::assertFalse(
|
||||
($abilities[$name] ?? true) === false,
|
||||
sprintf('%s names the indirect tool lit/%s as lit-%s', basename($path), $name, $name)
|
||||
);
|
||||
}
|
||||
|
||||
preg_match_all('#\blit/([a-z]+(?:-[a-z]+)*)#', $text, $matches);
|
||||
foreach ($matches[1] as $name) {
|
||||
self::assertFalse(
|
||||
($abilities[$name] ?? false) === true && str_ends_with($path, '.md'),
|
||||
sprintf('%s names the direct tool lit-%s as lit/%s', basename($path), $name, $name)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testGuidesUseTheErrorCodesTheClientSees(): void
|
||||
{
|
||||
foreach (self::texts() as $path => $text) {
|
||||
if (str_ends_with($path, '.md')) {
|
||||
self::assertDoesNotMatchRegularExpression('/`lit_(?!mcp)[a-z_]+`/', $text, basename($path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testOverviewListsExactlyTheDirectTools(): void
|
||||
{
|
||||
$overview = (string) file_get_contents(dirname(__DIR__, 3) . '/docs/guides/overview.md');
|
||||
preg_match('/Direkt sichtbar:(.*?)\n\n/s', $overview, $section);
|
||||
preg_match_all('/`lit-([a-z-]+)`/', $section[1] ?? '', $listed);
|
||||
|
||||
$direct = array_keys(array_filter(self::abilities()));
|
||||
$listedNames = $listed[1];
|
||||
sort($listedNames);
|
||||
|
||||
self::assertSame($direct, $listedNames);
|
||||
}
|
||||
}
|
||||
@@ -360,7 +360,8 @@ final class MenuWriterTest extends TestCase
|
||||
self::assertSame('lit_partial_write', $error->get_error_code());
|
||||
self::assertSame(3, $error->get_error_data()['menu_id']);
|
||||
self::assertSame('Home', $error->get_error_data()['previous_items'][0]['title']);
|
||||
self::assertStringContainsString('lit-menu-get', $error->get_error_data()['hint']);
|
||||
self::assertStringContainsString('lit/menu-get', $error->get_error_data()['hint']);
|
||||
self::assertStringContainsString('mcp-adapter-execute-ability', $error->get_error_data()['hint']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user