From 9669ccb36a5bf4ba0667dadf9f45fb0007956dfe Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Fri, 25 Sep 2026 15:15:28 +0200 Subject: [PATCH] docs: add per-ability isAvailable() to plan and spec Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7 --- .../plans/2026-09-25-etappe-0-anbindung.md | 44 +++++++++++++++++-- .../specs/2026-09-25-wp-lit-mcp-design.md | 1 + 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/superpowers/plans/2026-09-25-etappe-0-anbindung.md b/docs/superpowers/plans/2026-09-25-etappe-0-anbindung.md index 6787380..19b0cb1 100644 --- a/docs/superpowers/plans/2026-09-25-etappe-0-anbindung.md +++ b/docs/superpowers/plans/2026-09-25-etappe-0-anbindung.md @@ -670,10 +670,10 @@ Co-Authored-By: Claude Opus 5.5 (1M context) " **Interfaces:** - Consumes: `Access::currentUserMayUse()`, `Access::CAPABILITY` (Task 2). - Produces: - - `interface Ability { name(): string; label(): string; description(): string; inputSchema(): array; outputSchema(): ?array; annotations(): array; isDirect(): bool; permission(array $input): bool|\WP_Error; execute(array $input): mixed; }` + - `interface Ability { name(): string; label(): string; description(): string; inputSchema(): array; outputSchema(): ?array; annotations(): array; isDirect(): bool; isAvailable(): bool; permission(array $input): bool|\WP_Error; execute(array $input): mixed; }` - `Errors::forbidden(string $message): \WP_Error` (Code `lit_forbidden`), `Errors::invalidInput(string $message, array $fields = []): \WP_Error` (Code `lit_validation_failed`), `Errors::notFound(string $message): \WP_Error` (Code `lit_not_found`), `Errors::internal(): \WP_Error` (Code `lit_internal_error`). - `interface Integration { id(): string; isAvailable(): bool; /** @return list */ abilities(): array; }` - - `IntegrationRegistry::__construct(Integration ...$integrations)`, `available(): list`, `abilities(): list`, `directAbilityNames(): list`. + - `IntegrationRegistry::__construct(Integration ...$integrations)`, `available(): list`, `abilities(): list` (nur Abilities verfügbarer Integrationen, die selbst `isAvailable()` true liefern), `directAbilityNames(): list`. - `AbilityRegistrar::CATEGORY = 'lit-mcp'`, `__construct(IntegrationRegistry $registry)`, `register(): void`, `registerCategory(): void`, `registerAbilities(): void`, `argsFor(Ability $ability): array`. - [ ] **Step 1: Failing Tests** @@ -710,11 +710,23 @@ final class IntegrationRegistryTest extends TestCase self::assertSame(['lit/a'], $registry->directAbilityNames()); } - private function ability(string $name, bool $direct): Ability + public function testUnavailableAbilitiesAreSkippedEvenInAvailableIntegration(): void + { + $working = $this->ability('lit/a', true); + $broken = $this->ability('lit/needs-missing-plugin', true, false); + + $registry = new IntegrationRegistry($this->integration('core', true, [$working, $broken])); + + self::assertSame([$working], $registry->abilities()); + self::assertSame(['lit/a'], $registry->directAbilityNames()); + } + + private function ability(string $name, bool $direct, bool $available = true): Ability { $ability = Mockery::mock(Ability::class); $ability->allows('name')->andReturn($name); $ability->allows('isDirect')->andReturn($direct); + $ability->allows('isAvailable')->andReturn($available); return $ability; } @@ -772,6 +784,7 @@ final class AbilityRegistrarTest extends TestCase $ability->allows('outputSchema')->andReturn(['type' => 'object']); $ability->allows('annotations')->andReturn(['readonly' => true]); $ability->allows('isDirect')->andReturn(true); + $ability->allows('isAvailable')->andReturn(true); return $ability; } @@ -893,6 +906,13 @@ interface Ability /** Whether the ability is listed as its own MCP tool (otherwise only via meta-tools). */ public function isDirect(): bool; + /** + * Whether the ability can work on this installation (e.g. a required + * plugin function or version exists). Unavailable abilities are not + * registered at all, so they can never fail at call time. + */ + public function isAvailable(): bool; + /** @param array $input */ public function permission(array $input): bool|WP_Error; @@ -1002,7 +1022,11 @@ final class IntegrationRegistry { $abilities = []; foreach ($this->available() as $integration) { - array_push($abilities, ...$integration->abilities()); + foreach ($integration->abilities() as $ability) { + if ($ability->isAvailable()) { + $abilities[] = $ability; + } + } } return $abilities; @@ -1476,6 +1500,11 @@ final class GuideAbility implements Ability return true; } + public function isAvailable(): bool + { + return true; + } + public function permission(array $input): bool|WP_Error { return true; @@ -1731,6 +1760,11 @@ final class SiteStatusAbility implements Ability return true; } + public function isAvailable(): bool + { + return true; + } + public function permission(array $input): bool|WP_Error { return true; @@ -1843,9 +1877,11 @@ final class ServerRegistrarTest extends TestCase $direct = Mockery::mock(Ability::class); $direct->allows('name')->andReturn('lit/guide'); $direct->allows('isDirect')->andReturn(true); + $direct->allows('isAvailable')->andReturn(true); $indirect = Mockery::mock(Ability::class); $indirect->allows('name')->andReturn('lit/rare-tool'); $indirect->allows('isDirect')->andReturn(false); + $indirect->allows('isAvailable')->andReturn(true); $core = Mockery::mock(Integration::class); $core->allows('id')->andReturn('core'); diff --git a/docs/superpowers/specs/2026-09-25-wp-lit-mcp-design.md b/docs/superpowers/specs/2026-09-25-wp-lit-mcp-design.md index bfd73e2..3c97bcc 100644 --- a/docs/superpowers/specs/2026-09-25-wp-lit-mcp-design.md +++ b/docs/superpowers/specs/2026-09-25-wp-lit-mcp-design.md @@ -138,6 +138,7 @@ src/ Admin/ Einstellungsseite, Protokoll-Ansicht (Settings API, WP_List_Table) Integrations/ Integration.php Interface: isAvailable(), abilities(), prompts() + (jede Ability hat zusätzlich isAvailable(): nicht verfügbare werden nicht registriert) WordPress/ Content, Schema, Status/Kopie, Revisionen, Menüs, Einstellungen Media/ Medien-Tools, Verwendungssuche Divi/