Awesome! Yes, Opus 4.5 is really good now with PW. It also helps a lot that they have implemented the LSP in Claude Code directly. Honestly, at this stage I don't think we even need to feed docs to it anymore. Just instructions to explore the relevant API methods for a task itself itself in the codebase.
Is there a specific reason why you implemented that as MCP and not as Skill? MCPs eat a lot of context. Depends on the implementation, of course. So dunno about how much context Octopus occupies.
ATM I have some basic instructions in CLAUDE.md that explain how to bootstrap PW and use the CLI through ddev for exploration, debugging, DB queries. That makes a big difference already. Opus is great at exploring stuff through the PHP CLI, either as one-liners or as script files for more complex stuff.
Here's my current instructions:
## PHP CLI Usage (ddev)
All PHP CLI commands **must run through ddev** to use the web container's PHP interpreter.
### Basic Commands
```bash
# Run PHP directly
ddev php script.php
# Check PHP version
ddev php --version
# Execute arbitrary command in web container
ddev exec php script.php
# Interactive shell in web container
ddev ssh
```
### ProcessWire Bootstrap
Bootstrap ProcessWire by including `./index.php` from project root. After include, full PW API is available (`$pages`, `$page`, `$config`, `$sanitizer`, etc.).
**All CLI script files must be placed in `./cli_scripts/`.**
**Inline script execution:**
```bash
ddev exec php -r "namespace ProcessWire; include('./index.php'); echo \$pages->count('template=product');"
```
**Run a PHP script:**
```bash
ddev php cli_scripts/myscript.php
```
**Example CLI script** (`cli_scripts/example.php`):
```php
<?php namespace ProcessWire;
include(__DIR__ . '/../index.php');
// PW API now available
$products = $pages->find('template=product');
foreach ($products as $p) {
echo "{$p->id}: {$p->title}\n";
}
```
### PHP CLI Usage for Debugging & Information Gathering Examples
**One-liners** — use `ddev php -r` with functions API (`pages()`, `templates()`, `modules()`) to avoid bash `$` variable expansion. Local variables still need escaping (`\$t`). Prefix output with `PHP_EOL` to separate from RockMigrations log noise:
```bash
# Count pages by template
ddev php -r "namespace ProcessWire; include('./index.php'); echo PHP_EOL.'Products: '.pages()->count('template=product');"
# Check module status
ddev php -r "namespace ProcessWire; include('./index.php'); echo PHP_EOL.(modules()->isInstalled('ProcessShop') ? 'yes' : 'no');"
# List all templates (note \$t escaping for local var)
ddev php -r "namespace ProcessWire; include('./index.php'); foreach(templates() as \$t) echo \$t->name.PHP_EOL;"
```
**Script files** — preferred for complex queries, place in `./cli_scripts/`:
```php
// cli_scripts/inspect_fields.php
<?php namespace ProcessWire;
include(__DIR__ . '/../index.php');
$p = pages()->get('/');
print_r($p->getFields()->each('name'));
```
```bash
ddev php cli_scripts/inspect_fields.php
```
### TracyDebugger in CLI
**Works in CLI:**
- `d($var, $title)` — dumps to terminal using `print_r()` for arrays/objects
- `TD::dump()` / `TD::dumpBig()` — same behavior
**Does NOT work in CLI:**
- `bd()` / `barDump()` — requires browser debug bar
**Example:**
```php
<?php namespace ProcessWire;
include(__DIR__ . '/../index.php');
$page = pages()->get('/');
d($page, 'Home page'); // outputs to terminal
d($page->getFields()->each('name'), 'Fields');
```
### Direct Database Queries
Use `database()` (returns `WireDatabasePDO`, a PDO wrapper) for raw SQL queries:
```php
<?php namespace ProcessWire;
include(__DIR__ . '/../index.php');
// Prepared statement with named parameter
$query = database()->prepare("SELECT * FROM pages WHERE template = :tpl LIMIT 5");
$query->execute(['tpl' => 'product']);
$rows = $query->fetchAll(\PDO::FETCH_ASSOC);
// Simple query
$result = database()->query("SELECT COUNT(*) FROM pages");
echo $result->fetchColumn();
```
**Key methods:**
- `database()->prepare($sql)` — prepared statement, use `:param` placeholders
- `database()->query($sql)` — direct query (no params)
- `$query->execute(['param' => $value])` — bind and execute
- `$query->fetch(\PDO::FETCH_ASSOC)` — single row
- `$query->fetchAll(\PDO::FETCH_ASSOC)` — all rows
- `$query->fetchColumn()` — single value
**Example** (`cli_scripts/query_module_data.php`):
```php
<?php namespace ProcessWire;
include(__DIR__ . '/../index.php');
$query = database()->prepare("SELECT data FROM modules WHERE class = :class");
$query->execute(['class' => 'ProcessPageListerPro']);
$row = $query->fetch(\PDO::FETCH_ASSOC);
print_r(json_decode($row['data'], true));
```
### ddev Exec Options
- `ddev exec --dir /var/www/html/site <cmd>` — run from specific directory
- `ddev exec -s db <cmd>` — run in database container
- `ddev mysql` — MySQL client access