# Analisi tecnica - Estensione `linter-node` per supporto PHP

Data: 2026-04-28
Stato: analisi tecnica aggiornata dopo implementazione del baseline sintattico PHP (`php -l`)
Ambito: supporto PHP in `linter-node`, discovery runtime PHP Windows/Ubuntu, integrazione setup/PATH, selezione versione tramite `.env` con default out-of-box.

## Sintesi

Conviene sviluppare il supporto PHP in `linter-node` in parallelo alla skill `mcp-sophia-yii-developer`, ma come milestone separata.

La skill Sophia/Yii avra bisogno di un gate di validazione PHP affidabile. Il primo livello deve essere sempre disponibile e poco invasivo: `php -l` su singolo file. Tool piu ricchi come PHP_CodeSniffer, PHPStan o Psalm devono restare opzionali, perche richiedono dipendenze progetto-specifiche, standard di coding e bootstrap autoload non uniformi.

Decisione consigliata:

1. implementare subito il baseline sintattico PHP;
2. aggiungere discovery robusta del binario PHP;
3. permettere override tramite `.env` del progetto target;
4. aggiornare setup Windows/Ubuntu per trovare PHP e, dove opportuno, aggiungerlo al `PATH`;
5. preparare estensione futura verso PHPCS/PHPStan senza bloccare la prima milestone.

Nota architetturale: la gestione PHP deve seguire lo stesso modello gia usato dal linter ColdFusion per `JAVA_BIN` e `CFLINT_JAR`. Il runtime esterno non va vendorizzato nel server MCP e non va dedotto dal cwd del server: va risolto da configurazione esplicita, `.env` del progetto target, env launcher e discovery controllata.

Allineamento richiesto anche per bootstrap configurazioni MCP: gli script `genera_mcp_json.ps1` e `genera_mcp_json.sh` devono propagare verso `linter-mcp-server` le env linter gia valorizzate (almeno `CFLINT_JAR`, `JAVA_BIN`, `CFLINT_CONFIG`, `PHP_BIN`, `PHP_VERSION_PREFERENCE`), senza imporre valori hardcoded.

## Stato attuale osservato

### `linter-node`

File rilevanti:

- `linter-node/src/index.ts`
- `linter-node/src/config.ts`
- `linter-node/src/linters/php.ts`
- `linter-node/src/linters/php-runtime.ts`
- `linter-node/src/linters/php-tooling.ts`
- `linter-node/src/linters/cflint.ts`
- `linter-node/src/linters/eslint.ts`
- `linter-node/src/linters/sql.ts`
- `linter-node/src/linters/types.ts`
- `linter-node/package.json`
- `tests/smoke/linter-node-php.smoke.mjs`

Situazione attuale:

- `lint_code` supporta realmente `.cfc`, `.cfm`, `.js`, `.ts`, `.sql`, `.php`.
- `get_lint_config` supporta realmente `php` e restituisce configurazione PHP, runtime selezionato, eventuale errore runtime actionable e scaffolding tooling opzionale.
- La descrizione di `lint_code` cita PHP.
- `index.ts` instrada `.php` verso `lintPHP()`.
- `config.ts` carica `.env` solo dal `project_path`, coerente con governance repo.
- `php.ts` esegue il baseline sintattico via `execFile`, rileva parse error, espone runtime, candidati, warning e hint actionable nella risposta e applica la policy encoding UTF-8 senza BOM.
- `php-runtime.ts` implementa discovery cross-platform con precedenza: `tool-arg` > `project-env` > `launcher-env` > `PATH` > path noti.
- `php-tooling.ts` rileva in modo non bloccante PHPCS e PHPStan da config esplicita, `vendor/bin` locale e `PATH`, senza attivarli nel flusso baseline.
- Il server usa MCP SDK low-level con `Server` + `setRequestHandler`, schema semplice e compatibile con host severi.
- L'output e solo `content` testuale JSON stringificato; non usa ancora `structuredContent`.

## Delta implementato

Alla data di questo aggiornamento, la prima milestone PHP basata sul baseline sintattico puo essere considerata implementata a livello server MCP.

Elementi completati:

1. supporto `.php` nel routing di `lint_code`;
2. risoluzione runtime PHP con version detection e warning sui candidati;
3. supporto `get_lint_config(language="php")` con dettaglio runtime;
4. risposta `lint_code` PHP arricchita con `runtime.phpPath`, `runtime.version`, `runtime.source`;
5. enforcement encoding UTF-8 senza BOM sul baseline PHP attuale;
6. smoke test dedicato per file PHP valido e invalido;
7. scaffolding di discovery per PHPCS/PHPStan esposto in `get_lint_config`, ma non ancora usato come esecutore di lint.
8. override per chiamata tool tramite `php_bin` e `php_version`;
9. errore actionable se `php_bin`/`PHP_BIN` esplicito e invalido o se `php_version` richiesto dal tool non e disponibile;
10. output PHP con `candidates`, `warnings`, `userAction` e `suggestedEnv`.

Elementi ancora da completare fuori dalla prima milestone:

1. integrazione setup `setup.ps1` e `setup.sh` per discovery PHP lato bootstrap;
2. propagazione completa delle env PHP nei generatori `genera_mcp_json.*` se non ancora riallineati;
3. integrazione esecutiva opzionale di PHPCS;
4. integrazione esecutiva opzionale di PHPStan;
5. eventuale introduzione di `structuredContent` come estensione non breaking.

### Milestone runtime override/actionable errors

La selezione runtime PHP ora supporta override diretti nella chiamata tool:

```json
{
  "language": "php",
  "project_path": "C:\\xampp183\\htdocs\\SOPHIA_framework2",
  "php_version": "5.5",
  "php_bin": "C:\\xampp183\\php\\php.exe"
}
```

Gli stessi campi sono supportati anche da `lint_code` quando il file e `.php`.

Precedenza effettiva:

1. `php_bin` / `php_version` passati al tool;
2. `.env` del `project_path`;
3. env launcher;
4. `PATH`;
5. path noti.

Regole di sicurezza:

- `php_bin` passato al tool e forte: se invalido, il tool fallisce con `userAction` e `suggestedEnv`;
- `PHP_BIN` nel `.env` progetto e forte: se invalido, il tool non fa fallback a un altro PHP;
- `php_version` passato al tool e forte: se nessun candidato combacia, il tool ritorna errore actionable con candidati trovati;
- `PHP_VERSION_PREFERENCE` nel `.env` resta preferenza morbida quando non esiste `PHP_BIN`.

Esempio Sophia legacy:

```dotenv
PHP_BIN=C:\xampp183\php\php.exe
PHP_VERSION_PREFERENCE=5.5
PHP_LINTER=syntax
```

Verifica locale eseguita su `C:\xampp183\htdocs\SOPHIA_framework2`: `get_lint_config` e `lint_code` selezionano PHP 5.5.9 con `source: "project-env"`, mentre PHP 8.3.30 resta solo candidato rilevato.

### Verifica compatibilita Windows/Ubuntu

Controlli effettuati dopo la milestone runtime override:

- discovery command cross-platform preservata: `where php` su Windows, `which php` su Unix;
- discovery path noti separata per piattaforma: Laragon/XAMPP/WAMP/Chocolatey/Scoop su Windows, `/usr/bin/php`, `/usr/local/bin/php`, `/snap/bin/php`, `/opt/php/*/bin/php` e `update-alternatives --list php` su Ubuntu/Linux;
- `execFile` usato per `php -v` e `php -l`, quindi path con spazi restano gestiti senza quoting manuale;
- `.env` del `project_path` resta letto con `dotenv.parse`, compatibile con path Windows e Unix;
- smoke test aggiornato per usare un `php_bin` invalido dipendente dalla piattaforma (`C:\...` su Windows, `/does-not-exist/php` su Unix);
- corretto matching versione: `8` accetta qualunque PHP 8.x, `8.3` accetta solo PHP 8.3.x, `8.3.30` accetta solo la versione esatta.
- setup Windows e resolver runtime aggiornati per scoprire anche directory XAMPP versionate come `C:\xampp183\php\php.exe`;
- generatori MCP Windows/Unix aggiornati per propagare tutte le env PHP del linter (`PHP_BIN`, `PHP_VERSION_PREFERENCE`, `PHP_LINTER`, PHPCS e PHPStan opzionali).

Build TypeScript e smoke MCP PHP sono stati eseguiti con esito positivo nell'ambiente Windows corrente. La copertura Ubuntu resta statica/smoke-portable: il test non hardcoda piu path Windows e la discovery Unix resta non bloccante se `update-alternatives` non esiste.

### Runtime PHP locale osservato

Nel sistema corrente e presente:

```text
C:\laragon\bin\php\php-8.3.30-Win32-vs16-x64\php.exe
```

Verifica eseguita:

```text
PHP 8.3.30 (cli) (built: Jan 13 2026 22:50:40) (ZTS Visual C++ 2019 x64)
```

Nel comando `Get-Command php` non e emerso un `php` gia risolto da `PATH` nell'output utile, quindi la discovery da path noti e necessaria.

## Obiettivi

1. Permettere a `lint_code` di accettare file `.php`.
2. Usare un baseline sintattico sempre disponibile quando esiste un binario PHP.
3. Risolvere il binario PHP in modo prevedibile:
   - override progetto;
   - override launcher;
   - `PATH`;
   - discovery path noti;
   - default out-of-box.
4. Gestire piu versioni PHP senza ambiguita silenziosa.
5. Aggiornare setup Windows e Ubuntu con discovery/registrazione PATH.
6. Restare compatibili con client MCP severi e con pattern legacy `project_path`.
7. Assicurare che la generazione dei file `settings.*` includa (quando presenti) le env del linter anche per PHP, in analogia al modello CFML.

## Non obiettivi prima milestone

- Installare automaticamente stack web completi tipo Laragon/XAMPP/WAMP.
- Imporre PHPCS/PHPStan/Psalm come prerequisito.
- Lintare interi progetti in una sola chiamata.
- Eseguire codice applicativo PHP o bootstrap Yii.
- Fare fix automatico PHP.

## Variabili `.env` consigliate

Usare nomi espliciti e stabili, analoghi a `JAVA_BIN` e `CFLINT_JAR`:

```dotenv
PHP_BIN=C:\laragon\bin\php\php-8.3.30-Win32-vs16-x64\php.exe
PHP_VERSION_PREFERENCE=8.3
PHP_LINTER=syntax
PHP_PHPCS_BIN=vendor/bin/phpcs
PHP_PHPCS_STANDARD=PSR12
PHP_PHPSTAN_BIN=vendor/bin/phpstan
PHP_PHPSTAN_LEVEL=5
```

Significato:

- `PHP_BIN`: path assoluto al binario PHP da usare. E l'equivalente PHP di `JAVA_BIN`: precedenza massima e scelta esplicita del runtime.
- `PHP_VERSION_PREFERENCE`: major/minor desiderata quando discovery trova piu runtime.
- `PHP_LINTER`: modalita, inizialmente `syntax`; valori futuri `phpcs`, `phpstan`, `all`.
- `PHP_PHPCS_BIN`: path opzionale al binario PHPCS, relativo a `project_path` o assoluto. E l'equivalente operativo di un tool esterno come `CFLINT_JAR`, ma in forma eseguibile.
- `PHP_PHPCS_STANDARD`: standard PHPCS da usare se non esiste config locale.
- `PHP_PHPSTAN_BIN`: path opzionale a PHPStan.
- `PHP_PHPSTAN_LEVEL`: livello PHPStan se non esiste config locale.

Precedenza coerente con governance:

1. `.env` del progetto target passato via `project_path`;
2. env del launcher MCP;
3. discovery runtime.

Il `.env` del repo MCP non deve diventare fonte implicita di configurazione dominio.

### Parallelo con configurazione CFML esistente

Configurazione corrente CFML in `linter-node/src/config.ts`:

```text
CFLINT_JAR -> path del tool esterno CFLint
JAVA_BIN   -> runtime Java esplicito
JAVA_HOME  -> fallback per derivare il runtime Java
```

Configurazione PHP proposta:

```text
PHP_BIN          -> runtime PHP esplicito
PHP_PHPCS_BIN    -> tool esterno PHPCS, se abilitato
PHP_PHPSTAN_BIN  -> tool esterno PHPStan, se abilitato
```

Regola comune:

1. leggere `.env` del `project_path`;
2. usare env launcher solo come bootstrap/fallback;
3. fare discovery solo se manca una configurazione esplicita;
4. restituire errore actionable se il runtime/tool richiesto non esiste;
5. non usare fix automatici o tool opzionali se non richiesti.

## Algoritmo di selezione runtime PHP

Funzione proposta:

```text
resolvePhpRuntime(projectPath?: string): PhpRuntimeResolution
```

Output strutturato interno:

```ts
interface PhpRuntimeResolution {
  phpPath: string;
  version?: string;
  source: "project-env" | "launcher-env" | "path" | "known-location";
  candidates: PhpCandidate[];
  warnings: string[];
}

interface PhpCandidate {
  path: string;
  version?: string;
  source: string;
  matchesPreference: boolean;
}
```

Precedenza consigliata:

1. `PHP_BIN` da `.env` progetto, se esiste e `php -v` funziona.
2. `PHP_BIN` da launcher env, se esiste e `php -v` funziona.
3. `php` da `PATH`.
4. Discovery path noti.
5. Se piu candidati:
   - se `PHP_VERSION_PREFERENCE` e valorizzata, scegliere match `major.minor`;
   - altrimenti scegliere la versione piu alta stabile rilevata;
   - in caso di parita, preferire `PATH` rispetto a path noti;
   - includere warning con lista candidati.

Se nessun candidato valido:

- `lint_code` deve restituire errore actionable:
  - indica di installare PHP CLI;
  - indica di impostare `PHP_BIN`;
  - su Windows cita esempi Laragon/XAMPP;
  - su Ubuntu cita `sudo apt install php-cli`;
  - non fallire con stack trace grezzo.

## Discovery Windows

Path candidati da scandire:

```text
C:\laragon\bin\php\*\php.exe
C:\xampp\php\php.exe
C:\wamp64\bin\php\php*\php.exe
C:\wamp\bin\php\php*\php.exe
C:\tools\php*\php.exe
C:\Program Files\PHP\php.exe
C:\Program Files\php*\php.exe
C:\ProgramData\chocolatey\bin\php.exe
%USERPROFILE%\scoop\apps\php\current\php.exe
```

Regola importante:

- non assumere che Laragon abbia una sola versione;
- ordinare candidati per versione reale da `php -v`, non solo per nome cartella;
- path con spazi sempre quotati;
- normalizzare path con `Resolve-Path` in setup e `path.resolve` nel server.

Caso corrente:

```text
C:\laragon\bin\php\php-8.3.30-Win32-vs16-x64\php.exe
```

Deve essere scoperto anche se `php` non e nel `PATH`.

## Discovery Ubuntu

Fonti candidati:

```text
command -v php
/usr/bin/php
/usr/local/bin/php
/snap/bin/php
/opt/php/*/bin/php
```

Con `update-alternatives`:

```bash
update-alternatives --list php
```

Se disponibile, includere tutti i candidati restituiti.

Setup Ubuntu consigliato:

- se `php` non esiste e utente richiede installazione tooling PHP:

```bash
sudo apt-get update
sudo apt-get install -y php-cli composer
```

- non forzare PPA o versioni multiple nella prima milestone;
- se servono versioni specifiche, documentare installazione manuale o demandare a `PHP_BIN`.

## Integrazione setup Windows

### Opzione proposta

Aggiungere a `setup.ps1`:

```powershell
[switch]$DiscoverPhp
[switch]$InstallPhpTooling
```

Prima milestone puo implementare solo `-DiscoverPhp`; `-InstallPhpTooling` puo restare documentato/backlog.

### Comportamento `-DiscoverPhp`

1. Cerca `php` nel `PATH`.
2. Cerca nei path noti.
3. Per ogni candidato esegue `php.exe -v`.
4. Estrae versione.
5. Se nessun candidato:
   - stampa istruzioni manuali;
   - non installa stack completo senza consenso.
6. Se un candidato:
   - aggiunge directory al `PATH` sessione e utente con `Ensure-PathEntry`;
   - stampa `PHP_BIN=<path>` suggerito.
7. Se piu candidati:
   - se esiste `PHP_VERSION_PREFERENCE`, sceglie match;
   - altrimenti propone default "versione piu alta";
   - stampa lista candidati;
   - aggiunge solo il candidato selezionato al `PATH`.

### Coerenza con setup esistente

`setup.ps1` ha gia:

- `Ensure-PathEntry`;
- `Register-ToolPathFromCandidates`;
- riepilogo capability per tooling SVG;
- conferme esplicite per installazioni automatiche.

Quindi il supporto PHP deve riusare gli stessi pattern:

- nessuna modifica distruttiva;
- update PATH utente + sessione corrente;
- riepilogo capability `php`;
- alternativa manuale chiara.

## Integrazione setup Ubuntu/macOS

### Opzione proposta

Aggiungere a `setup.sh`:

```bash
--discover-php
--install-php-tooling
```

Prima milestone consigliata:

- `--discover-php`: non installa, cerca e stampa candidati.
- `--install-php-tooling`: su Ubuntu installa `php-cli composer`; su macOS usa Homebrew se disponibile.

### PATH Ubuntu

Su Ubuntu normalmente `apt install php-cli` crea `/usr/bin/php`, gia nel `PATH`.

Per runtime custom in `/opt/php/*/bin/php`, usare helper gia presente per SVG:

```bash
ensure_user_path_entry "$php_dir"
```

Persistenza:

- aggiungere export in `~/.profile`;
- aggiornare `PATH` della sessione corrente.

## Implementazione `linter-node`

### Nuovi file

```text
linter-node/src/linters/php.ts
linter-node/src/linters/php-runtime.ts
linter-node/src/linters/php-tooling.ts
```

Stato attuale: la separazione in tre file e gia utile e giustificata. `php.ts` copre il baseline esecutivo, `php-runtime.ts` la selezione del runtime, `php-tooling.ts` la discovery non bloccante dei tool avanzati.

### Modifiche `config.ts`

Estendere `getConfig(projectPath)` mantenendo il pattern attuale:

```ts
return {
  cflint: {
    jarPath: process.env.CFLINT_JAR || "...",
    javaPath: process.env.JAVA_BIN || deriveFromJavaHomeOrDefault(),
    defaultConfigPath: process.env.CFLINT_CONFIG || null
  },
  php: {
    binPath: process.env.PHP_BIN || null,
    versionPreference: process.env.PHP_VERSION_PREFERENCE || null,
    linter: process.env.PHP_LINTER || "syntax",
    phpcsBinPath: process.env.PHP_PHPCS_BIN || null,
    phpcsStandard: process.env.PHP_PHPCS_STANDARD || null,
    phpstanBinPath: process.env.PHP_PHPSTAN_BIN || null,
    phpstanLevel: process.env.PHP_PHPSTAN_LEVEL || null
  }
};
```

Il resolver PHP deve ricevere questa config, non leggere variabili sparse in piu punti. Questo replica il modello CFML: `lintCFML()` chiama `getConfig(projectPath)` e usa `currentConfig.cflint`; `lintPHP()` dovrebbe chiamare `getConfig(projectPath)` e usare `currentConfig.php`.

### Modifiche `index.ts`

Stato implementato attuale:

- importa `lintPHP`, `resolvePhpRuntime` e `detectPhpTooling`;
- la descrizione di `lint_code` include PHP;
- il routing `.php` richiama `lintPHP(filePath, fix, projectPath)`;
- `get_lint_config(language="php")` restituisce `config`, `runtime`, `runtimeError` e `tooling`.

Descrizione aggiornata `lint_code`:

```text
Supports CFML (.cfc, .cfm), JavaScript/TypeScript (.js, .ts), SQL (.sql), and PHP (.php).
```

Routing PHP:

```ts
else if (ext === ".php") {
  const result = await lintPHP(filePath, fix, projectPath);
}
```

- L'errore di estensione non supportata include anche `.php` nella lista delle estensioni valide.

### `lintPHP`

Baseline:

```text
php -l <file>
```

Comportamento:

- `fix` ignorato per PHP nella prima milestone;
- se `fix=true`, aggiungere warning `PHP_AUTO_FIX_UNSUPPORTED`;
- controllare file esistente;
- eseguire `php -l`;
- parsare stdout/stderr;
- restituire `LintResult`.

Esempio output OK:

```json
{
  "filePath": ".../Example.php",
  "messages": [],
  "fixable": false,
  "source": "php -l",
  "runtime": {
    "phpPath": "C:\\laragon\\bin\\php\\php-8.3.30-Win32-vs16-x64\\php.exe",
    "version": "8.3.30",
    "source": "known-location"
  }
}
```

Esempio output errore:

```json
{
  "filePath": ".../Example.php",
  "messages": [
    {
      "line": 12,
      "column": 1,
      "severity": "error",
      "message": "PHP Parse error: syntax error, unexpected token ...",
      "ruleId": "php-syntax"
    }
  ],
  "fixable": false,
  "source": "php -l",
  "runtime": {
    "phpPath": "C:\\laragon\\bin\\php\\php-8.3.30-Win32-vs16-x64\\php.exe",
    "version": "8.3.30",
    "source": "known-location"
  }
}
```

Parsing minimo:

- cercare `on line (\d+)`;
- se manca line, usare line `1`;
- column `1`;
- ruleId `php-syntax`;
- severity `error`.

### Encoding PHP

Stato implementato attuale:

- PHP: UTF-8 senza BOM come default, coerente con regole globali repo.
- Se BOM presente, il baseline attuale lo tratta come errore `FILE_ENCODING_ERROR`.
- Se `fix=true`, il supporto automatico e limitato alla rimozione BOM quando possibile; il linter aggiunge comunque il messaggio informativo `PHP_AUTO_FIX_UNSUPPORTED` per chiarire che non esiste un auto-fix generale PHP.

Nota di policy: per contesti legacy Sophia/Yii questa scelta e piu severa del minimo inizialmente ipotizzato. Se emergera troppo rumore su codebase storiche, la policy potra essere riportata a warning senza toccare il contratto runtime o il flusso del baseline sintattico.

## PHPCS/PHPStan futuri

Alla data attuale esiste gia il primo strato preparatorio: `get_lint_config(language="php")` espone un blocco `tooling` con rilevazione non bloccante di mode supportate, binari configurati o trovati e file di configurazione locali. Questo consente di decidere se abilitare i tool avanzati senza impattare il baseline sintattico.

Esempio shape attesa:

```json
{
  "language": "php",
  "config": {
    "linter": "syntax"
  },
  "runtime": {
    "phpPath": "...",
    "version": "8.3.30",
    "source": "project-env"
  },
  "tooling": {
    "supportedModes": ["syntax", "phpcs", "phpstan", "all"],
    "activeMode": "syntax",
    "phpcs": {
      "configuredBinPath": "vendor/bin/phpcs",
      "resolvedBinPath": ".../vendor/bin/phpcs",
      "source": "project-local",
      "exists": true,
      "configFile": ".../phpcs.xml",
      "standard": "PSR12"
    },
    "phpstan": {
      "configuredBinPath": null,
      "resolvedBinPath": null,
      "source": null,
      "exists": false,
      "configFile": null,
      "level": null
    }
  }
}
```

### Valutazione d'uso: universale vs progetto-specifico

Il baseline sintattico PHP e utilizzabile in modo quasi universale perche verifica solo la sintassi del singolo file con il runtime PHP selezionato. Non richiede convenzioni di progetto, bootstrap, autoload o regole di stile. Il suo limite e che non copre stile, compatibilita framework, regole custom o analisi semantica profonda.

PHPCS non e universale in senso stretto. Puo essere reso quasi universale solo se usato con uno standard molto generico come PSR12, ma in pratica molti progetti legacy o framework datati hanno deviazioni deliberate, sniffer custom, esclusioni per cartelle generated, file bootstrap o naming conventions non standard. Quindi PHPCS va trattato come tool semi-portabile: puo funzionare out-of-the-box su molti progetti PHP moderni, ma produce risultati affidabili solo quando il progetto dichiara il proprio standard o accetta esplicitamente uno standard di fallback.

PHPStan e ancora meno universale. Richiede spesso conoscenza del progetto: autoload Composer, bootstrap, estensioni, stub, livello di severita, esclusioni e a volte versioni PHP precise. Senza queste regole, il rumore e alto e l'esito puo essere fuorviante. PHPStan va quindi considerato intrinsecamente progetto-specifico e opt-in.

Regola pratica consigliata:

1. baseline sintattico PHP sempre disponibile come gate minimo universale;
2. PHPCS attivabile solo se esiste config progetto o se l'utente imposta esplicitamente `PHP_LINTER=phpcs` oppure `all`;
3. PHPStan attivabile solo se esiste config progetto coerente o se l'utente lo richiede esplicitamente sapendo che serve bootstrap adeguato.

### PHPCS

Discovery config:

```text
phpcs.xml
phpcs.xml.dist
.phpcs.xml
.phpcs.xml.dist
```

Discovery binario:

```text
project_path/vendor/bin/phpcs
project_path/vendor/bin/phpcs.bat
PHP_PHPCS_BIN
PATH phpcs
```

Prima integrazione consigliata:

- solo se `PHP_LINTER=phpcs` o `PHP_LINTER=all`;
- output JSON `--report=json`;
- mappare severity in `error`/`warning`;
- non installare PHPCS globalmente di default.

Come usarlo correttamente:

1. preferire `vendor/bin/phpcs` del progetto se presente;
2. usare `PHP_PHPCS_BIN` solo come override esplicito quando il binario non e standard;
3. preferire `phpcs.xml` o `phpcs.xml.dist` del progetto rispetto a `PHP_PHPCS_STANDARD`;
4. usare `PHP_PHPCS_STANDARD` solo come fallback semplice, ad esempio `PSR12`, quando il progetto non ha configurazione propria e accetta una regola generica.

Valutazione pratica:

- utilizzabile in modo relativamente trasversale su progetti moderni solo con standard semplici;
- non veramente universale su codebase legacy, Yii 1.1 o repository con regole custom;
- buon candidato per la prima estensione esecutiva dopo il baseline sintattico, proprio perche meno dipendente dal bootstrap rispetto a PHPStan.

Dettaglio consigliato per una futura milestone opt-in:

1. gate di attivazione:
  - attivare solo se `PHP_LINTER=phpcs` o `PHP_LINTER=all`;
  - se `PHP_LINTER=syntax`, limitarsi a riportare la discovery in `get_lint_config` senza eseguire nulla;
  - se binario o config non sono disponibili, ritornare warning/actionable e fare fallback silenzioso al baseline sintattico, salvo richiesta esplicita di fallimento hard.
2. ordine di risoluzione consigliato:
  - `PHP_PHPCS_BIN` assoluto o relativo a `project_path`;
  - `project_path/vendor/bin/phpcs` o `.bat`;
  - `PATH`;
  - nessun fallback a installazioni implicite o globali non esplicite fuori da `PATH`.
3. ordine di risoluzione config:
  - `phpcs.xml`, `phpcs.xml.dist`, `.phpcs.xml`, `.phpcs.xml.dist` nel progetto;
  - in assenza di config locale, `PHP_PHPCS_STANDARD` come fallback esplicito;
  - in assenza sia di config locale sia di standard esplicito, evitare default troppo invasivi e limitarsi a suggerire `PSR12` come standard opzionale.
4. forma di esecuzione consigliata:
  - invocare il binario tramite runtime PHP risolto solo se il binario e uno script PHP e non un wrapper eseguibile;
  - preferire `execFile` con argomenti array;
  - usare `--report=json` per output stabile;
  - limitare l'esecuzione al singolo file richiesto dal tool MCP, senza estendere a directory o intero progetto.
5. mapping del risultato verso `LintResult`:
  - errori PHPCS `severity=error` -> `severity: "error"`;
  - warning PHPCS -> `severity: "warning"`;
  - usare `ruleId` con codice sniff completo quando disponibile, ad esempio `PSR12.Files.FileHeader.SpacingAfterBlock`;
  - riportare `line` e `column` se presenti, altrimenti `column=1`;
  - `source` distinto, ad esempio `phpcs` o `phpcs+php-l` se viene combinato col baseline.
6. strategia di composizione col baseline:
  - anche se PHPCS e attivo, continuare a eseguire prima il controllo sintattico di base, per avere un gate chiaro e messaggi piu diagnostici in caso di parse error;
  - se il controllo sintattico di base fallisce, e ragionevole saltare PHPCS e restituire solo il problema sintattico;
  - se il controllo sintattico di base passa e PHPCS fallisce per configurazione mancante o standard invalido, restituire warning operativo separato dal risultato di lint sintattico.
7. failure mode da prevedere in analisi:
  - binario trovato ma non eseguibile sulla piattaforma corrente;
  - standard richiesto ma non installato;
  - regole custom che dipendono da package Composer non presenti;
  - wrapper `.bat` Windows che richiede quoting corretto;
  - output non JSON in caso di crash del binario.
8. segnali da riportare in `get_lint_config` quando questa milestone verra implementata:
  - `canRun: boolean` per PHPCS;
  - motivo bloccante se `canRun=false`;
  - config file effettivamente selezionato;
  - standard effettivo selezionato;
  - eventuale necessita di dipendenze Composer locali.

### PHPStan

Discovery config:

```text
phpstan.neon
phpstan.neon.dist
```

Discovery binario:

```text
project_path/vendor/bin/phpstan
project_path/vendor/bin/phpstan.bat
PHP_PHPSTAN_BIN
PATH phpstan
```

Avvertenza:

- PHPStan puo richiedere autoload, bootstrap, estensioni e versioni PHP compatibili;
- usarlo solo su richiesta esplicita o config rilevata;
- non bloccare il baseline sintattico.

Come usarlo correttamente:

1. preferire `vendor/bin/phpstan` del progetto se presente;
2. eseguire solo se esiste `phpstan.neon` o `phpstan.neon.dist`, oppure se l'utente imposta esplicitamente `PHP_PHPSTAN_BIN` e il livello desiderato;
3. considerare `PHP_PHPSTAN_LEVEL` solo come override minimale, non come sostituto di una configurazione progetto;
4. trattare errori di bootstrap o autoload come problemi di setup progetto, non del baseline linter.

Valutazione pratica:

- non adatto come strumento universale;
- utile solo in progetti con Composer/autoload e configurazione gia disciplinata;
- da integrare come fase opzionale separata, con fallback automatico al solo baseline sintattico in assenza di prerequisiti.

Dettaglio consigliato per una futura milestone opt-in:

1. gate di attivazione:
  - attivare solo se `PHP_LINTER=phpstan` o `PHP_LINTER=all`;
  - richiedere almeno una tra config locale, binario esplicito o binario locale di progetto;
  - evitare qualsiasi attivazione implicita solo perche `phpstan` e nel `PATH`, a meno che l'utente abbia richiesto espressamente il mode.
2. prerequisiti minimi consigliati:
  - `vendor/autoload.php` o bootstrap equivalente quando richiesto dal progetto;
  - `phpstan.neon` o `phpstan.neon.dist` nel progetto, oppure configurazione esplicita nota;
  - compatibilita tra runtime PHP selezionato e dipendenze usate da PHPStan;
  - progetto in stato sufficientemente installato, non solo sorgenti isolate.
3. ordine di risoluzione binario:
  - `PHP_PHPSTAN_BIN` assoluto o relativo a `project_path`;
  - `project_path/vendor/bin/phpstan` o `.bat`;
  - `PATH` solo se il mode e stato richiesto esplicitamente.
4. ordine di risoluzione config:
  - `phpstan.neon`, `phpstan.neon.dist`;
  - eventuale config passata dal progetto tramite include interno a PHPStan;
  - `PHP_PHPSTAN_LEVEL` come override leggero solo se non confligge con config progetto;
  - nessun tentativo di costruire una config sintetica troppo aggressiva in prima integrazione.
5. forma di esecuzione consigliata:
  - eseguire sul solo file richiesto dal tool quando possibile, ma documentare che PHPStan produce risultati piu affidabili a livello progetto;
  - usare output JSON o formato machine-readable stabile supportato dalla versione selezionata;
  - prevedere timeout piu conservativi rispetto al baseline sintattico e a PHPCS;
  - eseguire nella root progetto per preservare include path, autoload e config relative.
6. mapping del risultato verso `LintResult`:
  - errori di analisi -> `severity: "error"`;
  - problemi di configurazione/bootstrap -> meglio trattarli come warning o runtime issue separata, non come finding sul file sorgente;
  - usare `ruleId` derivato dall'identificatore PHPStan se disponibile, altrimenti namespace coerente come `phpstan-analysis`;
  - preservare file, linea e messaggio originale, evitando normalizzazioni che nascondano il motivo reale del fallimento.
7. strategia di composizione col baseline:
  - il baseline sintattico resta sempre il primo controllo;
  - PHPStan va eseguito solo dopo il successo sintattico;
  - in `PHP_LINTER=all`, l'ordine pragmatico e baseline sintattico -> PHPCS -> PHPStan, cosi da fermarsi prima sui problemi piu economici e meno dipendenti dal bootstrap.
8. failure mode da prevedere in analisi:
  - autoload Composer assente o incompleto;
  - config che include file mancanti;
  - estensioni PHP richieste ma non caricate nel runtime selezionato;
  - livello troppo severo rispetto al progetto legacy;
  - crash o memory limit del processo;
  - output machine-readable differente tra versioni PHPStan.
9. segnali da riportare in `get_lint_config` quando questa milestone verra implementata:
  - `canRun: boolean` per PHPStan;
  - prerequisiti mancanti distinti tra binario, config, autoload, bootstrap;
  - file config effettivo;
  - livello effettivo;
  - nota esplicita se il progetto appare non pronto per analisi statica affidabile.

### Disegno consigliato delle milestone future

Per evitare regressioni e mantenere chiaro il confine con la prima milestone, il percorso futuro puo essere dettagliato cosi:

1. milestone futura A, solo osservabilita avanzata:
  - estendere `get_lint_config(language="php")` con campi come `canRun`, `blockingReason`, `selectedConfig`, `selectedStandard`, `selectedLevel`;
  - non eseguire ancora PHPCS/PHPStan in `lint_code`.
2. milestone futura B, PHPCS opt-in esecutivo:
  - eseguire PHPCS solo su richiesta esplicita;
  - mantenere fallback al baseline sintattico quando PHPCS non e pronto;
  - introdurre smoke dedicati con progetto fittizio dotato di `phpcs.xml`.
3. milestone futura C, PHPStan opt-in esecutivo:
  - attivazione solo su progetto realmente bootstrap-ready;
  - smoke dedicati separati dal baseline per evitare flakiness;
  - nessun blocking della baseline se PHPStan non puo partire.

### Criteri decisionali per l'uso reale nei progetti

Quando in futuro si decidera se attivare uno dei due tool su un progetto concreto, la decisione puo seguire questa matrice pratica:

1. usare solo il baseline sintattico se:
  - il progetto e legacy o poco standardizzato;
  - non esistono config PHPCS/PHPStan;
  - manca Composer installato o il bootstrap non e riproducibile;
  - serve solo un gate rapido e stabile per syntax safety.
2. attivare PHPCS se:
  - il progetto ha `phpcs.xml` o accetta uno standard fallback esplicito;
  - il rumore atteso e gestibile;
  - si vuole enforcement di stile/convenzioni oltre alla sintassi.
3. attivare PHPStan se:
  - il progetto ha bootstrap/autoload affidabile;
  - esiste una config mantenuta;
  - il team vuole analisi semantica e puo assorbire il costo di tuning.
4. attivare `all` solo se:
  - il progetto e gia disciplinato;
  - il tempo di esecuzione extra e accettabile;
  - il team accetta che i tool avanzati restino opt-in e non baseline universale.

## Contratto MCP e compatibilita

Mantenere tool esistenti:

- `lint_code`
- `get_lint_config`

Non introdurre nuovo tool obbligatorio nella prima milestone.

Possibile estensione futura:

```text
discover_lint_runtimes
```

La milestone attuale evita nuova superficie MCP e usa `get_lint_config(language="php")` per riportare:

- PHP selezionato;
- versione;
- source;
- candidati;
- configurazioni PHPCS/PHPStan trovate;
- warning.

Nota aggiornata: `get_lint_config` per PHP e implementato e oggi rappresenta gia il punto di osservabilita corretto sia per il runtime selezionato sia per la futura attivazione controllata di PHPCS/PHPStan.

## Output e `structuredContent`

Il server oggi restituisce solo JSON stringificato in `content`.

Per compatibilita immediata:

- mantenere output attuale.

Stato attuale: il risultato `lint_code` PHP include gia il blocco `runtime`, mentre `structuredContent` non e ancora stato introdotto.

Per modernizzazione successiva:

- aggiungere `structuredContent` con shape stabile:

```json
{
  "filePath": "...",
  "language": "php",
  "messages": [],
  "fixable": false,
  "runtime": {
    "phpPath": "...",
    "version": "8.3.30",
    "source": "known-location"
  }
}
```

Questo e utile per host moderni, ma va trattato come estensione non breaking.

## Test e smoke

### Unit/smoke minimi

File temporanei:

- `valid.php`: `<?php echo "ok";`
- `invalid.php`: `<?php function broken( {`

Verifiche:

1. `lint_code(valid.php)` restituisce `messages=[]`.
2. `lint_code(invalid.php)` restituisce almeno un errore `php-syntax`.
3. `PHP_BIN` esplicito funziona.
4. `PHP_BIN` mancante ma PHP in `PATH` funziona.
5. Windows: discovery trova Laragon path.
6. Ubuntu: discovery trova `command -v php`.
7. Se nessun PHP, errore actionable.
8. `lint_code(valid.php)` include `runtime.version`.
9. `get_lint_config(php)` include blocco `tooling` con mode supportate e detection non bloccante.

### Smoke MCP

Estendere `tests/smoke/linter-node.smoke.mjs` oppure aggiungere test dedicato:

```text
tests/smoke/linter-node-php.smoke.mjs
```

Il test deve saltare con messaggio chiaro se PHP non e disponibile, oppure usare discovery se presente.

### Build

Verifica minima:

```bash
cd linter-node
npm run build
node ../tests/smoke/linter-node.smoke.mjs
```

## Integrazione con `mcp-sophia-yii-developer`

La skill Sophia dovrebbe dichiarare:

- eseguire `lint_code` su file `.php` prima di consegna quando disponibile;
- usare `project_path` per caricare `.env` del progetto Sophia;
- se progetto richiede PHP specifico, impostare `PHP_BIN` o `PHP_VERSION_PREFERENCE`;
- fallback temporaneo: se PHP non disponibile, documentare gap e non dichiarare validazione completa.

Per Sophia/Yii 1.1, il baseline sintattico e sufficiente come gate minimo per syntax safety. PHPCS/PHPStan vanno abilitati solo se il progetto dispone di config o se richiesti, per evitare falsi positivi massivi su legacy.

## Rischi e mitigazioni

| Rischio | Impatto | Mitigazione |
| --- | --- | --- |
| Piu versioni PHP installate | Lint con runtime sbagliato | `PHP_BIN` > `PHP_VERSION_PREFERENCE` > versione piu alta, warning candidati |
| PHP non in `PATH` su Windows | Lint inutilizzabile out-of-box | Discovery Laragon/XAMPP/WAMP/Scoop/Chocolatey e setup `-DiscoverPhp` |
| Setup modifica PATH in modo inatteso | Side effect ambiente utente | Flag opt-in e riepilogo esplicito |
| PHPStan/PHPCS rumorosi su legacy | Falsi positivi, blocco skill | Prima milestone solo baseline sintattico; tool avanzati opt-in |
| `.env` sbagliato | Runtime dominio errato | Usare solo `.env` del `project_path`, non repo MCP cwd |
| Path con spazi | Comandi falliscono | `execFile` invece di shell string dove possibile |
| Command injection | Rischio sicurezza | Usare `child_process.execFile` con argomenti array, non concatenazione shell |

## Raccomandazione implementativa

Priorita 1:

1. Estendere `config.ts` con blocco `php`, analogo a `cflint`.
2. `php-runtime.ts` con discovery cross-platform e precedenza `PHP_BIN`.
3. `php.ts` con baseline sintattico via `execFile`.
4. routing `.php` in `index.ts`.
5. `get_lint_config("php")` con runtime selezionato e candidati.
6. smoke test valid/invalid PHP.

Priorita 2:

1. `setup.ps1 -DiscoverPhp`.
2. `setup.sh --discover-php`.
3. README: sezione PHP runtime e `.env`.
4. aggiornamento `docs/server-capability-matrix.md`.

Priorita 3:

1. PHPCS opt-in.
2. PHPStan opt-in.
3. `structuredContent` per risultati lint.

Ordine consigliato aggiornato per i tool avanzati:

1. esecuzione PHPCS opt-in, perche piu semplice e meno dipendente dal bootstrap progetto;
2. warning/actionability migliore in `get_lint_config` quando config e binari sono incoerenti;
3. esecuzione PHPStan opt-in solo dopo aver definito bene le condizioni minime di progetto supportate.

## Conclusione

Il supporto PHP in `linter-node` e gia sufficientemente maturo per fungere da gate minimo affidabile per `mcp-sophia-yii-developer`: il baseline sintattico e disponibile, il runtime viene risolto in modo robusto e il server espone gia osservabilita utile per future estensioni. La direzione corretta resta mantenere universale solo il baseline sintattico, trattare PHPCS come estensione semi-portabile e PHPStan come integrazione esplicitamente progetto-specifica. In questo modo si evita di bloccare la prima milestone e si prepara un percorso di crescita coerente con repository legacy e progetti moderni.
