diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a55a796 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +/tests export-ignore +/docs/superpowers export-ignore +/.superpowers export-ignore +/phpunit.xml.dist export-ignore +/phpcs.xml.dist export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3be7de --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/vendor.lit-bak/ +.phpunit.result.cache diff --git a/assets/css/admin.css b/assets/css/admin.css new file mode 100644 index 0000000..6f8eac9 --- /dev/null +++ b/assets/css/admin.css @@ -0,0 +1,373 @@ +/* Einstellungen → LIT MCP. Only small additions to WordPress' own admin styles. */ + +/* Header: title left, logo right; wraps instead of overlapping on narrow screens. */ +.lit-mcp-header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 8px 16px; +} + +.lit-mcp-logo { + display: block; + height: 30px; + width: auto; +} + +@media screen and (max-width: 782px) { + .lit-mcp-logo { + height: 22px; + } +} + +.lit-mcp .nav-tab-wrapper { + margin-bottom: 1em; +} + +/* Muted secondary text (tool names, client URLs). */ +.lit-mcp-muted, +.lit-mcp-tool-name { + color: #646970; + font-size: 12px; + overflow-wrap: anywhere; +} + +.lit-mcp-tool-name { + font-family: Consolas, Monaco, monospace; +} + +/* Tab "Verbindung". */ +.lit-mcp-steps { + max-width: 50rem; +} + +.lit-mcp-steps li { + margin-bottom: 0.75em; +} + +.lit-mcp-copy-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px 8px; + margin-top: 6px; +} + +.lit-mcp-copy-row input.large-text { + flex: 1 1 22rem; + width: auto; +} + +.lit-mcp-copy-row .success { + color: #007017; +} + +.lit-mcp-status { + margin: 0 0 1em; +} + +.lit-mcp-status li { + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 0 6px; + margin-bottom: 8px; +} + +.lit-mcp-status .dashicons { + align-self: center; +} + +.lit-mcp-status-detail { + color: #50575e; +} + +.lit-mcp-status-yes-alt .dashicons, +.lit-mcp-result-yes-alt .dashicons { + color: #00a32a; +} + +.lit-mcp-status-warning .dashicons, +.lit-mcp-result-warning .dashicons { + color: #dba617; +} + +.lit-mcp-status-dismiss .dashicons, +.lit-mcp-result-dismiss .dashicons { + color: #d63638; +} + +/* Tab "Zugriff". */ +.lit-mcp-connections + .description { + margin-top: 8px; +} + +.lit-mcp-connections .column-actions { + width: 8em; + text-align: right; +} + +@media screen and (max-width: 782px) { + .lit-mcp-connections .column-created { + display: none; + } + + .lit-mcp-connections .column-actions { + width: auto; + } + + .lit-mcp-copy-row input.large-text { + font-size: 13px; + } +} + +/* Tab "Tools": legend, collapsible groups (WordPress postboxes) with aligned columns. */ +.lit-mcp-legend { + display: grid; + grid-template-columns: max-content 1fr; + gap: 2px 12px; + margin: 1em 0; +} + +.lit-mcp-legend dt { + font-weight: 600; +} + +.lit-mcp-legend dd { + margin: 0; +} + +.lit-mcp-groups .postbox-header { + flex-wrap: wrap; + gap: 0 12px; + padding-left: 12px; +} + +.lit-mcp-groups .postbox .hndle { + cursor: pointer; + justify-content: flex-start; + gap: 6px; + padding: 8px 0; + margin: 0; + font-size: 14px; + line-height: 1.4; +} + +.lit-mcp-groups .hndle .count { + color: #646970; + font-weight: 400; +} + +.lit-mcp-group-summary { + color: #646970; + font-size: 13px; + font-weight: 400; +} + +.lit-mcp-group-summary::before { + content: "– "; +} + +.lit-mcp-groups .postbox:not(.closed) .lit-mcp-group-summary { + display: none; +} + +.lit-mcp-group-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 4px 16px; + padding: 6px 0; +} + +.lit-mcp-group-controls .description { + font-style: italic; +} + +.lit-mcp-groups .handle-actions { + padding-right: 6px; +} + +/* Same arrow indicator as WordPress' own meta boxes (core scopes it to .meta-box-sortables). */ +.lit-mcp-groups .postbox .toggle-indicator::before { + content: "\f142"; + content: "\f142" / ''; + display: inline-block; + font: normal 20px/1 dashicons; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-decoration: none; +} + +.lit-mcp-groups .postbox.closed .toggle-indicator::before { + content: "\f140"; + content: "\f140" / ''; +} + +.lit-mcp-groups .postbox .inside { + margin: 0; + padding: 0; +} + +.lit-mcp-tools-table { + table-layout: fixed; + border: 0; + box-shadow: none; +} + +.lit-mcp-tools-table .column-tool { + width: 55%; +} + +.lit-mcp-tools-table .column-kind { + width: 15%; +} + +.lit-mcp-tools-table .column-mode { + width: 30%; +} + +.lit-mcp-tools-table td { + vertical-align: middle; +} + +.lit-mcp-tools-table select { + max-width: 100%; +} + +.lit-mcp-tools-table .description { + margin: 4px 0 0; +} + +.lit-mcp-kind { + color: #50575e; + white-space: nowrap; +} + +.lit-mcp-kind .dashicons { + font-size: 16px; + width: 16px; + height: 16px; + vertical-align: text-bottom; + color: #787c82; +} + +@media screen and (max-width: 782px) { + .lit-mcp-group-controls { + order: 3; + flex-basis: 100%; + padding-bottom: 10px; + } + + .lit-mcp-tools-table .column-tool { + width: 45%; + } + + .lit-mcp-tools-table .column-kind { + width: 12%; + } + + .lit-mcp-tools-table .column-mode { + width: 43%; + } + + .lit-mcp-tools-table th, + .lit-mcp-tools-table td { + padding-left: 6px; + padding-right: 6px; + } + + /* Icon only on phones; the word stays for screen readers (and as tooltip-free fallback). */ + .lit-mcp-kind-text { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + } + + .lit-mcp-tools-table select { + width: 100%; + font-size: 14px; + padding-right: 20px; + background-position: right 2px top 55%; + } +} + +/* Tab "Protokoll": a core-style list table; the change is the wide primary column. */ +.lit-mcp .lit_mcp_log_entries .column-object { + width: 24%; +} + +.lit-mcp .lit_mcp_log_entries .column-user { + width: 12em; +} + +.lit-mcp .lit_mcp_log_entries .column-created_at { + width: 11em; +} + +.lit-mcp .lit_mcp_log_entries .column-object, +.lit-mcp .lit_mcp_log_entries .column-user .lit-mcp-muted { + overflow-wrap: anywhere; +} + +.lit-mcp-summary { + margin-bottom: 2px; +} + +/* Non-ok results: a notice-coloured badge and a marked row (like core's unapproved comments); + #the-list beats core's "no shadow on the last row". */ +.lit-mcp-badge { + display: inline-block; + padding: 0 6px; + border-radius: 2px; + font-size: 12px; + font-weight: 600; + line-height: 1.7; +} + +.lit-mcp-badge-warning { + background: #fcf9e8; + color: #614a19; + box-shadow: inset 0 0 0 1px #dba617; +} + +.lit-mcp-badge-error { + background: #fcf0f1; + color: #8a2424; + box-shadow: inset 0 0 0 1px #d63638; +} + +.lit-mcp #the-list tr.lit-mcp-row-warning > :first-child { + box-shadow: inset 4px 0 0 #dba617; +} + +.lit-mcp #the-list tr.lit-mcp-row-error > :first-child { + box-shadow: inset 4px 0 0 #d63638; +} + +.lit-mcp .tablenav .actions select { + max-width: 14rem; +} + +.lit-mcp-date { + float: left; + display: flex; + align-items: center; + gap: 4px; + margin-right: 6px; +} + +.lit-mcp .tablenav .lit-mcp-reset { + display: inline-block; + line-height: 2.30769231; + margin-left: 8px; +} + +/* Tab "Limits". */ +.lit-mcp-unit { + margin-left: 4px; +} diff --git a/assets/img/loosen-it-logo.svg b/assets/img/loosen-it-logo.svg new file mode 100644 index 0000000..8206141 --- /dev/null +++ b/assets/img/loosen-it-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..e950819 --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,45 @@ +/* Einstellungen → LIT MCP: copy the server URL, collapsible tool groups, filter paging. */ +( function () { + 'use strict'; + + document.addEventListener( 'DOMContentLoaded', function () { + var hideTimeout; + + // "URL kopieren" – like WordPress' "Copy URL to clipboard" in the media library. + if ( window.ClipboardJS && document.querySelector( '.lit-mcp-copy' ) ) { + new window.ClipboardJS( '.lit-mcp-copy' ).on( 'success', function ( event ) { + var success = event.trigger.parentNode.querySelector( '.success' ); + + event.clearSelection(); + event.trigger.focus(); + if ( success ) { + clearTimeout( hideTimeout ); + success.classList.remove( 'hidden' ); + hideTimeout = setTimeout( function () { + success.classList.add( 'hidden' ); + }, 3000 ); + } + if ( window.wp && window.wp.a11y && window.litMcpAdmin ) { + window.wp.a11y.speak( window.litMcpAdmin.copied ); + } + } ); + } + + // Tool groups: WordPress' own postbox toggles, closed state saved per user. + if ( window.postboxes && window.pagenow && document.querySelector( '.lit-mcp-groups' ) ) { + window.postboxes.add_postbox_toggles( window.pagenow ); + } + + // Protokoll: a changed filter starts again on page 1. + document.querySelectorAll( '.lit-mcp .tablenav .actions select, .lit-mcp .tablenav .actions input' ).forEach( + function ( control ) { + control.addEventListener( 'change', function () { + var paged = control.form && control.form.querySelector( 'input[name="paged"]' ); + if ( paged ) { + paged.value = '1'; + } + } ); + } + ); + } ); +}() ); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e7c6ee8 --- /dev/null +++ b/composer.json @@ -0,0 +1,39 @@ +{ + "name": "loosen-it/wp-lit-mcp", + "description": "MCP-Server für WordPress, Divi 5 und Pods mit OAuth für claude.ai", + "type": "wordpress-plugin", + "license": "GPL-2.0-or-later", + "require": { + "php": ">=8.1", + "automattic/jetpack-autoloader": "^3.0", + "wordpress/mcp-adapter": "^0.5", + "wp-media/mcp-oauth": "^1.1" + }, + "require-dev": { + "brain/monkey": "^2.6", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "mockery/mockery": "^1.6", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.10", + "wp-coding-standards/wpcs": "^3.1" + }, + "autoload": { + "psr-4": { "LIT\\MCP\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "LIT\\MCP\\Tests\\": "tests/" } + }, + "scripts": { + "test": "@php vendor/bin/phpunit", + "lint": "@php vendor/bin/phpcs" + }, + "config": { + "sort-packages": true, + "platform": { "php": "8.1" }, + "allow-plugins": { + "automattic/jetpack-autoloader": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..c953e95 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2982 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f61708b5ca7588f7b1c5b47774383472", + "packages": [ + { + "name": "automattic/jetpack-autoloader", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/Automattic/jetpack-autoloader.git", + "reference": "e7e49a4e2f16cb2dfd3e58c478499a60d7d51839" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/e7e49a4e2f16cb2dfd3e58c478499a60d7d51839", + "reference": "e7e49a4e2f16cb2dfd3e58c478499a60d7d51839", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "^4.2.8", + "composer/composer": "^1.1 || ^2.0", + "yoast/phpunit-polyfills": "^1.1.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin", + "autotagger": true, + "mirror-repo": "Automattic/jetpack-autoloader", + "branch-alias": { + "dev-trunk": "3.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" + }, + "version-constants": { + "::VERSION": "src/AutoloadGenerator.php" + } + }, + "autoload": { + "psr-4": { + "Automattic\\Jetpack\\Autoloader\\": "src" + }, + "classmap": [ + "src/AutoloadGenerator.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Creates a custom autoloader for a plugin or theme.", + "keywords": [ + "autoload", + "autoloader", + "composer", + "jetpack", + "plugin", + "wordpress" + ], + "support": { + "source": "https://github.com/Automattic/jetpack-autoloader/tree/v3.1.3" + }, + "time": "2024-11-04T09:23:56+00:00" + }, + { + "name": "wordpress/mcp-adapter", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/mcp-adapter.git", + "reference": "7bfc49f46b3ea7544dded49d5a606089f825a80b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/mcp-adapter/zipball/7bfc49f46b3ea7544dded49d5a606089f825a80b", + "reference": "7bfc49f46b3ea7544dded49d5a606089f825a80b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.4 || ^8.0", + "wordpress/php-mcp-schema": "^0.1.0" + }, + "require-dev": { + "automattic/vipwpcs": "^3.0", + "php-stubs/wordpress-stubs": "^6.9", + "php-stubs/wp-cli-stubs": "^2.12", + "phpcompatibility/php-compatibility": "10.x-dev as 9.99.99", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "phpstan/extension-installer": "^1.3", + "phpstan/php-8-stubs": "^0.4.24", + "phpstan/phpstan": "^2.1.22", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.0", + "szepeviktor/phpstan-wordpress": "^2.0", + "wp-phpunit/wp-phpunit": "^6.5", + "wpackagist-plugin/plugin-check": "^1.6", + "yoast/phpunit-polyfills": "^4.0" + }, + "type": "wordpress-plugin", + "extra": { + "installer-paths": { + "vendor/{$vendor}/{$name}/": [ + "wpackagist-plugin/plugin-check" + ] + } + }, + "autoload": { + "psr-4": { + "WP\\MCP\\": "includes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "WordPress AI Team", + "homepage": "https://make.wordpress.org/ai/" + } + ], + "description": "Adapter for Abilities API, letting WordPress abilities to be used as MCP tools, resources or prompts", + "homepage": "https://github.com/wordpress/mcp-adapter", + "keywords": [ + "abilities-api", + "adapter", + "ai", + "api", + "integration", + "mcp", + "model-context-protocol", + "wordpress" + ], + "support": { + "issues": "https://github.com/wordpress/mcp-adapter/issues", + "source": "https://github.com/wordpress/mcp-adapter" + }, + "time": "2026-04-14T12:12:58+00:00" + }, + { + "name": "wordpress/php-mcp-schema", + "version": "v0.1.3", + "source": { + "type": "git", + "url": "https://github.com/WordPress/php-mcp-schema", + "reference": "b2fcf97aa023ce46e9f03493c194a72d5a46bea2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/php-mcp-schema/zipball/b2fcf97aa023ce46e9f03493c194a72d5a46bea2", + "reference": "b2fcf97aa023ce46e9f03493c194a72d5a46bea2", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "WP\\McpSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "WordPress", + "homepage": "https://wordpress.org" + } + ], + "description": "PHP DTOs for the Model Context Protocol (MCP) specification", + "homepage": "https://github.com/WordPress/php-mcp-schema", + "keywords": [ + "dto", + "mcp", + "model-context-protocol", + "php", + "schema" + ], + "time": "2026-08-10T09:12:14+00:00" + }, + { + "name": "wp-media/apply-filters-typed", + "version": "v1.2", + "source": { + "type": "git", + "url": "https://github.com/wp-media/apply-filters-typed.git", + "reference": "d8f5bca83e85196d1f01d92283b63ef6f886ac4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-media/apply-filters-typed/zipball/d8f5bca83e85196d1f01d92283b63ef6f886ac4d", + "reference": "d8f5bca83e85196d1f01d92283b63ef6f886ac4d", + "shasum": "" + }, + "require-dev": { + "phpcompatibility/phpcompatibility-wp": "^2.0", + "phpstan/extension-installer": "^1.4", + "szepeviktor/phpstan-wordpress": "^1.3", + "wp-coding-standards/wpcs": "^3", + "wp-media/phpunit": "^3" + }, + "type": "library", + "autoload": { + "files": [ + "functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0-or-later" + ], + "authors": [ + { + "name": "WP Media", + "email": "contact@wp-media.me" + } + ], + "description": "Library for usage of WordPress filters in a safe-type way", + "support": { + "issues": "https://github.com/wp-media/apply-filters-typed/issues", + "source": "https://github.com/wp-media/apply-filters-typed/tree/v1.2" + }, + "time": "2024-09-16T12:21:47+00:00" + }, + { + "name": "wp-media/mcp-oauth", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/wp-media/mcp-oauth.git", + "reference": "2f6b10d8bb81a9ba8b2b33bb0450995486262b09" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-media/mcp-oauth/zipball/2f6b10d8bb81a9ba8b2b33bb0450995486262b09", + "reference": "2f6b10d8bb81a9ba8b2b33bb0450995486262b09", + "shasum": "" + }, + "require": { + "php": ">=7.4", + "wordpress/mcp-adapter": "^0.5", + "wp-media/apply-filters-typed": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1", + "php": ">=7.4", + "php-stubs/wordpress-tests-stubs": "^6.8", + "phpcompatibility/phpcompatibility-wp": "^2.0", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "roave/security-advisories": "dev-master", + "szepeviktor/phpstan-wordpress": "^2.0", + "wp-coding-standards/wpcs": "^3", + "wp-media/phpunit": "^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "WPMedia\\MCP\\OAuth\\": "inc/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "WP Media", + "email": "contact@wp-media.me" + } + ], + "description": "MCP OAuth layer for the WordPress MCP Adapter", + "homepage": "https://github.com/wp-media/mcp-oauth", + "keywords": [ + "plugin", + "wordpress", + "wp" + ], + "support": { + "issues": "https://github.com/wp-media/mcp-oauth/issues", + "source": "https://github.com/wp-media/mcp-oauth/tree/v1.1.2" + }, + "time": "2026-09-02T18:52:01+00:00" + } + ], + "packages-dev": [ + { + "name": "antecedent/patchwork", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/antecedent/patchwork.git", + "reference": "8b6b235f405af175259c8f56aea5fc23ab9f03ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/8b6b235f405af175259c8f56aea5fc23ab9f03ce", + "reference": "8b6b235f405af175259c8f56aea5fc23ab9f03ce", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignas Rudaitis", + "email": "ignas.rudaitis@gmail.com" + } + ], + "description": "Method redefinition (monkey-patching) functionality for PHP.", + "homepage": "https://antecedent.github.io/patchwork/", + "keywords": [ + "aop", + "aspect", + "interception", + "monkeypatching", + "redefinition", + "runkit", + "testing" + ], + "support": { + "issues": "https://github.com/antecedent/patchwork/issues", + "source": "https://github.com/antecedent/patchwork/tree/2.2.3" + }, + "time": "2025-09-17T09:00:56+00:00" + }, + { + "name": "brain/monkey", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/Brain-WP/BrainMonkey.git", + "reference": "ea3aeb3d559ba3c0930b3f4d210b665a4c044d83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Brain-WP/BrainMonkey/zipball/ea3aeb3d559ba3c0930b3f4d210b665a4c044d83", + "reference": "ea3aeb3d559ba3c0930b3f4d210b665a4c044d83", + "shasum": "" + }, + "require": { + "antecedent/patchwork": "^2.1.17", + "mockery/mockery": "~1.3.6 || ~1.4.4 || ~1.5.1 || ^1.6.10", + "php": ">=5.6.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", + "phpcompatibility/php-compatibility": "^9.3.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.49 || ^9.6.30" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev", + "dev-version/1": "1.x-dev" + } + }, + "autoload": { + "files": [ + "inc/api.php" + ], + "psr-4": { + "Brain\\Monkey\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Giuseppe Mazzapica", + "email": "giuseppe.mazzapica@gmail.com", + "homepage": "https://gmazzap.me", + "role": "Developer" + } + ], + "description": "Mocking utility for PHP functions and WordPress plugin API", + "keywords": [ + "Monkey Patching", + "interception", + "mock", + "mock functions", + "mockery", + "patchwork", + "redefinition", + "runkit", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/Brain-WP/BrainMonkey/issues", + "source": "https://github.com/Brain-WP/BrainMonkey" + }, + "time": "2026-02-05T09:22:14+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "^2.2", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2026-05-06T08:26:05+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b61cd040da1a4925bc90a51c074f5297e7c0fa52", + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v3.0.0" + }, + "time": "2026-03-17T11:56:53+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.15", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/967a801bd188989a5669bd280f252d51c0fdc9ee", + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0 || ^3.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6.36", + "symplify/easy-coding-standard": "^13.2.17" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2026-08-19T19:37:52+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2026-08-11T10:17:44+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.9.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" + }, + "time": "2026-09-13T18:51:52+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-09-19T17:43:28+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.8", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-10-18T00:05:59+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/39467533fdb742446d68c1d10ac33d625ee0311c", + "reference": "39467533fdb742446d68c1d10ac33d625ee0311c", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.2.3", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.2.0", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2026-07-27T11:13:17+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/5f35d9408c54d7b529501f3c688b6eae562aea1f", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.2.0", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "phpcs4", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2026-07-27T10:28:41+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.32", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:23:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.37", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "aceaa623912caf8e80bffea22fc4103a0a01565f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/aceaa623912caf8e80bffea22fc4103a0a01565f", + "reference": "aceaa623912caf8e80bffea22fc4103a0a01565f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0 || ^2", + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.10", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.9", + "sebastian/global-state": "^5.0.8", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.37" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-09-23T09:59:59+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:22:56+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/4352c1a3df741a7ba9e61af6fed51d1fee41cbf7", + "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.9" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2026-08-11T04:55:59+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:10:35+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "c85be6922b7fd365942b986b9a50397d65407611" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c85be6922b7fd365942b986b9a50397d65407611", + "reference": "c85be6922b7fd365942b986b9a50397d65407611", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2026-08-11T05:25:24+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.6", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/4c378e1a528ea066890fc2397cbdd2f94eb2fc91", + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2026-08-06T00:17:32+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.4.1", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "ec2ff942335f33683a5957a85d138753876a05cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/ec2ff942335f33683a5957a85d138753876a05cf", + "reference": "ec2ff942335f33683a5957a85d138753876a05cf", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=7.2", + "phpcsstandards/phpcsextra": "^1.5.1", + "phpcsstandards/phpcsutils": "^1.2.3", + "squizlabs/php_codesniffer": "^3.13.5" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "time": "2026-07-27T11:53:23+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "8.1" + }, + "plugin-api-version": "2.6.0" +} diff --git a/docs/guides/content-types.md b/docs/guides/content-types.md new file mode 100644 index 0000000..fb0e28c --- /dev/null +++ b/docs/guides/content-types.md @@ -0,0 +1,86 @@ +# 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 + Taxonomien, Anzahl und ob Divi dafür aktiv ist. +- `lit-content-types` mit `post_type` liefert zusätzlich alle Felder: Schlüssel, Typ, Pflicht, Auswahlwerte + (`choices`), Beziehungsziel (`related`) und Schreibstatus: + - `writable` – sicher beschreibbar (Pods-Feld oder registriertes Meta-Feld), + - `readonly` – nicht beschreibbar, + - `unknown` – vorhandener Meta-Schlüssel ohne bekannten Typ; Schreiben geht, der Wert wird unverändert + gespeichert (Warnung in der Antwort). + +## Lesen und Suchen + +- `lit-content-query`: Filter `status`, `search`, `taxonomy` (`{taxonomy, terms: [IDs oder Slugs]}`), + `field` (`{key, value, compare}`), `date_after`/`date_before` (JJJJ-MM-TT), `parent`; Blättern mit + `next_cursor` → `cursor`. `field` und `taxonomy` sind auf Feld- bzw. Taxonomienamen beschränkt, die + `lit-content-types` für diesen Inhaltstyp auch anzeigt – ein unbekannter Name wird abgelehnt, keine leere + Trefferliste. Geliefert werden nur Einträge, die der verbundene Nutzer selbst bearbeiten darf (fremde + Entwürfe/private Inhalte ohne die passende Berechtigung erscheinen nicht). +- `lit-content-get`: alle Daten eines Eintrags inkl. Feldern, Begriffen, Beitragsbild und `rev`. + Bei Divi-Seiten fehlt `content`; den Aufbau liest `lit-divi-outline`, geändert wird er mit den + Divi-Werkzeugen (siehe `divi-structure`); Titel, Auszug, Felder und Begriffe gehen weiter über + `lit-content-update`. + +## Anlegen und Ändern + +- `lit-content-create` legt immer einen **Entwurf** an. Felder werden gegen das Schema geprüft; ein Fehler + bricht den ganzen Aufruf ab (`fields.` im Fehler nennt die Ursache). Divi-Seiten gleich mit + Inhalt: `divi` = Liste von Abschnitten in Kurzform (siehe `divi-shortform`) statt `content`; die Antwort + enthält dann die `tree_rev` für weitere Divi-Änderungen. +- `lit-content-update` ändert nur die übergebenen Werte, der Status bleibt. `rev` aus `lit-content-get` ist + **Pflicht** – hat jemand parallel gespeichert (Inhalt, Titel, Auszug, Status oder irgendetwas anderes am + Eintrag), kommt `rev_mismatch`: neu lesen, Änderung auf dem aktuellen Stand wiederholen. +- 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 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 + Wunsch des Nutzers verwenden. Ein **privater** Eintrag wird durch Planen zum Zeitpunkt öffentlich (die + Antwort warnt davor). +- `lit/post-trash` legt in den Papierkorb (nie endgültig) und braucht `confirm: true` nach Rückfrage. Ist der + Eintrag Startseite, Beitragsseite oder in Menüs verlinkt, nennen Rückfrage und `warnings` das – dem Nutzer + weitergeben. + +## Sicher überarbeiten + +1. `lit/post-duplicate` → Entwurfskopie; die Antwort enthält die `rev` der Kopie für `lit-content-update`. +2. Kopie mit `lit-content-update` bzw. den Divi-Werkzeugen bearbeiten und dem Nutzer die Vorschau zeigen; + `lit-page-render-summary` fasst den gerenderten Inhalt zusammen. +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` + aufrufen. Vorher entsteht eine Revision **und** eine vollständige Sicherung des bisherigen Originals im + 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 `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 + +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. diff --git a/docs/guides/divi-attrs.md b/docs/guides/divi-attrs.md new file mode 100644 index 0000000..f07f680 --- /dev/null +++ b/docs/guides/divi-attrs.md @@ -0,0 +1,64 @@ +# Divi-Einstellungen (Vollform) + +Jedes Divi-5-Modul speichert seine Einstellungen unter **Attributpfaden**, z. B. + +- `title.innerContent` – Text der Überschrift, +- `title.decoration.font.font` – Schrift der Überschrift (Farbe, Größe, Ebene `headingLevel`), +- `button.innerContent` – Beschriftung und Link eines Buttons (`text`, `linkUrl`, `linkTarget`), +- `module.decoration.background` – Hintergrund, `module.decoration.spacing` – Innen-/Außenabstand, +- `module.meta.adminLabel` – Admin-Label im Divi-Builder. + +Der Wert eines Pfads ist eine Zuordnung **Breakpoint → Zustand → Wert**: + +```json +{ "desktop": { "value": { "color": "#1a1a1a", "headingLevel": "h2" }, "hover": { "color": "#000000" } }, + "phone": { "value": { "size": "28px" } } } +``` + +Breakpoints: `desktop`, `tablet`, `phone` (je nach Website auch `tabletWide`, `phoneWide`, `widescreen`, +`ultraWide`); Zustände: `value`, `hover`, `sticky`. + +## Setzen + +In `attrs` (bei `lit-divi-insert` je Knoten, bei `lit-divi-update` je Änderung) steht Pfad → Wert je Breakpoint. +Kurz: `{"desktop": "#1a1a1a", "phone": "…"}` bzw. bei Objektwerten `{"desktop": {"size": "28px"}}`; Hover und +Sticky: `{"hover": {…}}` bzw. `{"desktop": {"value": {…}, "hover": {…}}}`. Nur die genannten Breakpoints +werden ersetzt, der Rest bleibt; `null` entfernt einen Pfad, Breakpoint oder Zustand: + +```json +{ "id": "s0.r0.c1.m0", + "attrs": { "title.decoration.font.font": { "desktop": { "color": "{{color:heading}}" }, + "phone": { "size": "28px" } }, + "module.decoration.spacing": null } } +``` + +Ein Objektwert ersetzt den bisherigen Wert dieses Breakpoints ganz (Unterschlüssel werden nicht +zusammengeführt): z. B. bei `module.decoration.sizing` einer Spalte `flexType` immer mitschicken (siehe +`divi-shortform`). + +Unbekannte Pfade → Fehler `invalid_attr_path` mit Vorschlägen unter `fields`. Texte werden bereinigt +(HTML nur, wo Divi Rich-Text erwartet; Links ohne `javascript:`). `$variable`-Verweise werden geparst, +geprüft und vom Plugin neu erzeugt (siehe `divi-placeholders`). Eigenes HTML, CSS und HTML-Attribute +(`…decoration.attributes`, `…advanced.html`, `…css…`) nur mit dem Recht `unfiltered_html`, sonst „nur im +Divi-Builder“. Unberührte Teile der Seite bleiben byte-identisch; Ausnahme: Divi selbst normalisiert Seiten +mit eigenen HTML-/Attribut-Feldern beim Speichern – nur wenn die Seite noch nicht in Divis eigener gespeicherter Form vorliegt (z. B. importierter oder direkt in die Datenbank geschriebener Inhalt); über den Divi-Builder oder LIT MCP gespeicherte Seiten betrifft das praktisch nie (Warnung in der Antwort, neue `tree_rev` gilt). + +## Pfade finden + +- `lit-divi-catalog` ohne `module` listet alle Module dieser Website; mit `module` (z. B. `"heading"`) alle + gültigen Pfade dieses Moduls auf dieser Website (live aus Divi) und die Kurzform-Felder mit ihrem Pfad. + `path_kinds` nennt die Feldart eines Pfads (`text`, `richtext`, `code`, `url`, `image`, `color`, `enum`, + `onoff`, `number`, `list`, `icon`), soweit aus den Divi-Metadaten ableitbar. Bei einem zusammengesetzten + Pfad (ein oder mehrere benannte Unterfelder unter einem Pfad, z. B. `buttonOne.innerContent` mit `text` und + `linkUrl`) ist der Eintrag stattdessen eine Zuordnung Unterfeld → Feldart (`{"text": "text", "linkUrl": + "url"}`), soweit für die einzelnen Unterfelder ableitbar; fehlende Pfade oder Unterfelder gelten als + unbekannt, statt eine Feldart zu raten. +- Ergebnisform ohne `module`: `{"modules": [{"name": "divi/heading", "title": "Heading", "category": "module", + "children": [], "shortform": true}, …], "notes": […]}`; mit `module`: `{"module": {"name": "divi/heading", + "title": …, "children": […], "item_type": null, "shortform": {"text": {"path": "title.innerContent", …}}, + "paths": […], "path_kinds": {…}}, "notes": […]}`. +- `lit-divi-node-get` zeigt die gesetzten Werte eines vorhandenen Knotens in genau dieser Form (`attrs`). +- Schrift-Gruppen liegen eine Ebene tiefer: `…decoration.font.font`, `…decoration.bodyFont.body.font`, + `…decoration.headingFont.h2.font`, `module.advanced.text.text`. +- Werte wie `$variable({…})$` sind Verweise auf globale Farben, Variablen oder dynamische Inhalte – nicht + durch feste Werte ersetzen (siehe `divi-design-system`). diff --git a/docs/guides/divi-design-system.md b/docs/guides/divi-design-system.md new file mode 100644 index 0000000..ab092c9 --- /dev/null +++ b/docs/guides/divi-design-system.md @@ -0,0 +1,20 @@ +# Divi-Designsystem + +`lit-divi-design-system` liest das Designsystem der Website live aus Divi: + +- **Globale Farben** (`colors`): ID (`gcid-…`), Name, Wert und der Platzhalter für neue Inhalte, z. B. + `{{color:primary}}` für die Primärfarbe oder `{{color:Nachtblau}}` für eine eigene globale Farbe. Die fünf + Theme-Farben heißen `primary`, `secondary`, `heading`, `body` und `link`. +- **Globale Variablen** (`variables`): Zahlen, Texte, Bilder, Links (`gvid-…`), Platzhalter `{{var:}}`. +- **Theme-Schriften** (`fonts`): Überschrift und Fließtext, Platzhalter `{{font:heading}}` / `{{font:body}}`. +- **Größen** (`sizes`): Schriftgrößen, Inhaltsbreite und Spaltenabstand aus den Theme-Optionen. +- **Presets** (`presets`): Modul-Presets je Modul (`kind: module`, z. B. alle Button-Presets) und + Gruppen-Presets (`kind: group`); `default: true` markiert das Standard-Preset eines Moduls. + +Regeln: + +1. Vor dem Gestalten einer Seite das Designsystem lesen. +2. Globale Farben, Variablen und Presets vor festen Werten verwenden; feste Farben oder Schriften nur, wenn + der Nutzer es ausdrücklich möchte. +3. Gibt es für einen Zweck noch keine Farbe, Variable oder kein Preset, dem Nutzer vorschlagen, es im + Divi-Builder anzulegen, statt feste Werte zu verteilen. diff --git a/docs/guides/divi-placeholders.md b/docs/guides/divi-placeholders.md new file mode 100644 index 0000000..571e0e9 --- /dev/null +++ b/docs/guides/divi-placeholders.md @@ -0,0 +1,47 @@ +# Platzhalter für Divi-Inhalte + +Platzhalter stehen **allein** im Wert (nicht mitten im Text) und werden beim Speichern in Divis eigene +Verweise übersetzt. So bleibt die Seite mit dem Designsystem verbunden: ändert sich die Primärfarbe, ändert +sich auch der Button. + +| Platzhalter | Bedeutung | +|---|---| +| `{{color:primary}}`, `secondary`, `heading`, `body`, `link` | die fünf Theme-Farben | +| `{{color:}}` | eigene globale Farbe (Name wie in `lit-divi-design-system`) | +| `{{var:}}` | globale Variable (Zahl, Text, Bild, Link) | +| `{{font:heading}}`, `{{font:body}}` | Theme-Schrift für Überschriften bzw. Fließtext | +| `{{post:title}}`, `excerpt`, `date`, `modified`, `author`, `url`, `featured_image`, `categories`, `tags` | dynamischer Inhalt des angezeigten Beitrags | +| `{{field:}}` | dynamischer Inhalt aus einem Feld (Pods/Meta), z. B. `{{field:telefon}}` | +| `"preset": ""` | Modul-Preset (kein Platzhalter, sondern eigenes Feld) | + +Beispiele: + +```json +{ "type": "button", "text": "Anrufen", "url": "{{field:telefon_link}}", + "attrs": { "button.decoration.background": { "desktop": { "color": "{{color:primary}}" }, + "hover": { "color": "{{color:Nachtblau}}" } } } } +``` + +```json +{ "type": "heading", "text": "{{post:title}}", "level": "h1" } +``` + +Unbekannte Namen lehnt das Werkzeug ab und nennt die vorhandenen; ist ein Name doppelt vergeben, statt des +Namens die ID aus `lit-divi-design-system` angeben (z. B. `{{color:gcid-…}}`). Felder, deren Schlüssel mit `_` +beginnt (geschützte Meta-Daten), sind nicht erlaubt. + +In Link-Feldern (`url`, `button_url` …) nimmt das Werkzeug jeden dynamischen Inhalt an, auch `{{post:title}}`; +sinnvoll sind dort nur Werte, die eine Adresse ergeben (`{{post:url}}`, ein Feld mit einer URL, eine +Link-Variable). + +## `$variable`-Werte + +In `lit-divi-node-get` erscheinen die übersetzten Verweise als `$variable({…})$`. Diese Werte unverändert +lassen oder wieder durch einen Platzhalter ersetzen. `$variable`-Verweise werden geparst, geprüft und vom +Plugin neu erzeugt: erlaubt sind nur ganze Werte mit bekannten Farben, Variablen, Schriften und +dynamischen Quellen; Zusatztexte davor/danach (`before`/`after`) werden bereinigt; alles andere (z. B. ein +Verweis mitten im Text, eigenes HTML-Ausgeben von Feldern) lehnt das Werkzeug mit `validation_failed` ab. + +Unberührte Teile der Seite bleiben dabei byte-identisch. Ausnahme: Divi selbst normalisiert Seiten mit +eigenen HTML-/Attribut-Feldern beim Speichern – nur wenn die Seite noch nicht in Divis eigener gespeicherter Form vorliegt (z. B. importierter oder direkt in die Datenbank geschriebener Inhalt); über den Divi-Builder oder LIT MCP gespeicherte Seiten betrifft das praktisch nie; die Antwort warnt dann, dass der Inhalt beim Speichern +verändert wurde, und die neue `tree_rev` gilt. diff --git a/docs/guides/divi-shortform.md b/docs/guides/divi-shortform.md new file mode 100644 index 0000000..1c9fb1b --- /dev/null +++ b/docs/guides/divi-shortform.md @@ -0,0 +1,119 @@ +# Divi-Kurzform + +Neue Divi-Inhalte beschreibt man als JSON-Knoten. `lit-divi-insert` (in `nodes`) und `lit-content-create` (in +`divi`) nehmen dieselbe Form. Jeder Knoten hat einen `type`; dazu Kurzform-Felder, optional `label` +(Admin-Label), `preset` (Name eines Modul-Presets aus `lit-divi-design-system`) und `attrs` (Vollform, siehe +`divi-attrs`). Kurz- und Vollform lassen sich kombinieren, aber nicht für denselben Pfad (dann Fehler +`validation_failed` mit dem Pfad). + +## Aufbau + +```json +{ "type": "section", "label": "Hero", "background_color": "{{color:primary}}", + "rows": [ + { "columns": "1_3,2_3", + "modules": [ + [ { "type": "image", "image_id": 33 } ], + [ { "type": "heading", "text": "Willkommen", "level": "h1" }, + { "type": "text", "content": "

Seit 1987 in Köln.

" }, + { "type": "button", "text": "Termin buchen", "url": "/kontakt", "preset": "CTA primär" } ] + ] } + ] } +``` + +- **Abschnitt** (`section`): `rows` (Zeilen) oder – für eine einspaltige Zeile – direkt `modules`; + `section_type: "fullwidth"` für Vollbreite-Abschnitte (dann nur `fullwidth-…`-Module in `modules`); + `background_color`. Spezialabschnitte (specialty) nur im Divi-Builder bauen. +- **Zeile** (`row`): `columns` = Breiten als Brüche, die zusammen 1 ergeben (`"4_4"`, `"1_2,1_2"`, + `"1_3,2_3"`, `"1_4,1_4,1_2"`, auch `1_5`, `2_5`, `3_5` wie im Divi-Builder – `4_5` nicht); `modules` = je + Spalte eine Liste (bei einer Spalte reicht eine einfache Liste). +- **Module** mit Kind-Modulen (Akkordeon, Tabs, Slider, Kontaktformular, Karte, Social-Media-Follow, + Icon-Liste) bekommen ihre Einträge in `items`; der Eintragstyp ergibt sich von selbst. +- `label` fehlt → Admin-Label „ – “, z. B. „Hero – Button“; Zeilen „Hero – Zeile“, eine Zeile + in einer Spalte „Hero – Zeile (innen)“. Abschnitten deshalb ein Label geben. +- `heading` ohne `level` wird `h2` (eine Seite hat meist schon eine `h1` im Titel). Auch Module mit Titel + (`blurb`, `cta`, `toggle`, Akkordeon-Eintrag, Slide, `contact-form`, `number-counter`, `team-member`) nehmen + `level` (`h1`–`h6`); ohne `level` gilt Divis Standard (beim Kontaktformular `h1` – daher meist `h2` setzen). + +## Kurzform-Felder + +| Modul | Felder | +|---|---| +| `text` | `content` (HTML) | +| `heading` | `text`, `level` (`h1`–`h6`) | +| `button` | `text`, `url`, `new_window` (true/false) | +| `image` | `image_id` (Mediathek, setzt URL/ID/Alt), `src`, `alt`, `title`, `url` | +| `blurb` | `title`, `url`, `content`, `image_id`, `src`, `alt`, `level` | +| `cta` | `title`, `content`, `button_text`, `button_url`, `level` | +| `icon` | `url`, `title` | +| `divider` | `show`, `color` | +| `testimonial` | `author`, `job_title`, `company`, `company_url`, `content`, `portrait_id`, `portrait_url` | +| `team-member` | `name`, `position`, `content`, `image_id`, `image_url`, `level` (des Namens) | +| `accordion` → `accordion-item` | Eintrag: `title`, `content`, `open`, `level` | +| `tabs` → `tab` | Eintrag: `title`, `content` | +| `toggle` | `title`, `content`, `open`, `level` | +| `gallery` | `image_ids` (Liste von Anhang-IDs) | +| `slider` → `slide` | Eintrag: `title`, `content`, `button_text`, `button_url`, `image_id`, `src`, `alt`, `level` | +| `contact-form` → `contact-field` | Formular: `title`, `level`, `email`, `success_message`, `submit_text`; Feld: `title`, `id`, `type` (`input`, `email`, `text`, `checkbox`, `radio`, `select`), `required` | +| `map` → `map-pin` | Karte: `address`, `lat`, `lng`, `zoom`; Pin: `title`, `content`, `address`, `lat`, `lng` | +| `video` | `src`, `thumbnail` | +| `number-counter` | `number`, `title`, `percent`, `level` | +| `social-media-follow` → `social-media-follow-network` | Eintrag: `network` (z. B. `facebook`), `url`, `label` | +| `icon-list` → `icon-list-item` | Eintrag: `text` | +| `code` | `content` – nur, wenn kein natives Modul passt (Warnung) | + +Alle anderen Module (siehe `lit-divi-catalog`) gehen über `attrs`. Die Kurzform-Felder eines Moduls samt Pfad +zeigt `lit-divi-catalog` mit `module`. + +Zwei Felder heißen wie Knoten-Schlüssel und lassen sich deshalb beim Einfügen **nicht** in Kurzform setzen: +`type` des Kontaktformular-Felds (`type` ist dort der Modultyp) und `label` des Social-Media-Netzwerks (`label` +ist dort das Admin-Label). Beim Einfügen über `attrs` (`fieldItem.advanced.type` bzw. +`socialNetwork.innerContent` mit `label`) setzen, danach auch mit `lit-divi-update` über `set` +(`{"set": {"type": "email"}}`). + +## Einfügen, ändern, verschieben, löschen + +- `lit-divi-insert`: `nodes` + Position – `parent` (Pfad-ID, z. B. Spalte `s0.r0.c1`; optional `index`), + `before` oder `after`; ohne Position ans Seitenende. Oben auf der Seite sind nur Abschnitte erlaubt, in + Abschnitten Zeilen, in Spalten Module. +- `lit-divi-update`: `changes` = `[{"id": "s0.r0.c1.m2", "set": {"text": "Jetzt buchen"}, "preset": "CTA + primär", "label": "Hero – Button"}]` – mehrere Knoten in einem Aufruf; `null` entfernt einen Wert. Denselben + Pfad nicht zugleich über `set` und `attrs` ändern (dann zwei Änderungen senden). +- `lit-divi-move`: `id` + Position (auch in andere Zeilen oder Abschnitte). +- `lit-divi-delete`: `ids`, erst nach Rückfrage mit `confirm: true`. +- `lit/divi-duplicate-node`: Kopie direkt hinter dem Original. +- Jede Antwort enthält die neue `tree_rev` (für die nächste Änderung), `changed` mit den neuen Pfad-IDs, + den betroffenen Abschnitt als `outline`, `warnings` (Qualität, siehe unten) und `revision_id`. + +Fehler: `validation_failed` bzw. `invalid_attr_path` mit `fields` (Eingabepfad → Problem, z. B. +`nodes[0].rows[0].columns`); dann wurde nichts gespeichert. Eine unbekannte Pfad-ID meldet `lit-divi-update` +mit den übrigen Eingabefehlern gesammelt als `validation_failed` (`fields.changes[i].id`), `lit-divi-move`, +`lit-divi-delete` und `lit/divi-duplicate-node` als `not_found` – in beiden Fällen mit `lit-divi-outline` +neu lesen. + +## Spaltenbreiten ändern + +Die Breite einer Spalte steht in `module.decoration.sizing` (Desktop: `flexType`, z. B. `8_24` = 1/3). Wer +diesen Desktop-Wert über `attrs` ersetzt, ersetzt das ganze Objekt: `flexType` immer mitschicken, sonst fällt +die Breite weg. Erlaubt sind je Breakpoint (Desktop, Tablet, Telefon, …) nur die Spaltenklassen des +Divi-Builders: `1_24` … `24_24`, `1_5`, `2_5`, `3_5`, `1_7`, `1_9`, `1_10`, `1_11` oder `none`. In einer Zeile +mit festen Breiten alle Spalten zusammen ändern (Summe 1). Die Angabe +`flexColumnStructure` der Zeile (Spaltenvorlage im Divi-Builder) passt das Werkzeug nicht an; nach einer +Breitenänderung zeigt der Builder dort evtl. noch die alte Vorlage; gespeichert sind die neuen Breiten (bei +Bedarf die Vorlage im Builder neu wählen). + +## Sicherheit und Speichern + +Alle Werte werden vor dem Speichern bereinigt: Texte ohne HTML, Rich-Text ohne `unfiltered_html` nur mit dem +HTML, das WordPress in Beiträgen erlaubt, Links ohne `javascript:`. +`$variable`-Verweise werden geparst, geprüft und vom Plugin neu erzeugt. Eigenes HTML, CSS und HTML-Attribute eines Moduls (`…decoration.attributes`, `…advanced.html`, +`…css…`) ändert nur, wer das Recht `unfiltered_html` hat – sonst „nur im Divi-Builder“. Unberührte Teile der +Seite bleiben byte-identisch; Ausnahme: Divi selbst normalisiert Seiten mit eigenen HTML-/Attribut-Feldern +beim Speichern – nur wenn die Seite noch nicht in Divis eigener gespeicherter Form vorliegt (z. B. importierter oder direkt in die Datenbank geschriebener Inhalt); über den Divi-Builder oder LIT MCP gespeicherte Seiten betrifft das praktisch nie (dann Warnung, dass der Inhalt beim Speichern verändert wurde – mit der neuen `tree_rev` +weiterarbeiten). + +## Warnungen + +Neue oder geänderte Knoten werden geprüft: feste Farben/Schriften, obwohl eine passende globale existiert; +Bilder ohne Alt-Text; leere Module; Sprünge in der Überschriftenhierarchie; Code-Modul. Warnungen dem Nutzer +nennen und möglichst gleich beheben. diff --git a/docs/guides/divi-structure.md b/docs/guides/divi-structure.md new file mode 100644 index 0000000..53e42d8 --- /dev/null +++ b/docs/guides/divi-structure.md @@ -0,0 +1,52 @@ +# Divi-Seiten: Aufbau lesen + +Divi 5 speichert eine Seite als Block-Markup: Abschnitte (`section`) enthalten Zeilen (`row`), Zeilen +Spalten (`column`), Spalten Module (`text`, `heading`, `button`, …). Manche Module haben Kind-Module +(`accordion` → `accordion-item`). Die Divi-Werkzeuge gibt es nur, wenn der Divi-5-Builder aktiv ist. + +## Seitenbaum + +- `lit-divi-outline` mit `post_id` liefert jeden Knoten mit **Pfad-ID**, Modultyp, Admin-Label, Textauszug, + Layout-Info (z. B. `Spalten 8_24+16_24`) und Preset-Namen – als kompakten Text (`outline`) und als Liste + (`nodes`). Mit `id` nur ein Teilbaum, mit `depth` begrenzte Tiefe. +- Pfad-IDs: `s0` = erster Abschnitt, `s0.r1` = zweite Zeile darin, `s0.r1.c0` = erste Spalte, + `s0.r1.c0.m2` = drittes Modul, `…m2.m0` = erstes Kind-Modul. `g` = globales Modul, `x` = fremder Block + (z. B. aus dem Block-Editor). Gezählt wird je Buchstabe innerhalb des Elternknotens. +- `lit-divi-node-get` mit `post_id` und `id` liefert alle Einstellungen eines Knotens (siehe `divi-attrs`), + die aktuellen Kurzform-Werte (`shortform`) und die IDs der Kinder. + +## Stand und Sperre + +- `tree_rev` ist der Stand des Seiteninhalts. Pfad-IDs gelten nur für diesen Stand – ändert jemand die Seite + (auch im Divi-Builder), verschieben sich die IDs. Divi-Änderungen verlangen deshalb immer die `tree_rev` + des letzten Lesevorgangs. +- `rev` ist davon getrennt: der Stand des ganzen Eintrags (Titel, Auszug, Status …) für `lit-content-update`. + +## Ändern + +Neue Inhalte und Änderungen: siehe `divi-shortform` (Kurzform, `lit-divi-insert`, `lit-divi-update`, +`lit-divi-move`, `lit-divi-delete`), `divi-attrs` (Vollform) und `divi-placeholders`. Ablauf: + +1. `lit-divi-outline` lesen (liefert `tree_rev`), `lit-divi-design-system` für Farben, Variablen, Presets. +2. Änderung mit dieser `tree_rev` senden. Die Antwort enthält die neue `tree_rev` und die neuen Pfad-IDs – + damit direkt weiterarbeiten, ohne erneut zu lesen. +3. `rev_mismatch`: jemand hat die Seite inzwischen geändert (z. B. im Divi-Builder) → neu lesen. +4. Zum Schluss `lit-page-render-summary` prüfen und dem Nutzer die Vorschau (`preview_url`) bzw. den + Divi-Builder (`edit_url`) nennen. Der Status bleibt unverändert; veröffentlichte Seiten werden live + geändert (vorher entsteht eine Revision, `revision_id`). + +## Was nicht bearbeitet wird + +- Knoten mit „nur lesbar“ (globale Module `g…`, fremde Blöcke `x…`) lassen sich nur als Ganzes verschieben, + duplizieren oder löschen; ihr Inhalt wird im Divi-Builder bearbeitet (globale Module folgen später). + `lit/divi-replace-text` ändert sie nicht, sondern nennt sie in `skipped_nodes`. +- Divi-4-Inhalt (Shortcodes `[et_pb_…]`) wird nicht gelesen oder geändert: Seite einmal im Divi-Builder + öffnen und speichern, Divi 5 wandelt sie dabei um. +- Ist das Markup beschädigt, lehnen die Werkzeuge ab (`unsupported`); die Seite im Divi-Builder öffnen und + speichern. + +## Ergebnis prüfen + +`lit-page-render-summary` rendert den Inhaltsbereich wie im Frontend und listet Überschriften, Texte, Links +und Bilder samt Warnungen (Sprünge in der Überschriftenhierarchie, fehlende Alt-Texte, Links ohne Ziel). +Header, Footer und Theme-Builder-Vorlagen sind nicht enthalten. diff --git a/docs/guides/media.md b/docs/guides/media.md new file mode 100644 index 0000000..3964c32 --- /dev/null +++ b/docs/guides/media.md @@ -0,0 +1,63 @@ +# Medien + +Werkzeugnamen: `lit-…` sind direkt aufrufbare Tools; `lit/…` sind Abilities, die über +`mcp-adapter-execute-ability` (Name in `ability_name`, Eingaben in `parameters`) aufgerufen werden. Welche +Tools direkt sichtbar sind, lässt sich unter Einstellungen → LIT MCP → Tools ändern (siehe `overview`); +`lit-media-preview` und `lit-media-download` lassen sich dort nur auf „direkt“ oder „deaktiviert“ stellen, +nie auf „indirekt“ – ihr Bild- bzw. Dateiergebnis wird nur bei direktem Aufruf zu einem sichtbaren Inhalt. + +## Suchen und ansehen + +- `lit-media-search`: Text (Titel, Dateiname, Beschreibung), `mime` (z. B. `image` oder `application/pdf`), + Zeitraum (`date_after`/`date_before`), `missing_alt: true` (Bilder ohne Alt-Text; `mime` muss dabei leer + oder ein Bild-Typ sein) und `unused: true` (keine gefundene Verwendung). Geliefert werden nur reguläre + Mediathek-Einträge (kein Papierkorb), die der Nutzer bearbeiten darf. `unused` prüft je Aufruf höchstens 20 + Medien; wird die Prüfung dadurch unterbrochen, folgt eine Warnung und mit `next_cursor` geht es weiter – + „keine Verwendung gefunden“ ist dabei keine Garantie (Details je Medium über `lit/media-get`). +- `lit-media-preview`: verkleinertes Bild ansehen (längste Kante `max_size`, Standard 512 px) – z. B. um + passende Alt-Texte zu schreiben. +- `lit/media-get`: alle Metadaten, Bildgrößen und Verwendungsorte (Beitragsbild, Inhalte inkl. Divi, + Bildfelder, Website-Icon, Logo, Header-/Hintergrundbild, Widgets). `certainty`: „sicher“ = per ID + eingebunden, „möglicherweise“ = nur die Datei-URL gefunden (kann zu einer anderen Kopie der Datei gehören). + Einträge, die der Nutzer nicht lesen darf, erscheinen als „(nicht lesbar)“. `usages_complete: false` heißt, + die Inhaltssuche wurde begrenzt – weitere Verwendungen sind möglich; `usages_truncated: true` heißt, es + gibt mehr Verwendungen als gelistet. + +## Hochladen + +- `lit-media-upload-from-url` (direkt): lädt eine öffentlich erreichbare `http(s)`-Datei; interne oder + lokale Adressen werden abgelehnt (auch als Ziel einer Weiterleitung). Scheitert der Abruf selbst, lautet die + Meldung immer „Die Datei konnte nicht geladen werden.“ – dann eine andere öffentliche URL versuchen oder die + Datei per `lit/media-upload` hochladen. +- `lit/media-upload` (indirekt): lädt eine Datei als Base64, höchstens 10 MB – größere Dateien per URL. +- Beide: optional `title`, `alt`, `post_id` (Eintrag, dem das Medium zugeordnet wird – braucht + Bearbeitungsrecht); erlaubte Dateitypen jpg, png, gif, webp, avif, pdf, mp4, mp3; Größenlimit siehe die + Tool-Beschreibung (Standard 5 MB, einstellbar bis 32 MB unter Einstellungen → LIT MCP → Limits). Beide legen bei + jedem Aufruf ein neues Medium an (nicht idempotent). + +## Ändern, herunterladen, löschen + +- `lit/media-update`: Alt-Text, Titel, Bildunterschrift, Beschreibung für bis zu 50 Medien in einem Aufruf – + alles oder nichts: Ist ein Eintrag ungültig (unbekannte ID, keine Berechtigung, Papierkorb, zu lang, + nichts zu ändern, veraltetes `modified`), wird nichts gespeichert und `failures` nennt jeden fehlerhaften + Eintrag. Optional pro Eintrag `modified` (Wert aus `lit/media-get` oder `lit-media-search`) als Schutz vor + parallelen Änderungen mitsenden – `modified` ändert sich bei Titel, Bildunterschrift und Beschreibung, + nicht bei einer reinen Alt-Text-Änderung. Bei Erfolg meldet `results` jeden Eintrag einzeln (mit neuem + `modified`). +- `lit-media-download`: Datei (Original oder eine Bildgröße aus `sizes` in `lit/media-get`) als eingebettete + Ressource, nur bis zum Upload-Limit und höchstens 10 MB – darüber die URL aus `lit/media-get` verwenden. +- `lit/media-delete`: nur nach Rückfrage (`confirm: true`). Wird das Medium verwendet (auch nur + „möglicherweise“), kommt `in_use` mit der Liste der Verwendungen – erst nach ausdrücklicher Zustimmung des + Nutzers mit `force: true` löschen. Medien im Papierkorb werden nicht gelöscht. Ohne aktivierten + Medien-Papierkorb (WordPress-Standard) ist das Löschen endgültig und nicht rückgängig zu machen. Die + Verwendungsprüfung findet NICHT: Term-Meta, Benutzer-Meta, Pods-Tabellen (`pods_*`, `podsrel`), + ACF-Felder (außer als Bildfeld im Schema mit einer ID pro Meta-Eintrag), serialisierte oder + kommagetrennte ID-Listen in Meta-Feldern und Optionen anderer Plugins – im Zweifel den Nutzer fragen, statt + sich auf „keine Verwendung gefunden“ zu verlassen. + +Alt-Texte beschreiben den Bildinhalt knapp und sachlich (keine „Bild von …“-Floskeln). + +## Wenn ein Schreibvorgang fehlschlägt + +Wie bei Inhalten (siehe `overview`): `code` = `write_failed` (alles wurde zurückgenommen) oder +`partial_write` (die Rücknahme war unvollständig, `hint` folgen statt zu wiederholen). diff --git a/docs/guides/overview.md b/docs/guides/overview.md new file mode 100644 index 0000000..28791d1 --- /dev/null +++ b/docs/guides/overview.md @@ -0,0 +1,54 @@ +# LIT MCP – Übersicht + +Dieser Server pflegt eine WordPress-Website. Alle Änderungen laufen mit den Rechten +des verbundenen WordPress-Benutzers. + +## Grundregeln + +1. **Divi-nativ:** Inhalte werden als echte Divi-5-Module erzeugt, damit sie im Visual + Builder weiter bearbeitet werden können. Das Code-Modul nur verwenden, wenn kein + natives Modul passt. +2. **Designsystem nutzen:** Globale Farben, Variablen und Presets der Website vor festen + Werten verwenden. +3. **Status respektieren:** Entwürfe bleiben Entwürfe, veröffentlichte Inhalte bleiben + veröffentlicht. Veröffentlichen nur auf ausdrücklichen Wunsch. +4. **Lesen vor Schreiben:** Vor Änderungen den aktuellen Stand lesen; Schreib-Tools + verlangen den `rev` des letzten Lesevorgangs. + +## Werkzeuge + +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`, `lit-media-search`, +`lit-media-preview`, `lit-media-download`, `lit-media-upload-from-url`, `lit-page-render-summary`, +`lit-divi-outline`, `lit-divi-node-get`, `lit-divi-catalog`, `lit-divi-design-system`, `lit-divi-insert`, +`lit-divi-update`, `lit-divi-move`, `lit-divi-delete`. + +Die Tools sind nach Integration gruppiert (Grundlagen, WordPress, Medien, Divi). Jede Gruppe hat unter +Einstellungen → LIT MCP → Tools eine eigene Einstellung (direkt/indirekt/deaktiviert), die ihre Tools übernehmen +(„wie Gruppe“). Eine Einstellung am einzelnen Tool geht vor; die Gruppe zeigt dann „gemischt“. „nur lesen“ +schaltet alle schreibenden Tools der Gruppe ab – auch solche mit eigener Einstellung. `lit-guide` bleibt immer +direkt; `lit-media-preview`/`lit-media-download` bleiben bei Gruppe „indirekt“ direkt. Ab mehr als 25 direkten +Tools warnt die Seite (Kontextkosten in claude.ai). Änderungen gelten ab der nächsten Verbindung. + +Ü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`), +Einstellungen (`lit/wp-settings-get`, `lit/wp-settings-update`), Medien (`lit/media-get`, `lit/media-upload`, +`lit/media-update`, `lit/media-delete`), Divi (`lit/divi-duplicate-node`, `lit/divi-replace-text`). + +Die `lit-divi-…`-Werkzeuge gibt es nur, wenn der Divi-5-Builder aktiv ist. + +Weitere Themen: `lit-guide` mit `topic` = `content-types`, `workflows`, `media`, `divi-structure`, +`divi-shortform`, `divi-attrs`, `divi-placeholders` oder `divi-design-system`. + +## Rückmeldungen + +- Erfolg: `ok`, `summary`, `changed`, `warnings`, `rev`, `revision_id` (Revision mit dem Stand vorher, nur bei + Tools, die Inhalt überschreiben, sonst `null`), `log_id`, URLs. Divi-Werkzeuge zusätzlich `tree_rev` (Stand + des Seiteninhalts, Pflicht für die nächste Divi-Änderung) und `outline` (betroffener Abschnitt); + `lit/divi-replace-text` nennt `tree_rev` je Seite in `results` (Probelauf: in `matches`). +- 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. diff --git a/docs/guides/workflows.md b/docs/guides/workflows.md new file mode 100644 index 0000000..7ae6496 --- /dev/null +++ b/docs/guides/workflows.md @@ -0,0 +1,62 @@ +# Abläufe + +## 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, + 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": , "show_on_front": "page"}}`. +5. Menü: `lit/menu-create` (optional mit `location` aus `lit/menu-list`), Einträge mit `lit/menu-set-items` – mit + `menu_id` und `menu_rev` aus der Antwort von `lit/menu-create` (kein `lit/menu-get` nötig). +6. Veröffentlichen erst, wenn der Nutzer es ausdrücklich möchte. + +## Divi-Seite bauen + +1. `lit-divi-design-system` lesen (Farben, Schriften, Presets) und `lit-divi-catalog` für die Module. +2. Seite als Entwurf mit Inhalt anlegen: `lit-content-create` mit `divi` (Abschnitte in Kurzform, siehe + `divi-shortform`) – oder eine leere Seite anlegen und Abschnitte mit `lit-divi-insert` einfügen. +3. Weitere Abschnitte/Module mit `lit-divi-insert`, Anpassungen mit `lit-divi-update`; immer die `tree_rev` + der letzten Antwort mitgeben. +4. `lit-page-render-summary` prüfen, `warnings` beheben, dem Nutzer `preview_url` und `edit_url` (Divi-Builder) + nennen. Veröffentlichen nur auf Wunsch (`lit-post-publish`). + +## Text auf vielen Seiten ersetzen + +`lit/divi-replace-text` zuerst mit `dry_run: true` (Standard): zeigt Treffer je Seite mit `tree_rev`. Nach +Rückfrage mit `dry_run: false`, `targets` (post_id + tree_rev aus dem Probelauf) und `confirm: true` +ausführen; jede Seite bekommt eine eigene Revision, `results` meldet das Ergebnis je Seite. Ersetzt wird nur +sichtbarer Text (keine Links, Stile, Code oder dynamischen Inhalte); in reinen Textfeldern (Titel, +Button-Texte) werden Leerzeichen am Anfang und Ende der Ersetzung entfernt. Globale Module werden nicht +geändert, sondern in `skipped_nodes` genannt – dem Nutzer sagen, dass er sie in der Divi-Bibliothek ändern muss. + +## Menüs pflegen + +- `lit/menu-get` liefert den Baum und `menu_rev` (Stand des Menüs; für ein neues Menü schon `lit/menu-create`). + `lit/menu-set-items` verlangt diese `menu_rev`; hat sich das Menü inzwischen geändert, kommt `rev_mismatch` – dann mit `lit/menu-get` neu lesen. + Die Antwort enthält die neue `menu_rev` für weitere Änderungen. +- 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 + an seine Stelle). +- Neue Einträge ohne `id`: `type` `post`/`taxonomy` mit `object_id`, oder `custom` mit `url` (`/pfad`, + `#anker`, `http(s)`, `mailto`, `tel`) und `title`; jeweils optional `children`. Das Ziel eines bestehenden + Eintrags lässt sich nicht ändern – dafür entfernen und neu anlegen. +- Verlinkt werden nur für den Nutzer lesbare, veröffentlichte Inhalte und Begriffe öffentlicher Taxonomien. + Private Inhalte sind immer ausgeschlossen. Entwürfe, ausstehende oder geplante Inhalte lassen sich nur mit + `allow_unpublished: true` verlinken – WordPress zeigt den Menüpunkt sofort an, Besucher bekommen bis zur + Veröffentlichung einen toten Link (die Antwort enthält dazu eine Warnung). +- Fallen dabei Einträge weg, verlangt das Tool `confirm: true`; `previous_items` (Stand vorher) erlaubt das + Zurückbauen – neu angelegte Einträge erhalten dabei eine neue `id`, CSS-Klassen, Linkziel und Beschreibung + gehen verloren. +- `lit/menu-assign-location` mit `menu_id: 0` entfernt ein Menü von seiner Position (es verschwindet dann von + der Website) und braucht dafür `confirm: true` nach Rückfrage; die Antwort nennt das entfernte Menü. + +## Einträge eines eigenen Inhaltstyps (Pods) + +1. `lit-content-types` mit `post_type` → Pflichtfelder, Auswahlwerte, Beziehungsziele. +2. `lit-content-create` mit allen Pflichtfeldern in `fields`. +3. Beziehungen über IDs (vorher mit `lit-content-query` suchen). diff --git a/docs/superpowers/notes/2026-09-26-etappe-1a-rulings.md b/docs/superpowers/notes/2026-09-26-etappe-1a-rulings.md new file mode 100644 index 0000000..246713d --- /dev/null +++ b/docs/superpowers/notes/2026-09-26-etappe-1a-rulings.md @@ -0,0 +1,35 @@ +# Etappe 1a – Entscheidungen (Rulings) und offene Kleinigkeiten + +Aus dem SDD-Ledger übernommen, 2026-09-26. + + - Ruling P1 (B1): T7 PostGuard::postType() return type `: object` (WP_Post_Type) instead of `object|WP_Error` (compile error); errors via exceptions/other method as plan logic allows — cost if wrong: none. + - Ruling P2 (B2): T1 adds `` to phpcs.xml.dist (WordPress.Security rule) — our exceptions are internal and become JSON WP_Error messages, never HTML output — cost if wrong: an exception message echoed into HTML unescaped (we never do this). + - Ruling P3 (B3): T7 FieldValidator::checkScalar and T15 SettingsWhitelist::validate — parenthesize nested ternaries / extract helper so PHPCompatibility passes — cost: none. + - Ruling P4 (R1): T14 MenuWriter must accept the exact tree shape lit/menu-get returns: for object-linked items (page/post/category/term) a `url` field is ignored (not rejected); add a unit test for the round trip menu-get → menu-set-items — cost: none. + - Ruling P5 (R2): spec principle 7 (never clobber parallel edits) binds: lit/content-update REQUIRES `rev` (from lit/content-get) for all content; mismatch → lit_rev_mismatch with hint to re-read. T6 instructions/T9 accordingly — cost if wrong: one extra read per update. + - Ruling P6 (R3): all-or-nothing (spec §8.2): T9 validates everything before writing; if writeExtras() fails after insert (create) → delete the just-created draft (force, it's our own new draft); after update → restore the pre-update revision via RevisionPoint; return the error — cost: none. + - Ruling P7 (R4): T11 check wp_trash_post($copyId) result; if it fails, return ok with a warning "Kopie konnte nicht in den Papierkorb gelegt werden" — cost: none. + - Ruling P8 (C4): T16 moves shared smoke helpers (fail/pass/mcp/cleanup/token) into tests/Integration/lib.sh sourced by both mcp-smoke.sh and wp-basis-smoke.sh — cost: none. + - Task 1: minor (deferred): Errors::make silent fallback on json encode failure w/o log; $details may overwrite reserved keys + - Task 2: minor (deferred): WP_Application_Passwords client-name branch untested; clientName not length-capped (sanitize when logging!); duplicated try/catch block in AbilityRegistrar + - Task 3: minor (deferred): insert format array not asserted in test; object_id/ability not truncated + - Task 4: minor (deferred): sampling ORDER BY ID vs index; array_merge numeric meta keys + - Task 5: minor (deferred): podFor call outside try/catch in write(); get_fields() null tolerance + - Task 6: Ruling P9: lit/content-types must only list/describe post types the user can edit (post type cap->edit_posts) — carried into Task 8 dispatch (small change in ContentTypesAbility) — cost if wrong: none (schema only) + - Task 6: minor (deferred): instructions "Inhaltstypen" line uncapped/untested length + - Task 7: minor (deferred): multi-value scalar fields validated as scalar; TermAssigner::assign trusts resolve(); PostGuard cap fallback to raw cap string + - Ruling P10: content rev must detect parallel edits of title/excerpt/content/status too, not only post_content: add Rev::ofPost(WP_Post) = Rev::of(post_content|post_title|post_excerpt|post_status|post_modified_gmt) used by PostPresenter (content-get) and content-update check/return (T9); Rev::of(string) stays for Divi (Etappe 2) — cost if wrong: occasional extra rev_mismatch → re-read + - Task 9: Ruling P11: rev does not cover field/term-only changes by others — accepted limitation (fields rarely edited in parallel; WP does not bump post_modified on meta change) — cost if wrong: a parallel field edit could be overwritten (revision/log still allow undo) + - Task 9: minor (deferred): assign() midway cleanup failure not reported; WP_Error after rowTouched relies on template pre-validation; Pods snapshot no "absent"; rollback via kses may alter admin markup + - Task 10: minor (deferred): permission()/execute() double load (repo-wide pattern) + - Task 11: Ruling P12: replace-with-copy loses original meta/terms (revision covers only title/content/excerpt) → before the swap, clone the original into a trashed backup post (PostCloner), return backup_id; if the backup can't be created → abort with lit_write_failed; with EMPTY_TRASH_DAYS=0 we already refuse — cost if wrong: one extra trashed post per swap + - Task 11: FINAL-FIX (important-ish, carry to final wave): PostCloner::ownMeta must check $wpdb->last_error and throw — a failed SELECT currently looks like "no meta" and replace would wipe original meta; also add create_posts guard before backup; include backup_id in WriteFailed details + - Task 11: minor (deferred): raw inserts skip meta hooks (intended); meta key with backslash edge case + - Task 14: Ruling P13: custom menu links may be /path, #anchor, http(s), mailto, tel (brief) — my 'http(s) only' was shorthand; javascript:/data: etc. must be rejected — cost if wrong: none + - Task 14: Ruling P14: private targets always refused; draft/pending/future only with allow_unpublished + correct warning; non-public taxonomy terms refused; status gate only for newly linked items (existing ones round-trip) — cost if wrong: Claude must set a flag for draft-based site setup + - Task 14: minor (deferred): description should state status gate applies only to new links; raw status string in message; no depth limit + - Task 16: minor (deferred): content-types guide "Jede Änderung liefert revision_id" also near duplicate (null there) + - Final review 1a: ready with fixes. Ruling P15: fix wave = I1 (PostCloner last_error, create_posts guard, backup_id in WriteFailed), I2 (settings isStored via sanitize_option), I3 (guard against future→publish on writes with missed schedule), I4 (edit_post_meta per key), I5 (guides/hints: codes without lit_ prefix, indirect tools via execute-ability as lit/, revision_id claim), M1 (settings FollowUp/WriteFailed/partial log; menu partial log), M2 (checkRelation read_post), M3 (PostGuard refuses trashed by default), M4 (status tools WriteFailed on throwing hook), M6 (permalink regex), M7 (ChangeLog truncate object_id/ability), Task14 description, consolidation: PostChangeResult helper, Rev::matches, ChangeLog::recordPartial. + - Ruling P16: rev naming — `rev` always means the post-level Rev::ofPost for post tools; Etappe 2 Divi tree lock will use a distinct field name (e.g. `tree_rev`) — spec §5.3 to be amended in Etappe 2 plan — cost if wrong: rename later + - Ruling P17: deferred to 1b: menu rev (M5), lit_mcp_guides filter, M8 warnings (front page trash, menu unassign confirm, private schedule note, divi hint) + - Final: parked (Ruling P18): missedSchedule 60s window & zero GMT date fallback; write_failed wording mentions backup — deferred to 1b minor fixes — cost if wrong: seconds-early publish on edits within last minute before schedule diff --git a/docs/superpowers/notes/2026-09-26-etappe-1b-abnahme.md b/docs/superpowers/notes/2026-09-26-etappe-1b-abnahme.md new file mode 100644 index 0000000..46d058d --- /dev/null +++ b/docs/superpowers/notes/2026-09-26-etappe-1b-abnahme.md @@ -0,0 +1,61 @@ +# Etappe 1b – Manuelle Abnahme (Browser) + +Vorbereitet von Task 13 (Doku/Version/E2E). Automatisiert geprüft ist bereits: alle Unit-Tests +(`composer test`), Lint (`composer lint`) und die drei Integrationsskripte +(`tests/Integration/mcp-smoke.sh`, `tests/Integration/wp-basis-smoke.sh`, +`tests/Integration/media-admin-smoke.sh`) – Details im Report von Task 13 +(`.superpowers/sdd/2026-09-25-etappe-1b-medien-admin/task-13-report.md`). + +Die folgenden Punkte lassen sich nicht automatisiert oder nur mit einem echten claude.ai-Connector +prüfen; das führt Jan-Niclas selbst im Browser durch. Aktueller Stand: Version 0.2.0, Branch `etappe-1`. + +## 1. Einstellungen → LIT MCP: alle Tabs + +- [ ] Aufrufen: WordPress-Adminbereich → Einstellungen → LIT MCP. +- [ ] Alle fünf Tabs öffnen: Verbindung, Zugriff, Tools, Protokoll, Limits. Jeder Tab lädt ohne + PHP-Fehler/-Warnung (WP_DEBUG ist an) und zeigt sinnvolle Inhalte. +- [ ] Im Tab „Zugriff“: Rolle „Redakteur“ freigeben, speichern; ein Redakteur-Konto kann sich danach + verbinden (Access-Capability wirkt sofort, kein Cache). Freigabe wieder entfernen, speichern. +- [ ] Im Tab „Tools“: ein Tool (z. B. `lit/content-query`, Standard „direkt“) auf „indirekt“ stellen, + speichern – der Hinweistext dort weist bereits darauf hin, dass die Serveranweisungen erst ab + der nächsten Verbindung gelten. Danach wieder auf „Standard“ zurückstellen, speichern. + +## 2. claude.ai-Connector verbinden und Widerruf + +- [ ] Mit claude.ai verbinden (Staging-Domain oder `ddev share`), OAuth-Flow durchlaufen. +- [ ] Die neue Verbindung erscheint unter Einstellungen → LIT MCP → Zugriff („Aktive Verbindungen“) + mit Client-Namen und Zeitpunkt. +- [ ] Dort „Widerrufen“ klicken. Der nächste Tool-Aufruf in claude.ai scheitert (401) und claude.ai + verlangt eine neue Anmeldung, bevor Tools wieder nutzbar sind. + +## 3. Medien-Workflow in claude.ai + +- [ ] In claude.ai anfragen: „Suche Bilder ohne Alt-Text, zeig mir eins und schlag einen Alt-Text + vor.“ Erwartet: `lit-media-search` mit `missing_alt: true` liefert Treffer, `lit-media-preview` + zeigt ein sichtbares Bild in der Unterhaltung (nicht nur eine Textbeschreibung). +- [ ] Optional weiterführen: Alt-Text mit `lit/media-update` speichern lassen und in der WordPress- + Mediathek prüfen, dass er übernommen wurde. + +## 4. Werkzeuge → Website-Zustand + +- [ ] WordPress-Adminbereich → Werkzeuge → Website-Zustand → Reiter „Info“ bzw. die Testübersicht. +- [ ] Der Test „LIT MCP Endpunkt“ ist grün, wenn der Server erreichbar und `/.well-known/` durchgereicht + wird – sonst zeigt er verständlich, was fehlt (z. B. nginx-Sperre für Punkt-Pfade). Bei diesem + DDEV-Setup hat `mcp-smoke.sh` bereits eine WARN dazu ausgegeben (`/.well-known/` von der + nginx-Konfiguration blockiert) – das hier im Site-Health-Test bestätigen und für die + Staging-/Produktions-nginx-Konfiguration eine Ausnahme für `/.well-known/` einplanen. + +## 5. Protokoll-Tab + +- [ ] Einstellungen → LIT MCP → Protokoll: Einträge der eben durchgeführten Tests (Task 13 und die + manuellen Schritte hier) sind sichtbar. +- [ ] Filter nach Tool/Ability (z. B. `lit/media-update`) funktioniert und schränkt die Liste ein. +- [ ] Ein Eintrag mit Revisions-Link öffnet den WordPress-Revisionsvergleich (z. B. nach einer + `lit-content-update`- oder `lit/post-replace-with-copy`-Änderung). +- [ ] „Protokoll leeren“ leert die Liste (Bestätigungsdialog beachten) und zeigt danach die + Erfolgsmeldung „Protokoll geleert.“. + +## Ergebnis + +Kurze Rückmeldung an den Ausführenden (Task 13 / Controller): welche Punkte grün sind und was ggf. +als Folgeaufgabe (Bugfix oder Doku-Korrektur) aufgenommen werden muss. diff --git a/docs/superpowers/notes/2026-09-26-etappe-1b-rulings.md b/docs/superpowers/notes/2026-09-26-etappe-1b-rulings.md new file mode 100644 index 0000000..ebcd51f --- /dev/null +++ b/docs/superpowers/notes/2026-09-26-etappe-1b-rulings.md @@ -0,0 +1,34 @@ +# Etappe 1b – Entscheidungen (Rulings) und offene Kleinigkeiten + +Aus dem SDD-Ledger übernommen, 2026-09-26. + + - Q1 (B1): T1 test expects sorted order ['logo','photo'] (code sorts) — fix the test. + - Q2 (B2/B4): every task that adds/changes a direct tool updates docs/guides/overview.md "Direkt sichtbar:" in the same task; T13 keeps the exact heading "Direkt sichtbar:" (ToolNamingTest regex). + - Q3 (B3): indirect tools are always written lit/ in descriptions/hints (ToolNamingTest). + - Q4 (R1/C1/C3): media writes reuse 1a building blocks: ChangeLog via FollowUp (or PostChangeResult-style helper), failures after irreversible steps → WriteFailed (clean → write_failed, file kept but meta failed → partial_write with attachment id), server-side failures → Errors::writeFailed not validation_failed/unsupported; one shared helper for both upload abilities (no duplicated permission/result code). + - Q5 (R2/C4): URL import passes limit_response_size (Limits upload MB) to the download and rejects early on Content-Length; upload MIME list = intersect(filter result, get_allowed_mime_types()); SVG only if WordPress itself allows it for the user. + - Q6 (R3): lit/media-download is a DIRECT tool (embedded resource only works for direct tools); overview updated. + - Q7 (R4/C6): usage finder covers featured image, [gallery ids], Divi gallery_ids, block "ids"/"id", wp-image- classes, custom_logo/site_icon, Divi image attrs, file-name URL matches in content, Pods/ACF meta ids; uncertain → "möglicherweise verwendet" (blocks delete without force); usages list only titles of posts the user can read (else "(nicht lesbar)"). + - Q8 (R5): unused search caps work per call (small candidate batch, max N isUsed checks, then next_cursor) — no multi-hundred full scans per request. + - Q9 (R6): multisite: Access tab lists/revokes only users that belong to the current site; a non-super-admin cannot revoke super admins' connections. + - Q10 (R7): add Task 14 at the end: 1a deferred items (menu rev for menu-set-items, lit_mcp_guides filter per spec §9.2, warnings: trashing front page / menu-linked page, menu location unassign needs confirm, scheduling a private entry makes it public, divi hint wording; missedSchedule +60s window and zero-GMT fallback; write_failed wording mentions kept backup/revision). + - Q11 (R8): server instructions (dynamic) list the currently direct tools; hints use the neutral form "lit/" plus "(direkt als lit-, falls freigeschaltet, sonst über mcp-adapter-execute-ability)"; guides state that direct visibility depends on the settings. + - Q12 (C2): MediaGuard refuses trashed attachments unless opted in; delete of trashed attachment refuses (would be permanent). + - Q13 (C5): log tab links objects by object_type (post types → edit link, attachment → media, nav_menu → menus screen, settings → none). + - Q14 (C7): Site Health test async or transient-cached; (C8) readme wording per 1a (which tools are revertible how); (C9) tempnam in tests; (C10) media-update uses per-item post_modified_gmt as optimistic lock via optional `modified` in input — accepted if cheap, else ruling "no lock for media metadata" documented; (C11) no duplicate notice, refuse removing the current user's own role; (C12) add the client-name app-password test. + - Task 1: Ruling Q7b: usage search also covers widgets, header_image_data, background_image; uncovered (term/user meta, Pods tables, ACF, CSV id lists in meta, other plugins' options) documented and must be repeated in media-delete description (T5) + - Task 3: minor (deferred): failure branches (editor WP_Error, resize/save fail, missing file) untested; preview/download refuse trashed while media-get allows (intentional: trashed media not served) + - Task 4: accepted deviation: Content-Length only checked after capped download (Q5 "early" not feasible without HEAD) + - Task 4: minor (deferred): M1 filter keeps last sideload path (guard $moved === null); IPv4 pin only for cURL transport; DNS rebinding residual (core) + - Task 5: minor (deferred): docblock misplacement MediaUsageFinder constructor; guides must mention media tools (T13) + - Task 6: Ruling Q11b: remaining Q11 parts (hints naming direct-by-default tools written neutrally "lit/ (direkt: lit-, falls freigeschaltet)", guides state that direct visibility is configurable) are carried into Task 10 (settings UI makes overrides possible) — cost if wrong: stale tool names in hints after an admin override + - Task 9: minor (deferred, final wave): nonce bind user+uuid; is_string guard for uuid; JS confirm on revoke; own-role warning only on change; cache_users before loop; heading "Verbindungen" (may include expired sessions) + - Task 10: Ruling Q11c: neutral tool naming applied to error hints only; ability descriptions/schemas keep default names (instructions list the current direct tools, so Claude can map) — cost if wrong: descriptions name a tool form that is not listed after an override + - Task 10: minor (deferred): ExposurePolicy::sanitize keeps unknown-but-wellformed ability names (dead data) + - Task 11: minor (deferred): filters()/extraFilters() split; date shape-only validation; JS-only clear confirm + - Task 12: minor (deferred): Uninstaller::removeSiteData public but internal + - Ruling Q15: Task 14 (deferred 1a items, brief written by controller) runs BEFORE Task 13 so docs/E2E cover it — cost if wrong: none + - Task 14: accepted: Errors::writeFailed ?$state + WriteFailed::$rolledBackState added (existing params couldn't replace the sentence) + - Task 14: minor (deferred, final wave): schedule test asserts 'öffentlich' substring (use 'ÖFFENTLICH'); numeric guide topics from filter dropped; Rev::ofMenu docblock; menu-get→set-items unit round trip; Pods textdomain notices noise check + - Task 13: minor (deferred): readme doesn't mention lit_mcp_upload_mimes filter + - Final review 1b: with fixes. Ruling Q16: fix wave = I1 download hard cap 10 MB + upload bound max 32 MB (tab label), I2 filename search via wp_allow_query_attachment_by_filename, M3 edit_user in manageable(), M4 mcp.public=false for DIRECT_ONLY, M5 wp_slash alt/title on upload, M6 neutral sideload error (log detail), M7 menu-assign-location destructive:true, M8 readme corrections (MEDIA_TRASH constant, site filters, super admins), M9 neutral hint in preview, deferred cheap: MediaUsageFinder docblock, Task 9 is_string uuid guard, Task 14 'ÖFFENTLICH' assertion, Rev::ofMenu docblock. Etappe-2 notes (requiresDirect flag, tree_rev, direct tool count) → Etappe 2 planning. diff --git a/docs/superpowers/notes/2026-09-26-etappe-2-entscheidungen.md b/docs/superpowers/notes/2026-09-26-etappe-2-entscheidungen.md new file mode 100644 index 0000000..0bca87c --- /dev/null +++ b/docs/superpowers/notes/2026-09-26-etappe-2-entscheidungen.md @@ -0,0 +1,20 @@ +# Etappe 2 – Entscheidungen der Planung + +Getroffen bei der Planung von Etappe 2a/2b, 2026-09-26. Kosten, falls falsch, jeweils am Ende. + +- D1 (Ruling P16): `rev` = `Rev::ofPost` (ganzer Eintrag); Divi-Sperre = `tree_rev` = `Rev::of(post_content)`. Divi-Lese-Tools liefern beide, Divi-Schreib-Tools verlangen `tree_rev` und liefern beide neu. – Kosten: keine. +- D2: Direkte Tools nach Etappe 2 = 21 (Spec §4.3 ohne `lit/library-insert`, plus `lit/site-status`, `lit/media-download`). `lit/site-status` bleibt direkt (leeres Schema, Anker von mcp-smoke.sh), `lit/media-download` muss direkt bleiben. Indirekt: `lit/divi-duplicate-node`, `lit/divi-replace-text`. – Kosten: etwas mehr Kontext je Unterhaltung; in Etappe 3 neu bewerten. +- D3: BlockTree mit eigenem Tokenizer (Muster von `WP_Block_Parser`), opake Knoten für globale Module/Layouts und undekodierbare Attribute, unbalanciertes Markup → Ablehnung. – Kosten: globale Module erst ab Etappe 3 bearbeitbar. +- D4: `ExposurePolicy::DIRECT_ONLY` bleibt eine Liste; Etappe 2 bringt keine Bild-/Ressourcen-Tools, ein `Ability::requiresDirect()` wäre YAGNI. – Kosten: bei weiteren Bild-Tools die Liste ergänzen. +- D5: `lit/page-render-summary` gehört zur WordPress-Integration (funktioniert für jeden Eintrag) und rendert den Inhaltsbereich im Prozess (`the_content`), ohne HTTP-Loopback. – Kosten: Header/Footer/Theme Builder fehlen im Auszug. +- D6: `lit/divi-dynamic-sources` und die Warnung „ähnliche Abschnitte“ kommen mit Etappe 3 (Theme Builder, `lit/library-find-similar`). – Kosten: bis dahin keine Quellenliste; `{{field:…}}` nimmt jeden Meta-Schlüssel. +- D7 (Ruling R1): Divi-Speichern ohne kses für Benutzer ohne `unfiltered_html` (sonst re-serialisiert WordPress jeden Block) ist nur vertretbar, weil keine client-gelieferte Zeichenkette ungeprüft in `post_content` gelangt. Klartext-Werte werden je Feldtyp bereinigt (Text: `sanitize_text_field`; Rich-Text: `wp_kses_post`, abhängig von `unfiltered_html`; URLs: `esc_url_raw`, geroutet über die Attribut-Metadaten des Katalogs, ersatzweise über den Schlüsselnamen). Werte mit `$variable(...)$` – ob aus Platzhaltern erzeugt oder unverändert aus `lit/divi-node-get` zurückgegeben – werden geparst, streng geprüft (zulässiger Typ, existierende gcid-/gvid-/Dynamic-Content-Quellennamen, whitelisted Settings-Schlüssel, String-Settings bereinigt) und vom Plugin neu serialisiert; alles andere wird mit `validation_failed` abgelehnt. Code-Modul ungefiltert nur mit `unfiltered_html`. – Kosten: jeder neue Schreibpfad muss bereinigen, streng validieren und neu serialisieren (Review-Pflicht). +- R2 (Ruling): Ob Divi das Dynamic-Content-`before`/`after` bei der Ausgabe selbst escaped, wird in Etappe 2b im Divi-Quellcode verifiziert; die Bereinigung aus D7 gilt unabhängig vom Ergebnis (Verteidigung in der Tiefe). – Kosten: keine (Verifikation, keine Abhängigkeit der Sicherheit vom Ergebnis). +- R2 – Befund (Etappe 2b, Divi 5.2.1, Ruling S5): (1) Dynamic-Content-`before`/`after` wird bei der Ausgabe nur dann mit `wp_kses` gefiltert, wenn der **Autor des Beitrags** kein `unfiltered_html` hat (`DynamicContentElements::get_wrapper_element`); auf Seiten eines Administrators gelangt es ungefiltert ins HTML – ein Redakteur könnte dort Skript platzieren. (2) `enable_html: on` gibt Meta-Werte mit `wp_kses_post` statt `esc_html` aus; Divi entfernt `enable_html` beim Speichern nur für Benutzer ohne `unfiltered_html` (`Security::sanitize_dynamic_content_fields`, `wp_insert_post_data` Priorität 10). (3) `custom_meta_` wird ohne `is_protected_meta`-Prüfung ausgegeben (`DynamicContentOptionPostMetaKey`). (4) Farbverweis-Namen gehen unescaped in `var(--)` (`StyleLibrary\Utils::resolve_dynamic_variable`). (5) Divis eigene `wp_insert_post_data`-Filter sind kein kses und laufen auch bei unserem Speichern ohne kses: `AttributeSecurity` (Priorität 5, immer, sobald `"decoration":{` und `"attributes":{` vorkommen) und `HtmlSecurity` (Priorität 6, bei `htmlBefore`/`htmlAfter`/`elementType`) parsen und re-serialisieren die **ganze Seite**; `DynamicContentFixes` (Priorität 10). Folgen: die Plugin-Prüfung aus D7 ist die eigentliche Garantie (VariableRef: nur ganze, geprüfte, neu serialisierte Verweise; `before`/`after` ohne unzulässiges HTML, kein `enable_html`, keine Meta-Schlüssel mit `_`); Divis Filter bleiben aktiv (Verteidigung in der Tiefe) und werden nie entfernt; Schreiben auf `*.decoration.attributes` und `*.advanced.html` nur mit `unfiltered_html` („nur im Divi-Builder“, Ruling S7), und auf Seiten mit diesen Feldern gilt die Byte-Invarianz nicht (Divi normalisiert beim Speichern). – Kosten: keine (Dokumentation; die Sicherheit hängt nicht von Divis Ausgabe ab). + - Nachtrag (Etappe 2b, Task 10, live): Divis `AttributeSecurity`/`HtmlSecurity` serialisieren mit `serialize_blocks()`; Bytes ändern sich nur, wenn die Seite noch nicht in dieser Form gespeichert war (z. B. Import, direkter DB-Schreibzugriff). Über Builder oder LIT MCP gespeicherte Seiten bleiben außerhalb der Änderung byte-identisch (E2E `divi-write-smoke.sh` 8a/8b). +- D8: Spalten neuer Zeilen über `module.decoration.sizing.desktop.value.flexType` (`"1_3,2_3"` → `8_24`, `16_24`; Fünftel und 1/7, 1/9, 1/10, 1/11 nativ), wie Divi 5.2 (Flex-Layout ist Standard). – Kosten: falls der Builder anders speichert, nur Composer anpassen. +- D9: Platzhalter stehen allein im Wert; Überschriften der Kurzform ohne `level` werden `h2`. – Kosten: eingebettete dynamische Texte nur über die Vollform. +- D10: `lit/divi-replace-text` je Inhaltstyp; Probelauf liefert `tree_rev` je Seite, Ausführung nur auf diesen `targets` mit `confirm: true`; ersetzt nur sichtbaren Text (`…innerContent`, Schlüssel text/title/label/alt/titleText), nie URLs oder Stile. – Kosten: Text mit HTML-Entitäten muss so gesucht werden, wie er gespeichert ist. +- D11: `lit/content-create` mit `divi` speichert über `wp_insert_post` mit WordPress' üblicher Filterung (nur neuer, selbst erzeugter Inhalt – nichts Unberührtes, das byte-identisch bleiben müsste). – Kosten: für Benutzer ohne `unfiltered_html` kann WordPress Entitäten normalisieren; `tree_rev` in der Antwort beschreibt den gespeicherten Stand. + - **Überholt (Etappe 2b, Task 9):** Titel, Auszug und übrige Felder laufen weiter durch `wp_insert_post` mit üblicher Filterung; das Divi-Layout wird danach über `DiviSaver::saveNew` im selben kses-Fenster wie alle Divi-Schreibvorgänge gespeichert (D7, ein einziger Speicherweg). Grund: WordPress' kses würde die `$variable`-Verweise und Blockattribute sonst für Benutzer ohne `unfiltered_html` verändern; bereinigt wird vorher je Feldart (AttrWriter/VariableRef). +- D12: Fixtures: handgeschriebene Grenzfälle (`synthetic-*.html`), mit Divis eigener Konvertierung erzeugte Seiten (`converted-*.html`), echte Modul-Metadaten (`metadata-sample.json`); im Visual Builder gebaute Seiten exportiert Jan-Niclas später mit `tests/Integration/divi-export-page.php` als `builder-*.html` – sie laufen automatisch im Roundtrip-Test mit. diff --git a/docs/superpowers/notes/2026-09-26-etappe-2a-abnahme.md b/docs/superpowers/notes/2026-09-26-etappe-2a-abnahme.md new file mode 100644 index 0000000..7c7d684 --- /dev/null +++ b/docs/superpowers/notes/2026-09-26-etappe-2a-abnahme.md @@ -0,0 +1,65 @@ +# Etappe 2a – Manuelle Abnahme (Divi lesen) + +Vorbereitet von Task 8 (Hinweise/Guides/Serveranweisungen/Ende-zu-Ende-Test). Automatisiert geprüft ist +bereits: alle Unit-Tests (`composer test`, 692 Tests), Lint (`composer lint`) und alle vier +Integrationsskripte (`tests/Integration/divi-read-smoke.sh`, `tests/Integration/mcp-smoke.sh`, +`tests/Integration/wp-basis-smoke.sh`, `tests/Integration/media-admin-smoke.sh`) – Details im Report von +Task 8 (`.superpowers/sdd/2026-09-26-etappe-2a-divi-lesen/task-8-report.md`). + +Die folgenden Punkte lassen sich nicht automatisiert prüfen (echter Divi Visual Builder, +claude.ai-Connector); das führt Jan-Niclas selbst durch. + +## 1. Builder-Fixtures exportieren (Roundtrip mit echten Seiten) + +Alle bisherigen Divi-Fixtures sind entweder handgeschrieben (`synthetic-*.html`), von Divis eigener +Shortcode→Block-Konvertierung erzeugt (`converted-*.html`) oder reine Modul-Metadaten +(`metadata-sample.json`). Es fehlt noch mindestens eine echte, im Visual Builder gebaute Seite als +Gegenprobe für Parser/Serializer. + +- [ ] 2–3 Seiten im Divi Visual Builder bauen. Zusammen sollten sie enthalten: + - ein **globales Modul** (aus der Divi-Bibliothek eingefügt), + - ein **Modul-Preset**, + - eine **globale Farbe** und eine **globale Variable**, + - **Dynamic Content** (z. B. Beitragstitel, ein Meta-Feld), + - einen **Spezialabschnitt** (Specialty Section) und eine **verschachtelte Zeile**. + - Keine personenbezogenen Daten verwenden – die Fixtures werden eingecheckt. +- [ ] Je Seite exportieren und den Roundtrip prüfen: + ```bash + cd /home/janniclas/Projekte/WordPress + timeout 300 ddev exec wp eval-file wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-export-page.php + cd wp-content/plugins/WP-LIT-MCP && composer test + ``` + Erwartet: `Success: builder-.html: bytes, blocks, globalModule: ja/nein, tree_rev .`, + danach `composer test` weiterhin grün (der Test läuft automatisch über alle `builder-*.html`-Dateien mit). +- [ ] Schlägt der Roundtrip fehl (Parser/Serializer erzeugen nicht mehr das byte-identische Markup): das + ist ein **Fehler im Plugin** (`Parser`/`Serializer`), nie ein Grund, die Fixture anzupassen. Als + Folgeaufgabe melden statt selbst zu reparieren. +- [ ] Fixtures ohne personenbezogene Daten einchecken: + ```bash + git add tests/Fixtures/divi/builder-*.html + git -c user.name="Jan-Niclas Loosen" -c user.email="janniclas@loosen-it.de" commit -m "test: Divi builder fixtures" + ``` + +## 2. claude.ai: Divi-Lese-Werkzeuge im Gespräch + +Mit dem claude.ai-Connector gegen eine Website mit aktivem Divi-5-Theme (z. B. dieses DDEV-Setup oder +Staging) verbinden und prüfen, dass die Antworten mit dem tatsächlichen Aufbau im Divi-Builder +übereinstimmen: + +- [ ] „Zeig mir den Aufbau der Startseite“ → sollte `lit-divi-outline` aufrufen und einen Seitenbaum mit + Pfad-IDs, Modultypen und Textauszügen liefern, der zum Divi-Builder passt. +- [ ] „Welche Farben und Presets hat die Website?“ → sollte `lit-divi-design-system` aufrufen und die + globalen Farben/Presets nennen, die auch im Divi-Builder (Designsystem-Bereich) zu sehen sind. +- [ ] „Was sieht ein Besucher auf der Seite?“ → sollte `lit-page-render-summary` aufrufen und + Überschriften/Texte/Links/Bilder liefern, die dem Frontend entsprechen. +- [ ] Bei einer der zuvor im Visual Builder gebauten Seiten (Schritt 1): stichprobenartig prüfen, dass + globales Modul, Preset, Dynamic Content und Spezialabschnitt im Outline/Node-Get sinnvoll + wiedergegeben werden. Nur das globale Modul ist dabei „nur lesbar“ (siehe `divi-structure`-Guide); + ein Spezialabschnitt (specialty section) ist ein normaler, bearbeitbarer `divi/section`-Block – + Outline zeigt ihn lediglich mit dem Zusatz `[specialty]`, nicht als „nur lesbar“. + +## Ergebnis + +Kurze Rückmeldung an den Ausführenden (Task 8 / Controller): welche Punkte grün sind, welche +Builder-Fixtures committet wurden, und was ggf. als Folgeaufgabe (Bugfix im Parser/Serializer oder +Doku-Korrektur) aufgenommen werden muss. diff --git a/docs/superpowers/notes/2026-09-26-etappe-2a-rulings.md b/docs/superpowers/notes/2026-09-26-etappe-2a-rulings.md new file mode 100644 index 0000000..8ae8de4 --- /dev/null +++ b/docs/superpowers/notes/2026-09-26-etappe-2a-rulings.md @@ -0,0 +1,34 @@ +# Etappe 2a – Entscheidungen (Rulings) und offene Kleinigkeiten + +Aus dem SDD-Ledger übernommen, 2026-09-26. Weiterführende Rulings für 2b stehen im 2b-Ledger bzw. später in 2026-09-26-etappe-2b-rulings.md. + +## Pre-flight rulings +- R1 (blocking B1/B2): kses bypass on Divi saves is only acceptable if NO client-supplied string reaches post_content unsanitized. `$variable(...)$` values from input (placeholders AND raw values echoed back from divi-node-get) are never written as-is: parse the JSON payload, validate strictly (type ∈ {color, content}; name ∈ existing gcid-*/gvid-*/known dynamic-content source names/custom_meta_ with key [a-zA-Z0-9_-]; settings keys whitelisted; before/after/other string settings sanitized with wp_kses_post for users without unfiltered_html, sanitize_text_field for plain keys), then RE-SERIALIZE by the plugin. Anything else → validation error. D7 text in 2a Task 1 (spec amendment + decisions note) must describe exactly this ("werden geparst, geprüft und vom Plugin neu erzeugt"), not "alle Werte bereinigt" in general. Implementation happens in 2b (Placeholders/AttrWriter/ShortformWriter/TextReplacer) — carried into 2b pre-flight; 2b adds tests with hand-crafted malicious $variable payloads (script in before/after, unknown type, bogus name) and a DDEV check as a user without unfiltered_html. +- R2 (B3 assumption): 2b verifies in Divi source whether dynamic-content before/after are escaped on output; regardless, R1's sanitization applies (defense in depth). Record the finding in the decisions note. +- R3 (risky, URL routing): 2b routes URL sanitization by the catalog's attribute metadata (field type url/link/image/upload) when available, and by key name (url|src|link|href|image) otherwise; background-image and link hrefs covered; tests. +- R4 (cosmetic): PostPresenter → DiviModules static dependency documented in a docblock (layering exception) — fine. +- R5: security-sensitive tasks (2a Task 2 BlockTree, 2b writers) get opus reviewers. + +## Progress +- Task 1: fix round 1/5 (4 addressed: §5.6 tree_rev, §10 cross-ref D7, §11.3, §14; commits 040a60f..c9b31a6) — Ruling: 4-line docs-only fix verified by controller directly against the diff instead of a re-review agent — cost if wrong: none (docs) +- Task 1: complete (commits 703fecb..c9b31a6, review clean) +- Task 2: minor (deferred, MUST fix in 2a final wave before 2b writers): Json top-level list-like object → opaque; Block::create validates block name pattern; setAttrs/insertChild/removeChild guard on opaque blocks; copy() deep-clones stdClass or doc narrowed; Json::encode invalid UTF-8 handling documented/caught +- Task 2: complete (commits c9b31a6..8222240, review clean; fuzzed 200k inputs, 0 diffs) +- Task 3: minor (deferred, 2a final wave): AttrMap set/remove reject empty path segments +- Task 3: complete (commits 8222240..08d70ab, review clean) +- Task 4: note for 2b pre-flight: only ~564/4358 attr paths get a metadata kind; unknown kind MUST default to the strictest sanitizer (sanitize_text_field; URL only by key-name match), never raw +- Task 4: fix round 1/5 (composite paths unknown, report example, flatten reuse, shortName; commits 2f4dcf9..02ae76d) +- Task 4: complete (commits 08d70ab..02ae76d, review clean) +- Task 5: minor (deferred): design-system response unbounded for sites with hundreds of presets (no filter/detail param) +- Task 5: complete (commits 02ae76d..a54a337, review clean) +- Task 6: minor (deferred; 2a final wave): outline unbounded breadth (add limit/truncated flag); node-get exposes globalModule id for global refs; node-get description notes $variable values are re-validated on write (R1); outline outputSchema required list +- Task 6: complete (commits a54a337..d27504a, review clean) +- Task 7: fix round 1/5 (Text::shorten shared, data URIs, lazy src, ob drain, globals, oEmbed note, text extraction, tests; commits 2e9acd6..b3cd0ee) +- Task 7: complete (commits d27504a..b3cd0ee, review clean) +- Task 8: minor (deferred): divi-attrs guide should mention catalog without module lists modules +- Task 8: complete (commits b3cd0ee..f433fef, review clean) +- Final review (opus): ready with fixes. Ruling F1: final fix wave = #1 per-subName path kinds (+guide), #2 opaque guards, #3 list-like top-level attrs → opaque, #4 Block::create name validation, #7 AttrMap empty segments + object-ness, #8 copy deep-clone, #9 Json::encode UTF-8 → documented/mapped, #10 Container::append @internal/touched, #11 node-get rev + type short name, #12 outline label normalize/shorten + id pattern, #13 docblock/abnahme text, node-get R1 description note, outline outputSchema required, divi-attrs guide catalog-without-module — spec §5.3 + load-bearing for 2b writers — cost if wrong: small rework +- Ruling F2: outline limit/truncated, design-system size/filter, globalModule id on node-get → moved to 2b pre-flight (write results embed outline) — cost if wrong: none, tracked +- Ruling F3: #5 (R1/R3/embedded $variable plan amendments, R2 finding: Divi kses on before/after only when author lacks unfiltered_html, OptionLoopPostMetaKey raw), #6 (real builder fixtures), #14 (ambiguous design-system labels) → blocking items for 2b pre-flight — cost if wrong: none +- Final fix wave: 11/11 addressed (commits 4137882..4356e69 after trailer normalization; 722 tests, smoke green); per-subName path kinds chosen (path_kinds values string|object); re-review running +- Re-review: 10/11 clean; residuals (Serializer docblock, /D anchors on NAME/ID patterns, number-advanced → unknown, doc wording 'ein oder mehrere') fixed by controller inline (c24b91d, 723 tests, lint ok). Parked: AttrMap::remove() doesn't traverse stdClass (harmless, no caller); children of opaque blocks editable via direct object access (unreachable: NodeIndex doesn't index below opaque) diff --git a/docs/superpowers/notes/2026-09-26-etappe-2b-abnahme.md b/docs/superpowers/notes/2026-09-26-etappe-2b-abnahme.md new file mode 100644 index 0000000..0fa353f --- /dev/null +++ b/docs/superpowers/notes/2026-09-26-etappe-2b-abnahme.md @@ -0,0 +1,167 @@ +# Etappe 2b – Manuelle Abnahme (Divi schreiben) + +Vorbereitet von Task 10 (Hinweise, Guides, Version 0.3.0, Ende-zu-Ende-Test). Automatisch geprüft sind +bereits: + +- alle Unit-Tests (`composer test`) und Lint (`composer lint`); +- die fünf Integrationsskripte `divi-write-smoke.sh`, `divi-read-smoke.sh`, `mcp-smoke.sh`, + `wp-basis-smoke.sh` und `media-admin-smoke.sh` (alle in `tests/Integration/`). + +`divi-write-smoke.sh` prüft über den OAuth-Endpunkt: + +- Einfügen, Ändern, Verschieben, Löschen und Duplizieren; +- Ersetzen mit Probelauf; +- `lit-content-create` mit Divi-Inhalt; +- den ersten Abschnitt auf einer leeren Seite; +- einen Redakteur ohne `unfiltered_html`: unberührte Bytes bleiben gleich, und es wird weder Skript, + Event-Handler, `javascript:` noch ein eingeschleustes Attribut gespeichert oder gerendert; +- Divis eigene Speicherfilter (Seite mit eigenen HTML-Attributen); +- keine Wege am Divi-Pfad vorbei: Rohtext-`content` mit Divi-Markup wird abgelehnt, und bei Divi + „nur lesen“ lehnt `lit-content-create` `divi` ab und `lit/divi-insert` ist auch über + `mcp-adapter-execute-ability` nicht erreichbar. + +Details stehen im Report von Task 10 (`.superpowers/sdd/2026-09-26-etappe-2b-divi-schreiben/task-10-report.md`). + +Die folgenden Punkte lassen sich nicht automatisch prüfen, weil sie den echten Divi Visual Builder und den +claude.ai-Connector brauchen. Jan-Niclas prüft sie selbst (Spec §1.3 Nr. 2, §13 „Bearbeitbarkeit im +Visual Builder“). Abweichungen als kurze Notiz zurückmelden; sie werden Folgeaufgaben (z. B. anders +gespeicherte Spaltenbreiten, D8). + +## Vorbereitung: Testdaten des Probelaufs entfernen + +Der Probelauf „Tischlerei Holzwerk Berger“ (trial-feedback.md) hat Daten auf der DDEV-Seite hinterlassen. +Vor der Abnahme von Hand zurücksetzen, sonst laufen §1–§3 auf einer veränderten Seite und das Testmenü +ist live. Nicht automatisch gelöscht – bitte selbst prüfen und ausführen +(`cd /home/janniclas/Projekte/WordPress && ddev wp …`): + +- [ ] Website-Titel und Untertitel zurücksetzen: + `ddev wp option update blogname "Breva Map Dev"` und `ddev wp option update blogdescription ""` + (vorher leer). +- [ ] Die „[Test]“-Entwürfe löschen: Seiten 831, 834, 837, 839 und Beiträge 854, 856, 858 + (vorher mit `ddev wp post list --post_type=page,post --post_status=draft --fields=ID,post_title` + prüfen, dass es genau diese sind), dann `ddev wp post delete 831 834 837 839 854 856 858 --force`. +- [ ] Das Menü „[Test] Hauptmenü“ (ID 22) aus der Position `primary-menu` nehmen und löschen: + `ddev wp menu location remove 22 primary-menu` und `ddev wp menu delete 22`; falls vorher ein + anderes Menü dort zugewiesen war, dieses wieder zuweisen (`ddev wp menu location assign + primary-menu`). +- [ ] Die Platzhalter-Medien 828, 829, 830 löschen: `ddev wp post delete 828 829 830 --force` + (vorher in der Mediathek prüfen, dass sie nirgends mehr verwendet werden). +- [ ] Optional: Einträge des Probelaufs im Protokoll-Tab über „Protokoll leeren“ entfernen. + +## 0. Verbindung, Zugriff, Tools- und Protokoll-Tab + +- [ ] Discovery ohne Weiterleitung (Fix DiscoveryCanonical, bb85868): + `curl -si https://wordpress.ddev.site:8443/.well-known/oauth-protected-resource | head -1` und + `curl -si https://wordpress.ddev.site:8443/.well-known/oauth-authorization-server | head -1` + antworten beide mit `200` (kein `301` auf eine URL mit Schrägstrich) und JSON. +- [ ] Den Connector in claude.ai neu verbinden (Einstellungen → Connectors → LIT MCP → Verbinden): + Login, Zustimmung, danach sind die Tools in einer neuen Unterhaltung sichtbar. +- [ ] Mit einem Konto verbinden, dessen Rolle unter Zugriff **nicht** freigegeben ist (z. B. ein + Abonnent): die Autorisierung bricht mit „Dein Benutzer hat keinen Zugriff auf LIT MCP.“ ab; es + entsteht keine Verbindung (kein Anwendungspasswort im Profil). +- [ ] Einstellungen → LIT MCP → Zugriff: die Spalte „Zuletzt genutzt“ zeigt für die eben genutzte + Verbindung ein Datum (Aktualisierung höchstens einmal am Tag). +- [ ] Tools-Tab: je Gruppe Modus und „nur lesen“; die Warnung zur Zahl direkter Tools erscheint erst, + wenn mehr als 25 Tools direkt sichtbar sind (z. B. testweise eine Gruppe auf „direkt“ stellen, + danach zurücksetzen). +- [ ] Protokoll-Tab: Liste wie die WordPress-Listen (Filter nach Tool, Benutzer und Ergebnis links über + der Tabelle, Blättern, „Einträge pro Seite“ in den Ansichtsoptionen oben rechts); Links zu + Bearbeiten/Revision je Eintrag. + +## 1. Visual Builder: von Claude gebaute Seite + +- [ ] Dieser Auftrag in claude.ai: + „Lege eine Entwurfsseite ‚Abnahme Divi‘ an mit Hero (Überschrift, Text, Button mit Preset), einer + Zeile 1/3 + 2/3 mit Bild und Blurb, einem Akkordeon und einem Kontaktformular.“ + Erwartet: + - Claude liest vorher `lit-divi-design-system` und `lit-divi-catalog`; + - dann `lit-content-create` mit `divi` (oder eine leere Seite plus `lit-divi-insert`); + - die Antwort nennt `preview_url` und `edit_url` (bei `divi` der Link in den Visual Builder, + `…&et_fb=1`). +- [ ] Die Seite im Divi Visual Builder öffnen (`edit_url`) und prüfen: + - [ ] Alle Module erscheinen als **native Module**, nicht als Code-Modul. + - [ ] Die Admin-Labels folgen dem Muster „ – “, z. B. „Hero – Button“. + - [ ] Jedes Modul lässt sich anklicken, ändern und speichern, ohne Fehlermeldung des Builders. + - [ ] Die Spaltenbreiten stimmen (1/3 + 2/3), auch in der Spalteneinstellung des Builders. + - [ ] Farben aus `{{color:…}}` sind als **globale Farbe** verknüpft: der Farbwähler zeigt die globale + Farbe, nicht einen festen Hex-Wert. + - [ ] Das Button-Preset ist im Builder als Preset ausgewählt. + - [ ] Akkordeon-Einträge und Kontaktformular-Felder sind einzeln bearbeitbar. +- [ ] Eine zweite Seite nur mit `lit-divi-insert` aufbauen lassen: leere Seite anlegen, dann + „Füge einen Abschnitt ‚Start‘ mit Überschrift und Text ein“. Im Builder öffnen: der Abschnitt + erscheint normal und ist bearbeitbar (Ruling S9: Hülle `divi/placeholder` wie beim Builder). + +## 2. Visual Builder: von Claude geänderte Seite + +- [ ] Eine bestehende, im Builder gebaute Seite kopieren (`lit/post-duplicate`) und an der Kopie per + claude.ai einen Button-Text ändern, ein Modul verschieben und einen Abschnitt duplizieren lassen. +- [ ] Die Kopie im Builder öffnen und prüfen: + - [ ] Alle unveränderten Module sehen aus wie vorher (Einstellungen, Presets, globale Farben). + - [ ] Die geänderten Module sind bearbeitbar, und die Änderung ist sichtbar. + - [ ] Die Kopie eines Abschnitts heißt „