diff --git a/.agents/skills/rl/SKILL.md b/.agents/skills/rl/SKILL.md index 89c375c..26fde07 100644 --- a/.agents/skills/rl/SKILL.md +++ b/.agents/skills/rl/SKILL.md @@ -1,36 +1,136 @@ -# RL — Reinforcement Learning Experiments (Drush CLI) +--- +name: rl +version: 1.0.0 +description: > + Manage Reinforcement Learning (A/B testing) experiments via Drush CLI. + Thompson Sampling multi-armed bandit experiments: create, analyze, + configure, and optimize content variants. +triggers: + - /rl + - experiment + - a/b test + - ab test + - bandit + - thompson sampling + - conversion rate + - variant +--- -Manage A/B testing experiments powered by Thompson Sampling via Drush. +# RL: Reinforcement Learning Experiments -## Commands +You are managing A/B testing experiments powered by Thompson Sampling. +The RL module tracks impressions (turns) and conversions (rewards) across +content variants and automatically optimizes traffic distribution. -### Discovery & Analysis -- `drush rl:list` — List all experiments with stats -- `drush rl:status ` — Experiment phase, confidence, distribution -- `drush rl:performance ` — Arm-level conversion rates and labels -- `drush rl:trends ` — Historical trends (--period=daily|weekly|monthly) -- `drush rl:export ` — Export full data (--snapshots for history) -- `drush rl:analyze ` — Full analysis with recommendations +## Preamble: Auto-discover Current State -### Experiment CRUD -- `drush rl:experiment:create --module=X --name="Y"` — Create experiment -- `drush rl:experiment:update --name="Y"` — Update metadata -- `drush rl:experiment:delete ` — Delete experiment + all data +```bash +# List all active experiments +drush rl:list --format=yaml + +# Get current module configuration +drush rl:config:list + +# If experiments exist, get status of the most active one +# drush rl:status --format=yaml +``` + +## Commands Reference + +### Discovery & Analysis (read-only) + +| Command | Alias | Purpose | +|---|---|---| +| `rl:list` | `rll` | List all experiments with summary stats | +| `rl:status ` | `rlst` | Detailed status: phase, confidence, traffic distribution | +| `rl:performance ` | `rlp` | Arm-level performance with human-readable labels | +| `rl:trends ` | `rlt` | Historical trends with period aggregation | +| `rl:export ` | `rle` | Export complete experiment data | +| `rl:analyze ` | `rla` | Full analysis with actionable recommendations | + +### Experiment Lifecycle + +| Command | Alias | Purpose | +|---|---|---| +| `rl:experiment:create ` | `rl-ec` | Create experiment (`--module`, `--name`, `--dry-run`) | +| `rl:experiment:update ` | `rl-eu` | Update name/module (`--module`, `--name`, `--dry-run`) | +| `rl:experiment:delete ` | `rl-ed` | Delete experiment + all data (`--dry-run`) | ### Configuration -- `drush rl:config:list` — List all settings -- `drush rl:config:get ` — Get one setting -- `drush rl:config:set ` — Set with validation -- `drush rl:config:reset` — Reset to defaults -- `drush rl:event-log:clear` — Clear all snapshots - -### Settings Keys -| Key | Type | Default | + +| Command | Alias | Purpose | +|---|---|---| +| `rl:config:get [key]` | `rl-cg` | Get one or all settings | +| `rl:config:set ` | `rl-cs` | Set a setting with validation (`--dry-run`) | +| `rl:config:list` | `rl-cl` | List all settings with current values | +| `rl:config:reset` | `rl-cr` | Reset all settings to defaults (`--dry-run`) | +| `rl:event-log:clear` | `rl-elc` | Clear all snapshots (`--dry-run`) | + +### Setup + +| Command | Alias | Purpose | |---|---|---| -| debug_mode | boolean | false | -| enable_event_log | boolean | true | -| event_log_max_rows | integer | 100000 | -| chart_line_threshold | integer | 9 | +| `rl:setup-ai` | `rl-sa` | Install AI skill files (`--host`, `--check`) | + +## Configuration Keys + +| Key | Type | Default | Description | +|---|---|---|---| +| `debug_mode` | boolean | false | Log Thompson Sampling scores | +| `enable_event_log` | boolean | true | Record snapshots for historical charts | +| `event_log_max_rows` | integer | 100000 | Max snapshot rows (1000–10000000) | +| `chart_line_threshold` | integer | 9 | Max variants for 2D line chart (2–50) | + +## Workflow Examples + +### Create and monitor a new A/B test +```bash +# Create experiment +drush rl:experiment:create hero_cta_test \ + --module=my_module \ + --name="Hero CTA Button Color Test" + +# Check status after data collection +drush rl:status hero_cta_test + +# Get detailed performance breakdown +drush rl:performance hero_cta_test --format=yaml + +# Full analysis with recommendations +drush rl:analyze hero_cta_test +``` + +### Configure the module +```bash +# Enable debug logging temporarily +drush rl:config:set debug_mode true + +# Increase snapshot storage +drush rl:config:set event_log_max_rows 500000 + +# Check all settings +drush rl:config:list +``` + +## Key Concepts + +- **Experiment**: A multi-armed bandit test comparing content variants +- **Arm**: A single variant (identified by arm_id, often a node ID) +- **Turn/Impression**: One display of a variant to a visitor +- **Reward/Conversion**: A successful outcome (click, form submit, etc.) +- **Thompson Sampling**: Bayesian algorithm that balances + exploration vs exploitation +- **Phase**: exploration, learning, exploitation, conclusive +- **Confidence**: Statistical confidence that the top + performer is truly best + +## Notes -All state-changing commands support `--dry-run`. -Analysis commands support `--format=json|yaml|table`. +- Experiments are tracked in custom database tables, + not Drupal config entities +- The `rl.php` endpoint provides high-performance + tracking with minimal Drupal bootstrap +- Arms are typically node IDs; the analyzer resolves + them to node titles automatically +- Views management is handled by `drush_webmaster` + (`wm:view:*` commands); do not duplicate diff --git a/.claude/skills/rl/SKILL.md b/.claude/skills/rl/SKILL.md index b3b5bb5..26fde07 100644 --- a/.claude/skills/rl/SKILL.md +++ b/.claude/skills/rl/SKILL.md @@ -16,13 +16,13 @@ triggers: - variant --- -# RL — Reinforcement Learning Experiments +# RL: Reinforcement Learning Experiments You are managing A/B testing experiments powered by Thompson Sampling. The RL module tracks impressions (turns) and conversions (rewards) across content variants and automatically optimizes traffic distribution. -## Preamble — Auto-discover Current State +## Preamble: Auto-discover Current State ```bash # List all active experiments @@ -133,4 +133,4 @@ drush rl:config:list - Arms are typically node IDs; the analyzer resolves them to node titles automatically - Views management is handled by `drush_webmaster` - (`wm:view:*` commands) — do not duplicate + (`wm:view:*` commands); do not duplicate diff --git a/.github/workflows/skills-pair-check.yml b/.github/workflows/skills-pair-check.yml new file mode 100644 index 0000000..a5a8057 --- /dev/null +++ b/.github/workflows/skills-pair-check.yml @@ -0,0 +1,62 @@ +name: Skills pair check + +on: + pull_request: + paths: + - '.claude/**' + - '.agents/**' + - '**/.claude/**' + - '**/.agents/**' + +jobs: + pairs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Fail when paired skill files differ + run: | + fail=0 + # Every SKILL.md whose skill exists in both trees must exist in + # both trees and be byte-identical. Skills living in only one + # tree (e.g. Claude-only helpers) are skipped by design. + while IFS= read -r claude_file; do + agents_file="${claude_file//\/.claude\//\/.agents\/}" + agents_dir=$(dirname "$agents_file") + if [ -d "$agents_dir" ]; then + if [ ! -f "$agents_file" ]; then + echo "MISSING: $agents_file (no .agents counterpart)" + fail=1 + elif ! cmp -s "$claude_file" "$agents_file"; then + echo "DIFFER: $claude_file vs $agents_file" + fail=1 + fi + fi + done < <(find . -type f -name 'SKILL.md' -path '*/.claude/*' \ + -not -path './.git/*' -not -path '*/node_modules/*' \ + -not -path '*/vendor/*' -not -path '*worktrees*') + while IFS= read -r agents_file; do + claude_file="${agents_file//\/.agents\//\/.claude\/}" + claude_dir=$(dirname "$claude_file") + if [ -d "$claude_dir" ] && [ ! -f "$claude_file" ]; then + echo "MISSING: $claude_file (no .claude counterpart)" + fail=1 + fi + done < <(find . -type f -name 'SKILL.md' -path '*/.agents/*' \ + -not -path './.git/*' -not -path '*/node_modules/*' \ + -not -path '*/vendor/*' -not -path '*worktrees*') + # All other files are compared only when both counterparts + # exist; host-specific files such as agents/openai.yaml stay + # optional. + while IFS= read -r claude_file; do + agents_file="${claude_file//\/.claude\//\/.agents\/}" + if [ -f "$agents_file" ] \ + && ! cmp -s "$claude_file" "$agents_file"; then + echo "DIFFER: $claude_file vs $agents_file" + fail=1 + fi + done < <(find . -type f -path '*/.claude/*' \ + -not -name 'SKILL.md' -not -path './.git/*' \ + -not -path '*/node_modules/*' -not -path '*/vendor/*' \ + -not -path '*worktrees*') + exit $fail diff --git a/modules/rl_menu_link/.agents/skills/rl_menu_link/SKILL.md b/modules/rl_menu_link/.agents/skills/rl_menu_link/SKILL.md index 11a3b84..4b9474c 100644 --- a/modules/rl_menu_link/.agents/skills/rl_menu_link/SKILL.md +++ b/modules/rl_menu_link/.agents/skills/rl_menu_link/SKILL.md @@ -1,57 +1,138 @@ -# RL Menu Link — A/B test menu link labels via Drush CLI +--- +name: rl_menu_link +version: 1.0.0 +description: > + A/B test Drupal menu link labels using Thompson Sampling. Works for + both menu_link_content entities and YAML-defined links. Per-language + scoping. Manage experiments via Drush CLI. +triggers: + - /rl-menu-link + - menu link test + - menu label test + - menu variant + - a/b test menu + - test menu link + - rl_menu_link +--- -A/B test Drupal menu link labels for menu_link_content entities and -YAML-defined links using Thompson Sampling. Per-language scoping. +# RL Menu Link: Drush CLI -## Commands +You are managing A/B testing experiments for Drupal menu link labels. +The rl_menu_link module is a content-entity-backed integration on top +of the parent rl module's Thompson Sampling engine. -### Discovery -- `drush rl:menu-link:list` — List experiments (`--enabled=yes|no|all`) -- `drush rl:menu-link:get ` — Full details + live arm stats + original label +## Preamble: Auto-discover Current State -### Lifecycle -- `drush rl:menu-link:create --variants="A,B,C"` — Create -- `drush rl:menu-link:update --label="X"` — Update -- `drush rl:menu-link:delete ` — Delete + purge analytics +```bash +# List all menu link experiments with stats. +drush rl:menu-link:list --format=yaml -### Common options -- `--variants="A,B,C"` or `--variants=A --variants=B` — Alternative labels -- `--label="..."` — Human-readable label -- `--langcode=es` — Per-language scope (default `und` = all languages) -- `--disabled` — Create as disabled -- `--dry-run` — Preview without applying +# Filter to active experiments only. +drush rl:menu-link:list --enabled=yes --format=yaml +``` -All commands output YAML. All state-changing commands support `--dry-run`. +## Commands Reference -## Plugin ID format +| Command | Alias | Purpose | +|---|---|---| +| `rl:menu-link:list` | `rl-mll` | List experiments (`--enabled=yes\|no\|all`) | +| `rl:menu-link:get ` | `rl-mlg` | Show full details + live arm stats + original label | +| `rl:menu-link:create ` | `rl-mlc` | Create experiment (`--variants`, `--label`, `--langcode`, `--disabled`, `--dry-run`) | +| `rl:menu-link:update ` | `rl-mlu` | Update label / variants / enable / disable (`--dry-run`) | +| `rl:menu-link:delete ` | `rl-mld` | Delete experiment AND purge RL analytics (`--dry-run`) | -- User-created menu links: `menu_link_content:abc-uuid` -- YAML-defined links: machine name like `system.admin_content`, - `user.page`, `system.admin_structure` +All state-changing commands support `--dry-run`. All commands output YAML. ## Concepts -- The original label is always tested as variant `v0` (read live from - the menu link manager). -- Stored variants are `v1`, `v2`, etc. -- Each `(plugin_id, langcode)` pair is its own experiment with - independent Thompson Sampling state. -- Reward signal: user clicked the tracked menu link. -- Lookups are indexed; tested at 10K+ experiments per site. +- **Plugin ID**: every menu link in Drupal has a plugin ID. For + user-created `menu_link_content` entities it looks like + `menu_link_content:abc-uuid` (the entity UUID). For YAML-defined links + from contrib/custom modules it is the link's machine name (e.g., + `system.admin_content`, `user.page`). +- **Variant**: an alternative label text. The original label (whatever + the menu link normally renders) is always tested as variant `v0`. + Stored variants are `v1`, `v2`, etc. +- **Langcode**: experiments are scoped per language. Use `und` (the + default, `LANGCODE_NOT_SPECIFIED`) for "all languages". Lookup tries + language-specific match first, falls back to "all languages". +- **Reward**: a click on the tracked menu link. + +## Workflow Examples + +### Test alternatives for a content menu link + +```bash +# Find the plugin ID first via the menu UI or: +drush ev "echo \\Drupal::entityTypeManager()->getStorage('menu_link_content')->load(1)->getPluginId();" + +# Then create the experiment. +drush rl:menu-link:create menu_link_content:abc-uuid \ + --variants="Services,What We Do,Solutions" \ + --label="Services menu link test" +``` -## Example +### Test a core admin menu link ```bash -# A/B test the core "Content" admin link with alternatives. drush rl:menu-link:create system.admin_content \ - --variants="Content,Manage Content,Site Content" + --variants="Content,Manage Content,Edit Site" +``` -# Check progress. -drush rl:menu-link:get +### Test a Spanish-only variant + +```bash +drush rl:menu-link:create menu_link_content:abc-uuid \ + --variants="Servicios,Lo Que Hacemos" \ + --langcode=es +``` + +### Use multiple --variants flags -# Delete + purge. -drush rl:menu-link:delete +```bash +drush rl:menu-link:create system.admin_structure \ + --variants="Structure" \ + --variants="Site Structure" \ + --variants="Layout" +``` + +### Preview before applying + +```bash +drush rl:menu-link:create system.admin_content \ + --variants="One,Two" \ + --dry-run +``` + +### Check current state of an experiment + +```bash +drush rl:menu-link:get +# Returns: id, label, plugin id, original_label (read live from menu +# link manager), langcode, enabled, variants list, rl_experiment_id, +# total_turns, per-arm turns/rewards/rate. ``` -The same admin UI is available at /admin/config/services/rl-menu-link -via Views. +## How variants reach end users + +1. The module's preprocess_menu hook walks the menu tree on every + render and looks up an experiment by each item's plugin ID. +2. Matching items get their `title` swapped with the Thompson Sampling + winner, and `data-rl-ml-experiment-id` / `data-rl-ml-arm-id` data + attributes are injected onto the rendered anchor. +3. The bundled tracking JS uses IntersectionObserver to record an + impression when the link enters the viewport, and a click handler + to record a reward. +4. Tens of thousands of experiments scale via indexed `(plugin_id, + langcode)` lookups (content entities, not config). + +## Notes + +- The same menu link can have separate experiments per language. +- Vertical tabs on `menu_link_content` edit forms also create menu link + experiments; the Drush commands operate on the same content entities. +- The admin UI is at `/admin/config/services/rl-menu-link` (Views). +- Analytics are managed by the parent `rl` module; use `drush rl:list` + and the `rl:experiment:*` commands to inspect raw turn/reward data. +- Deleting an experiment via this command purges the RL analytics + tables (turns, rewards, snapshots, registry) atomically. diff --git a/modules/rl_menu_link/.claude/skills/rl_menu_link/SKILL.md b/modules/rl_menu_link/.claude/skills/rl_menu_link/SKILL.md index f536ded..4b9474c 100644 --- a/modules/rl_menu_link/.claude/skills/rl_menu_link/SKILL.md +++ b/modules/rl_menu_link/.claude/skills/rl_menu_link/SKILL.md @@ -15,13 +15,13 @@ triggers: - rl_menu_link --- -# RL Menu Link — Drush CLI +# RL Menu Link: Drush CLI You are managing A/B testing experiments for Drupal menu link labels. The rl_menu_link module is a content-entity-backed integration on top of the parent rl module's Thompson Sampling engine. -## Preamble — Auto-discover Current State +## Preamble: Auto-discover Current State ```bash # List all menu link experiments with stats. diff --git a/modules/rl_page_title/.agents/skills/rl_page_title/SKILL.md b/modules/rl_page_title/.agents/skills/rl_page_title/SKILL.md index 8e6cc4a..0ec4fdf 100644 --- a/modules/rl_page_title/.agents/skills/rl_page_title/SKILL.md +++ b/modules/rl_page_title/.agents/skills/rl_page_title/SKILL.md @@ -1,54 +1,133 @@ -# RL Page Title — A/B test page titles via Drush CLI +--- +name: rl_page_title +version: 1.0.0 +description: > + A/B test page titles for any Drupal page (nodes, Views displays, custom + controllers, any path) using Thompson Sampling. Per-language scoping. + Manage experiments via Drush CLI. +triggers: + - /rl-page-title + - page title test + - title experiment + - title variant + - a/b test page title + - test page title + - rl_page_title +--- -A/B test Drupal page titles for any page (nodes, Views displays, custom -controllers, any path) using Thompson Sampling. Per-language scoping. +# RL Page Title: Drush CLI -## Commands +You are managing A/B testing experiments for Drupal page titles. The +rl_page_title module is a content-entity-backed integration on top of +the parent rl module's Thompson Sampling engine. -### Discovery -- `drush rl:page-title:list` — List experiments (`--enabled=yes|no|all`) -- `drush rl:page-title:get ` — Full details + live arm stats +## Preamble: Auto-discover Current State -### Lifecycle -- `drush rl:page-title:create --variants="A,B,C"` — Create -- `drush rl:page-title:update --label="X"` — Update label/variants/enable/disable -- `drush rl:page-title:delete ` — Delete + purge analytics +```bash +# List all page title experiments with stats. +drush rl:page-title:list --format=yaml + +# Filter to active experiments only. +drush rl:page-title:list --enabled=yes --format=yaml +``` + +## Commands Reference -### Common options -- `--variants="A,B,C"` or `--variants=A --variants=B` — Alternative titles -- `--label="..."` — Human-readable label -- `--langcode=es` — Per-language scope (default `und` = all languages) -- `--disabled` — Create as disabled -- `--dry-run` — Preview without applying +| Command | Alias | Purpose | +|---|---|---| +| `rl:page-title:list` | `rl-ptl` | List experiments (`--enabled=yes\|no\|all`) | +| `rl:page-title:get ` | `rl-ptg` | Show full details + live RL stats per arm | +| `rl:page-title:create ` | `rl-ptc` | Create experiment (`--variants`, `--label`, `--langcode`, `--disabled`, `--dry-run`) | +| `rl:page-title:update ` | `rl-ptu` | Update label / variants / enable / disable (`--dry-run`) | +| `rl:page-title:delete ` | `rl-ptd` | Delete experiment AND purge RL analytics (`--dry-run`) | -All commands output YAML. All state-changing commands support `--dry-run`. +All state-changing commands support `--dry-run`. All commands output YAML. ## Concepts -- The original title is always tested as variant `v0` (read live). -- Stored variants are `v1`, `v2`, etc. -- Each `(path, langcode)` pair is its own experiment with independent - Thompson Sampling state. -- Reward signal: user stayed on page 10 seconds (bounce-rate proxy). -- Lookups are indexed; tested at 10K+ experiments per site. +- **Path**: the internal Drupal path to test (e.g., `/node/42`, `/blog`, + `/user/login`). Aliases are accepted and resolved on save. +- **Variant**: an alternative title text. The original title (whatever + Drupal renders normally) is always tested as variant `v0`. Stored + variants are `v1`, `v2`, etc. +- **Langcode**: experiments are scoped per language. Use `und` (the + default, `LANGCODE_NOT_SPECIFIED`) for "all languages". Lookup tries + language-specific match first, falls back to "all languages". +- **Reward**: a successful outcome (the user stayed on the page for 10 + seconds, default reward strategy). Tracked client-side via the + bundled JS. -## Example +## Workflow Examples + +### Test alternatives for a node title ```bash -# A/B test the /blog page title with two alternatives. -drush rl:page-title:create /blog \ - --variants="News & Insights,Latest Articles" \ - --label="Blog index test" +# Create with two alternatives. The original /node/42 title is v0. +drush rl:page-title:create /node/42 \ + --variants="Learn About Our Team,Meet ACME" -# Check progress. -drush rl:page-title:get +# Wait for traffic, then check progress. +drush rl:page-title:get -# Disable temporarily. +# Pause without deleting if results are inconclusive. drush rl:page-title:update --disable -# Delete + purge. +# Delete and purge analytics when done. drush rl:page-title:delete ``` -The same admin UI is available at /admin/config/services/rl-page-title -via Views. +### Test a Views page title in Spanish only + +```bash +drush rl:page-title:create /blog \ + --variants="Nuestro Blog,Últimos Artículos" \ + --langcode=es \ + --label="Blog index (Spanish)" +``` + +### Use multiple --variants flags instead of comma-separated + +```bash +drush rl:page-title:create /user/login \ + --variants="Sign In" \ + --variants="Log In to Continue" \ + --variants="Welcome Back" +``` + +### Test a custom module page (the form page itself) + +```bash +drush rl:page-title:create /node/add/article \ + --variants="Create New Article,Write a New Story" +``` + +### Preview before applying + +```bash +drush rl:page-title:create /blog \ + --variants="One,Two" \ + --dry-run +``` + +## How variants reach end users + +1. The module's preprocess_page_title hook intercepts the rendered title + for the matching path on every request. +2. The Thompson Sampling selector picks a winning variant based on + accumulated turns and rewards. +3. The bundled tracking JS records a turn on page load and a reward if + the user stays past the threshold (10s default). +4. Tens of thousands of experiments scale via indexed `(path, langcode)` + lookups (content entities, not config). + +## Notes + +- The same page can have separate experiments per language, with + independent Thompson Sampling state. +- Vertical tabs on entity edit forms also create page title experiments; + the Drush commands operate on the same content entities. +- The admin UI is at `/admin/config/services/rl-page-title` (Views). +- Analytics are managed by the parent `rl` module; use `drush rl:list` + and the `rl:experiment:*` commands to inspect raw turn/reward data. +- Deleting an experiment via this command purges the RL analytics + tables (turns, rewards, snapshots, registry) atomically. diff --git a/modules/rl_page_title/.claude/skills/rl_page_title/SKILL.md b/modules/rl_page_title/.claude/skills/rl_page_title/SKILL.md index 772b934..0ec4fdf 100644 --- a/modules/rl_page_title/.claude/skills/rl_page_title/SKILL.md +++ b/modules/rl_page_title/.claude/skills/rl_page_title/SKILL.md @@ -15,13 +15,13 @@ triggers: - rl_page_title --- -# RL Page Title — Drush CLI +# RL Page Title: Drush CLI You are managing A/B testing experiments for Drupal page titles. The rl_page_title module is a content-entity-backed integration on top of the parent rl module's Thompson Sampling engine. -## Preamble — Auto-discover Current State +## Preamble: Auto-discover Current State ```bash # List all page title experiments with stats. diff --git a/modules/rl_page_title/README.md b/modules/rl_page_title/README.md index 7302a13..0a0ca5a 100644 --- a/modules/rl_page_title/README.md +++ b/modules/rl_page_title/README.md @@ -34,7 +34,7 @@ experiment entity. Stored variants are arms v1, v2, ... vN. The RL experiment ID is a deterministic hash: `rl_page_title-{12-char-sha1-of-path-pipe-langcode}`. The hash includes the -langcode so each language gets its own Thompson Sampling state -- an English +langcode so each language gets its own Thompson Sampling state: an English experiment for `/blog` and a Spanish experiment for `/blog` accumulate separate turns and rewards. diff --git a/src/Drush/Commands/RlCommandsBase.php b/src/Drush/Commands/RlCommandsBase.php index 9c6fb61..aa3232c 100644 --- a/src/Drush/Commands/RlCommandsBase.php +++ b/src/Drush/Commands/RlCommandsBase.php @@ -18,7 +18,7 @@ abstract class RlCommandsBase extends DrushCommands { /** * Switches to admin user for the duration of the Drush process. * - * Does not call switchBack() — relies on process termination after + * Does not call switchBack(), relying on process termination after * command execution. */ protected function switchToAdmin(): void { diff --git a/src/Drush/Commands/RlSetupCommands.php b/src/Drush/Commands/RlSetupCommands.php index 5a42f6b..536239b 100644 --- a/src/Drush/Commands/RlSetupCommands.php +++ b/src/Drush/Commands/RlSetupCommands.php @@ -180,15 +180,15 @@ protected function checkSkillFiles(array $modules, string $projectRoot, string $ continue; } if (!file_exists($dest)) { - $results[] = sprintf('%s — NOT INSTALLED', $relativePath); + $results[] = sprintf('%s: NOT INSTALLED', $relativePath); $outdated = TRUE; } elseif (md5_file($source) !== md5_file($dest)) { - $results[] = sprintf('%s — OUTDATED', $relativePath); + $results[] = sprintf('%s: OUTDATED', $relativePath); $outdated = TRUE; } else { - $results[] = sprintf('%s — up to date', $relativePath); + $results[] = sprintf('%s: up to date', $relativePath); } } }