phpstan-mcp-mx 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/README.md +3 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/phpstan/error.d.ts +8 -0
- package/dist/phpstan/error.js +9 -0
- package/dist/phpstan/error.js.map +1 -0
- package/dist/phpstan/output.d.ts +57 -0
- package/dist/phpstan/output.js +76 -0
- package/dist/phpstan/output.js.map +1 -0
- package/dist/phpstan/process.d.ts +45 -0
- package/dist/phpstan/process.js +148 -0
- package/dist/phpstan/process.js.map +1 -0
- package/dist/phpstan/queue.d.ts +21 -0
- package/dist/phpstan/queue.js +76 -0
- package/dist/phpstan/queue.js.map +1 -0
- package/dist/phpstan.d.ts +7 -65
- package/dist/phpstan.js +8 -289
- package/dist/phpstan.js.map +1 -1
- package/dist/server.js +4 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/analysis.d.ts +8 -0
- package/dist/tools/analysis.js +131 -0
- package/dist/tools/analysis.js.map +1 -0
- package/dist/tools/inspect.d.ts +14 -0
- package/dist/tools/inspect.js +218 -0
- package/dist/tools/inspect.js.map +1 -0
- package/dist/tools/prompts.d.ts +3 -0
- package/dist/tools/prompts.js +45 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/shared.d.ts +160 -0
- package/dist/tools/shared.js +219 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/writes.d.ts +17 -0
- package/dist/tools/writes.js +131 -0
- package/dist/tools/writes.js.map +1 -0
- package/dist/tools.d.ts +0 -11
- package/dist/tools.js +4 -671
- package/dist/tools.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +14 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Wijzigingen
|
|
2
|
+
|
|
3
|
+
Alle noemenswaardige wijzigingen per versie. Vanaf 0.10.0 gegenereerd door
|
|
4
|
+
semantic-release uit de commit-historie; de versies daarvoor zijn met de hand
|
|
5
|
+
geschreven.
|
|
6
|
+
|
|
7
|
+
# [0.10.0](https://codeberg.org/MartijnSchimmel/phpstan-mcp-mx/compare/v0.9.0...v0.10.0) (2026-09-10)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* log ook de diagnose en de tools die iets veranderen ([cf76a9e](https://codeberg.org/MartijnSchimmel/phpstan-mcp-mx/commit/cf76a9e136259059ae7db87112a3d869fe43619b))
|
|
13
|
+
|
|
14
|
+
## 0.9.0
|
|
15
|
+
|
|
16
|
+
- Logmeldingen naar de client bij het starten en afronden van een analyse, met duur,
|
|
17
|
+
bevindingen en waarschuwingen. De server declareert daarvoor de `logging`-capability.
|
|
18
|
+
- `tools.ts` en `phpstan.ts` opgesplitst per verantwoordelijkheid. Geen gedragswijziging: de
|
|
19
|
+
dekking is voor en na identiek.
|
|
20
|
+
- Testdekking wordt gemeten (`npm run coverage`) met drempels die een terugval opvangen.
|
|
21
|
+
- zod bijgewerkt naar 4.6.1.
|
|
22
|
+
|
|
23
|
+
## 0.8.0
|
|
24
|
+
|
|
25
|
+
- Nieuwe tool `server_config`: met welke instellingen deze server draait — roots, standaard
|
|
26
|
+
geheugenlimiet, hoeveel analyses tegelijk, waar de tijdelijke bestanden heen gaan en of
|
|
27
|
+
schrijvende tools aanstaan. Een geweigerde instelling wordt gemeld in plaats van
|
|
28
|
+
verzwegen.
|
|
29
|
+
|
|
30
|
+
## 0.7.1
|
|
31
|
+
|
|
32
|
+
- README noemt de CI-opstelling. Eerste release die vanuit CI gepubliceerd is.
|
|
33
|
+
|
|
34
|
+
## 0.7.0
|
|
35
|
+
|
|
36
|
+
- Elk project krijgt zijn eigen tijdelijke map, zodat PHPStans resultcache per project blijft
|
|
37
|
+
werken. Zonder dit deelden alle projecten één cache en maakten ze die van elkaar ongeldig.
|
|
38
|
+
Terug te zetten met `PHPSTAN_MCP_TMPDIR=shared`.
|
|
39
|
+
|
|
40
|
+
## 0.6.0
|
|
41
|
+
|
|
42
|
+
- Nieuwe tool `clear_result_cache`.
|
|
43
|
+
- Nieuwe tool `generate_baseline`, alleen beschikbaar als de server met
|
|
44
|
+
`PHPSTAN_MCP_ALLOW_WRITES=1` gestart is. Zonder die vlag wordt hij niet geregistreerd.
|
|
45
|
+
|
|
46
|
+
## 0.5.0
|
|
47
|
+
|
|
48
|
+
- Nieuwe tool `diagnose_project`: PHPStans eigen opstelling in een project.
|
|
49
|
+
- `list_projects` meldt per project of het onder git staat.
|
|
50
|
+
- Een analyse waarschuwt als er een baseline meedoet; een schone uitslag is dan geen schone
|
|
51
|
+
code.
|
|
52
|
+
- Nieuwe prompt met het werkpatroon om je eigen wijziging na te kijken.
|
|
53
|
+
- `analyse_changed` kent `staged`, voor een controle vlak vóór een commit.
|
|
54
|
+
- PHPStan wordt gestart met `PAO_DISABLE=1`, zodat `laravel/pao` de uitvoer niet vervangt.
|
|
55
|
+
|
|
56
|
+
## 0.4.0
|
|
57
|
+
|
|
58
|
+
- `PHPSTAN_MCP_MAX_PARALLEL` begrenst hoeveel analyses er over projecten heen tegelijk
|
|
59
|
+
draaien (standaard 2).
|
|
60
|
+
- Een geannuleerd verzoek breekt de analyse af in plaats van door te rekenen.
|
|
61
|
+
|
|
62
|
+
## 0.3.1
|
|
63
|
+
|
|
64
|
+
- Bij een timeout wordt de procesgroep nu ook op Windows opgeruimd.
|
|
65
|
+
|
|
66
|
+
## 0.3.0
|
|
67
|
+
|
|
68
|
+
- Een analyse meldt het als PHPStan zegt dat het resultaat onvolledig is. Die waarschuwing
|
|
69
|
+
ging eerder verloren.
|
|
70
|
+
- Waarschuwingen en algemene fouten worden begrensd op tien meldingen van elk hoogstens 1000
|
|
71
|
+
tekens.
|
|
72
|
+
|
|
73
|
+
## 0.2.0
|
|
74
|
+
|
|
75
|
+
- Nieuwe tool `analyse_changed`: alleen de PHP-bestanden die volgens git gewijzigd zijn.
|
|
76
|
+
- De analysetools kennen `config`, voor een ander configbestand binnen het project.
|
|
77
|
+
- De analysetools kennen `identifiers`, om de bevindingen tot één soort te beperken.
|
|
78
|
+
- `PHPSTAN_MCP_MEMORY_LIMIT` zet de standaard geheugenlimiet.
|
|
79
|
+
|
|
80
|
+
## 0.1.1
|
|
81
|
+
|
|
82
|
+
- README beschrijft de PATH-valkuil bij het registreren van de server.
|
|
83
|
+
|
|
84
|
+
## 0.1.0
|
|
85
|
+
|
|
86
|
+
- Eerste publicatie: `list_projects`, `analyse_project`, `analyse_file` en
|
|
87
|
+
`baseline_summary`.
|
package/README.md
CHANGED
|
@@ -521,9 +521,9 @@ npm test
|
|
|
521
521
|
npm run inspect # MCP Inspector tegen de gebouwde server
|
|
522
522
|
```
|
|
523
523
|
|
|
524
|
-
Dezelfde vier stappen draaien in CI op Node
|
|
525
|
-
|
|
526
|
-
|
|
524
|
+
Dezelfde vier stappen draaien in CI op Node 24, en nog een keer bij een release. `engines`
|
|
525
|
+
belooft `>=20`; die ondergrens is bij het opzetten van CI eenmalig in Docker nagelopen en
|
|
526
|
+
draait niet mee bij elke build.
|
|
527
527
|
|
|
528
528
|
## Licentie
|
|
529
529
|
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { loadDefaultMemoryLimit, loadMaxParallel } from './phpstan.js';
|
|
4
4
|
import { loadTmpDirMode } from './tmpdir.js';
|
|
5
|
-
import { WRITES_ENV_VAR, writesAllowed } from './tools.js';
|
|
5
|
+
import { WRITES_ENV_VAR, writesAllowed } from './tools/writes.js';
|
|
6
6
|
import { loadRoots } from './roots.js';
|
|
7
7
|
import { createServer } from './server.js';
|
|
8
8
|
import { VERSION } from './version.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAEnC,8EAA8E;IAC9E,kEAAkE;IAClE,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IAEjD,OAAO,CAAC,KAAK,CACX,eAAe,OAAO,uBAAuB,KAAK,CAAC,MAAM,yBAAyB;QAChF,qBAAqB,EAAE;QACvB,mBAAmB,EAAE;QACrB,cAAc,EAAE;QAChB,cAAc,EAAE,CACnB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH;;;GAGG;AACH,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,OAAO,cAAc,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,aAAa,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/E,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc;IACrB,IAAI,aAAa,EAAE;QAAE,OAAO,yBAAyB,CAAC;IAEtD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,CAAC;IACvD,OAAO,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,EAAE;QAClD,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,aAAa,cAAc,cAAc,UAAU,8CAA8C,CAAC;AACxG,CAAC;AAED,sEAAsE;AACtE,SAAS,mBAAmB;IAC1B,IAAI,CAAC;QACH,OAAO,eAAe,MAAM,CAAC,eAAe,EAAE,CAAC,WAAW,CAAC;IAC7D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,aAAa,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB;IAC5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,sBAAsB,EAAE,CAAC;QACvC,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,oBAAoB,KAAK,EAAE,CAAC;IAChE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,aAAa,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;IACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fout die als bruikbare melding naar het model teruggaat, niet als crash.
|
|
3
|
+
*
|
|
4
|
+
* Staat in een eigen module omdat zowel phpstan.ts als de queue hem gooit, en
|
|
5
|
+
* queue.ts importeren vanuit phpstan.ts zou een cyclus zijn.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PhpstanError extends Error {
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fout die als bruikbare melding naar het model teruggaat, niet als crash.
|
|
3
|
+
*
|
|
4
|
+
* Staat in een eigen module omdat zowel phpstan.ts als de queue hem gooit, en
|
|
5
|
+
* queue.ts importeren vanuit phpstan.ts zou een cyclus zijn.
|
|
6
|
+
*/
|
|
7
|
+
export class PhpstanError extends Error {
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/phpstan/error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;CAAG"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const outputSchema: z.ZodObject<{
|
|
3
|
+
totals: z.ZodObject<{
|
|
4
|
+
errors: z.ZodNumber;
|
|
5
|
+
file_errors: z.ZodNumber;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
files: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
8
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
9
|
+
message: z.ZodString;
|
|
10
|
+
line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
11
|
+
identifier: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
12
|
+
tip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
13
|
+
}, z.core.$strip>>;
|
|
14
|
+
}, z.core.$strip>>;
|
|
15
|
+
errors: z.ZodArray<z.ZodString>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
type PhpstanOutput = z.infer<typeof outputSchema>;
|
|
18
|
+
/**
|
|
19
|
+
* Leest PHPStans JSON, of null als het geen bruikbare JSON is. Normaliseert
|
|
20
|
+
* `files: []` naar `{}` zodat het schema streng kan blijven; PHP codeert een lege
|
|
21
|
+
* associatieve array nu eenmaal als JSON-array.
|
|
22
|
+
*/
|
|
23
|
+
export declare function tryParse(stdout: string): PhpstanOutput | null;
|
|
24
|
+
/**
|
|
25
|
+
* Haalt uit stderr wat het model moet weten bij een gelúkte analyse. Dit werd eerder
|
|
26
|
+
* helemaal weggegooid zodra de JSON parseerde — en juist daar staat PHPStans
|
|
27
|
+
* "Result is incomplete because of severe errors.": geldige JSON die het project niet
|
|
28
|
+
* volledig dekt. Zonder deze regels leest een model een schone uitslag die dat niet is.
|
|
29
|
+
*/
|
|
30
|
+
export declare function collectWarnings(stderr: string): string[];
|
|
31
|
+
/** Zet PHPStans uitvoer om in het interne resultaat, met de waarschuwingen erbij. */
|
|
32
|
+
export declare function toResult(project: string, output: PhpstanOutput, warnings: readonly string[]): AnalysisResult;
|
|
33
|
+
/** Eén bevinding van PHPStan, gekoppeld aan een bestand. */
|
|
34
|
+
export interface Finding {
|
|
35
|
+
/** Absoluut pad zoals PHPStan het rapporteert. */
|
|
36
|
+
readonly file: string;
|
|
37
|
+
readonly line: number | null;
|
|
38
|
+
readonly message: string;
|
|
39
|
+
/** Bijv. "argument.type"; ontbreekt bij oudere PHPStan-versies. */
|
|
40
|
+
readonly identifier: string | null;
|
|
41
|
+
/** PHPStans eigen hint bij deze fout, als die er is. */
|
|
42
|
+
readonly tip: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** Uitkomst van een geslaagde analyse — ook als er fouten gevonden zijn. */
|
|
45
|
+
export interface AnalysisResult {
|
|
46
|
+
readonly project: string;
|
|
47
|
+
readonly findings: readonly Finding[];
|
|
48
|
+
/** Fouten die niet aan een bestand hangen, zoals configuratieproblemen. */
|
|
49
|
+
readonly generalErrors: readonly string[];
|
|
50
|
+
/** Wat PHPStan naast de JSON op stderr zei; leeg als er niets bijzonders was. */
|
|
51
|
+
readonly warnings: readonly string[];
|
|
52
|
+
readonly totals: {
|
|
53
|
+
readonly fileErrors: number;
|
|
54
|
+
readonly generalErrors: number;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const findingSchema = z.object({
|
|
3
|
+
message: z.string(),
|
|
4
|
+
line: z.number().nullable().optional(),
|
|
5
|
+
identifier: z.string().nullable().optional(),
|
|
6
|
+
tip: z.string().nullable().optional(),
|
|
7
|
+
});
|
|
8
|
+
const outputSchema = z.object({
|
|
9
|
+
totals: z.object({ errors: z.number(), file_errors: z.number() }),
|
|
10
|
+
files: z.record(z.string(), z.object({ messages: z.array(findingSchema) })),
|
|
11
|
+
errors: z.array(z.string()),
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Leest PHPStans JSON, of null als het geen bruikbare JSON is. Normaliseert
|
|
15
|
+
* `files: []` naar `{}` zodat het schema streng kan blijven; PHP codeert een lege
|
|
16
|
+
* associatieve array nu eenmaal als JSON-array.
|
|
17
|
+
*/
|
|
18
|
+
export function tryParse(stdout) {
|
|
19
|
+
if (stdout.trim() === '')
|
|
20
|
+
return null;
|
|
21
|
+
let raw;
|
|
22
|
+
try {
|
|
23
|
+
raw = JSON.parse(stdout);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
// PHP codeert een lege associatieve array als [], niet als {}. Een project
|
|
29
|
+
// zonder bevindingen levert dus "files": [] op, wat het schema anders afkeurt.
|
|
30
|
+
if (typeof raw === 'object' && raw !== null && Array.isArray(raw.files)) {
|
|
31
|
+
raw.files = {};
|
|
32
|
+
}
|
|
33
|
+
const parsed = outputSchema.safeParse(raw);
|
|
34
|
+
return parsed.success ? parsed.data : null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Regels op stderr die geen waarschuwing zijn. Bewust een lijst van wat ongevaarlijk
|
|
38
|
+
* is en niet van wat alarmerend is: PHPStan kan meldingen introduceren die we hier
|
|
39
|
+
* niet kennen, en die stil laten vallen is precies de fout die dit moet voorkomen.
|
|
40
|
+
*/
|
|
41
|
+
const HARMLESS_STDERR = [/^Note: /];
|
|
42
|
+
/**
|
|
43
|
+
* Haalt uit stderr wat het model moet weten bij een gelúkte analyse. Dit werd eerder
|
|
44
|
+
* helemaal weggegooid zodra de JSON parseerde — en juist daar staat PHPStans
|
|
45
|
+
* "Result is incomplete because of severe errors.": geldige JSON die het project niet
|
|
46
|
+
* volledig dekt. Zonder deze regels leest een model een schone uitslag die dat niet is.
|
|
47
|
+
*/
|
|
48
|
+
export function collectWarnings(stderr) {
|
|
49
|
+
return stderr
|
|
50
|
+
.split('\n')
|
|
51
|
+
.map((line) => line.trim())
|
|
52
|
+
.filter((line) => line !== '' && !HARMLESS_STDERR.some((pattern) => pattern.test(line)));
|
|
53
|
+
}
|
|
54
|
+
/** Zet PHPStans uitvoer om in het interne resultaat, met de waarschuwingen erbij. */
|
|
55
|
+
export function toResult(project, output, warnings) {
|
|
56
|
+
const findings = [];
|
|
57
|
+
for (const [file, entry] of Object.entries(output.files)) {
|
|
58
|
+
for (const message of entry.messages) {
|
|
59
|
+
findings.push({
|
|
60
|
+
file,
|
|
61
|
+
line: message.line ?? null,
|
|
62
|
+
message: message.message,
|
|
63
|
+
identifier: message.identifier ?? null,
|
|
64
|
+
tip: message.tip ?? null,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
project,
|
|
70
|
+
findings,
|
|
71
|
+
generalErrors: output.errors,
|
|
72
|
+
warnings,
|
|
73
|
+
totals: { fileErrors: output.totals.file_errors, generalErrors: output.totals.errors },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/phpstan/output.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Waarom een run gestopt is, als hij niet zelf klaar was. */
|
|
2
|
+
type StopReason = 'timeout' | 'geannuleerd';
|
|
3
|
+
export interface Execution {
|
|
4
|
+
readonly code: number | null;
|
|
5
|
+
readonly signal: NodeJS.Signals | null;
|
|
6
|
+
/** Null als het proces gewoon zelf klaar was. */
|
|
7
|
+
readonly reason: StopReason | null;
|
|
8
|
+
readonly stdout: string;
|
|
9
|
+
readonly stderr: string;
|
|
10
|
+
readonly spawnError: NodeJS.ErrnoException | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Start PHPStan zonder shell. Bewust spawn en niet execFile: die laatste kapt de
|
|
14
|
+
* output af op maxBuffer (1 MB), en de JSON van een groot project gaat daar
|
|
15
|
+
* ruim overheen.
|
|
16
|
+
*
|
|
17
|
+
* `detached: true` maakt het proces leider van een eigen procesgroep, zodat een
|
|
18
|
+
* timeout de héle groep kan doden. Zonder dat blijven PHPStans parallelle workers
|
|
19
|
+
* na een timeout gewoon doorrekenen.
|
|
20
|
+
*/
|
|
21
|
+
export declare function execute(binary: string, args: readonly string[], cwd: string, timeoutMs: number, signal: AbortSignal | undefined): Promise<Execution>;
|
|
22
|
+
/** Hoe een procesgroep op dit platform opgeruimd wordt. */
|
|
23
|
+
export type KillPlan = {
|
|
24
|
+
readonly kind: 'signaal';
|
|
25
|
+
readonly target: number;
|
|
26
|
+
readonly signal: NodeJS.Signals;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: 'commando';
|
|
29
|
+
readonly command: string;
|
|
30
|
+
readonly args: readonly string[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Bepaalt hoe de procesgroep van `pid` gedood wordt. Als plan teruggegeven en niet
|
|
34
|
+
* meteen uitgevoerd, want anders is de Windows-tak niet te testen op een machine
|
|
35
|
+
* waar je hem niet kunt uitvoeren — en juist die tak was stuk.
|
|
36
|
+
*/
|
|
37
|
+
export declare function planKill(pid: number, signal: NodeJS.Signals, platform?: NodeJS.Platform): KillPlan;
|
|
38
|
+
/**
|
|
39
|
+
* Of een mislukte kill het melden waard is. `ESRCH` betekent dat de groep al weg
|
|
40
|
+
* is — de race tussen de timer en deze aanroep, en precies de gewenste uitkomst.
|
|
41
|
+
* Alles anders wél melden: de vorige versie slikte élke fout en verborg daarmee dat
|
|
42
|
+
* een negatieve pid op Windows niet eens bestaat.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isReportableKillError(error: unknown): boolean;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { tmpDirEnv } from '../tmpdir.js';
|
|
3
|
+
/** Hoe lang een gedode procesgroep de tijd krijgt voordat SIGKILL volgt. */
|
|
4
|
+
const KILL_GRACE_MS = 5_000;
|
|
5
|
+
/**
|
|
6
|
+
* Start PHPStan zonder shell. Bewust spawn en niet execFile: die laatste kapt de
|
|
7
|
+
* output af op maxBuffer (1 MB), en de JSON van een groot project gaat daar
|
|
8
|
+
* ruim overheen.
|
|
9
|
+
*
|
|
10
|
+
* `detached: true` maakt het proces leider van een eigen procesgroep, zodat een
|
|
11
|
+
* timeout de héle groep kan doden. Zonder dat blijven PHPStans parallelle workers
|
|
12
|
+
* na een timeout gewoon doorrekenen.
|
|
13
|
+
*/
|
|
14
|
+
export function execute(binary, args, cwd, timeoutMs, signal) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
// Geen `timeout`-optie van spawn: die doodt alleen het proces zelf.
|
|
17
|
+
const child = spawn(binary, args, {
|
|
18
|
+
cwd,
|
|
19
|
+
shell: false,
|
|
20
|
+
detached: true,
|
|
21
|
+
// PAO_DISABLE hoort bij --no-progress en --no-ansi: allemaal om PHPStans
|
|
22
|
+
// eigen, machineleesbare uitvoer te krijgen. laravel/pao hangt zich via
|
|
23
|
+
// autoload.files in de autoloader van het project, ziet aan variabelen als
|
|
24
|
+
// CLAUDECODE dat er een agent draait, en vervangt de uitvoer dan door zijn
|
|
25
|
+
// eigen formaat ({"tool":"phpstan","result":"passed"}). Dat parseert hier
|
|
26
|
+
// niet, dus zonder deze regel is zo'n project onanalyseerbaar — en de server
|
|
27
|
+
// erft die agent-variabelen juist altijd, want de client start hem.
|
|
28
|
+
//
|
|
29
|
+
// De tmpdir-variabelen komen uit de projectroot (die is hier de cwd), zodat
|
|
30
|
+
// elk project zijn eigen resultcache houdt.
|
|
31
|
+
env: { ...process.env, PAO_DISABLE: '1', ...tmpDirEnv(cwd) },
|
|
32
|
+
});
|
|
33
|
+
const stdout = [];
|
|
34
|
+
const stderr = [];
|
|
35
|
+
let spawnError = null;
|
|
36
|
+
child.stdout.on('data', (chunk) => stdout.push(chunk));
|
|
37
|
+
child.stderr.on('data', (chunk) => stderr.push(chunk));
|
|
38
|
+
child.on('error', (error) => {
|
|
39
|
+
spawnError = error;
|
|
40
|
+
});
|
|
41
|
+
const timers = [];
|
|
42
|
+
const stopTimers = () => timers.forEach((timer) => { clearTimeout(timer); });
|
|
43
|
+
let reason = null;
|
|
44
|
+
// Timeout en annulering ruimen op dezelfde manier op; alleen de reden verschilt,
|
|
45
|
+
// en die bepaalt straks welke uitleg het model krijgt.
|
|
46
|
+
const stop = (why) => {
|
|
47
|
+
reason ??= why;
|
|
48
|
+
killGroup(child.pid, 'SIGTERM');
|
|
49
|
+
// Wie op SIGTERM niet reageert, krijgt alsnog SIGKILL.
|
|
50
|
+
timers.push(setTimeout(() => { killGroup(child.pid, 'SIGKILL'); }, KILL_GRACE_MS));
|
|
51
|
+
};
|
|
52
|
+
timers.push(setTimeout(() => { stop('timeout'); }, timeoutMs));
|
|
53
|
+
const onAbort = () => { stop('geannuleerd'); };
|
|
54
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
55
|
+
let settled = false;
|
|
56
|
+
const finish = (code, stopSignal) => {
|
|
57
|
+
if (settled)
|
|
58
|
+
return;
|
|
59
|
+
settled = true;
|
|
60
|
+
stopTimers();
|
|
61
|
+
// Listener opruimen: het signaal van een verzoek kan langer leven dan deze
|
|
62
|
+
// run, en een achterblijvende listener houdt het proces vast.
|
|
63
|
+
signal?.removeEventListener('abort', onAbort);
|
|
64
|
+
resolve({
|
|
65
|
+
code,
|
|
66
|
+
signal: stopSignal,
|
|
67
|
+
reason,
|
|
68
|
+
stdout: Buffer.concat(stdout).toString('utf8'),
|
|
69
|
+
stderr: Buffer.concat(stderr).toString('utf8'),
|
|
70
|
+
spawnError,
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
// Normaal wachten we op 'close': dan is alle output binnen.
|
|
74
|
+
child.on('close', finish);
|
|
75
|
+
// Maar bij een timeout wordt alleen het proces zelf gedood, terwijl zijn
|
|
76
|
+
// kleinkinderen — PHPStan draait parallelle workers — de pipes openhouden.
|
|
77
|
+
// 'close' komt dan pas als die klaar zijn, en dan is de timeout geen timeout
|
|
78
|
+
// meer. Zodra het proces zelf door een signaal is gestopt, heeft wachten op
|
|
79
|
+
// meer output geen zin.
|
|
80
|
+
child.on('exit', (code, exitSignal) => {
|
|
81
|
+
if (exitSignal !== null)
|
|
82
|
+
finish(code, exitSignal);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Bepaalt hoe de procesgroep van `pid` gedood wordt. Als plan teruggegeven en niet
|
|
88
|
+
* meteen uitgevoerd, want anders is de Windows-tak niet te testen op een machine
|
|
89
|
+
* waar je hem niet kunt uitvoeren — en juist die tak was stuk.
|
|
90
|
+
*/
|
|
91
|
+
export function planKill(pid, signal, platform = process.platform) {
|
|
92
|
+
// Een negatieve pid als groeps-id is POSIX; op Windows bestaat dat niet en
|
|
93
|
+
// gooit process.kill. `taskkill /T` neemt de kinderen mee, `/F` forceert.
|
|
94
|
+
// Windows heeft geen SIGTERM-equivalent, dus de vriendelijke eerste stap valt
|
|
95
|
+
// daar weg; de tweede aanroep treft dan een al verdwenen boom, en dat is
|
|
96
|
+
// onschadelijk (exit 128, net als ESRCH hieronder).
|
|
97
|
+
if (platform === 'win32') {
|
|
98
|
+
return { kind: 'commando', command: 'taskkill', args: ['/T', '/F', '/PID', String(pid)] };
|
|
99
|
+
}
|
|
100
|
+
// Het minteken maakt van het pid een groeps-id; dat werkt alleen omdat het
|
|
101
|
+
// proces met `detached` is gestart.
|
|
102
|
+
return { kind: 'signaal', target: -pid, signal };
|
|
103
|
+
}
|
|
104
|
+
/** Exit code van taskkill als het proces er al niet meer is. */
|
|
105
|
+
const TASKKILL_NOT_FOUND = 128;
|
|
106
|
+
/** Doodt de hele procesgroep van `pid`, op de manier die dit platform kent. */
|
|
107
|
+
function killGroup(pid, signal) {
|
|
108
|
+
if (pid === undefined)
|
|
109
|
+
return;
|
|
110
|
+
const plan = planKill(pid, signal);
|
|
111
|
+
if (plan.kind === 'commando') {
|
|
112
|
+
// Fire-and-forget: dit loopt in een timer en mag de event loop niet blokkeren.
|
|
113
|
+
const killer = spawn(plan.command, [...plan.args], { shell: false, stdio: 'ignore' });
|
|
114
|
+
killer.on('error', (error) => { reportKillFailure(pid, error); });
|
|
115
|
+
killer.on('exit', (code) => {
|
|
116
|
+
if (code !== 0 && code !== TASKKILL_NOT_FOUND) {
|
|
117
|
+
reportKillFailure(pid, new Error(`${plan.command} gaf exit code ${String(code)}`));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
process.kill(plan.target, plan.signal);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (isReportableKillError(error))
|
|
127
|
+
reportKillFailure(pid, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Of een mislukte kill het melden waard is. `ESRCH` betekent dat de groep al weg
|
|
132
|
+
* is — de race tussen de timer en deze aanroep, en precies de gewenste uitkomst.
|
|
133
|
+
* Alles anders wél melden: de vorige versie slikte élke fout en verborg daarmee dat
|
|
134
|
+
* een negatieve pid op Windows niet eens bestaat.
|
|
135
|
+
*/
|
|
136
|
+
export function isReportableKillError(error) {
|
|
137
|
+
return error?.code !== 'ESRCH';
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Meldt dat het opruimen niet gelukt is. Naar stderr, want bij stdio-transport is
|
|
141
|
+
* stdout het protocol — en niet gooien: dit loopt in een timer, waar een exception
|
|
142
|
+
* de hele server omhaalt.
|
|
143
|
+
*/
|
|
144
|
+
function reportKillFailure(pid, error) {
|
|
145
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
146
|
+
console.error(`phpstan-mcp kon procesgroep ${String(pid)} niet opruimen: ${reason}`);
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/phpstan/process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Env-variabele met het aantal analyses dat tegelijk mag draaien. */
|
|
2
|
+
export declare const MAX_PARALLEL_ENV_VAR = "PHPSTAN_MCP_MAX_PARALLEL";
|
|
3
|
+
/**
|
|
4
|
+
* Leest hoeveel analyses tegelijk mogen draaien. Een onbruikbare waarde wordt
|
|
5
|
+
* geweigerd en niet stil vervangen door de standaard — net als bij de
|
|
6
|
+
* geheugenlimiet: wie dit zet, heeft er iets mee bedoeld.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadMaxParallel(env?: NodeJS.ProcessEnv): number;
|
|
9
|
+
/**
|
|
10
|
+
* Laat `task` pas draaien als er een slot vrij is. Dit zit *binnen* de queue per
|
|
11
|
+
* project en niet ervoor: de queue gaat over PHPStans resultcache in dat ene
|
|
12
|
+
* project, dit over de machine als geheel.
|
|
13
|
+
*/
|
|
14
|
+
export declare function withSlot<T>(task: () => Promise<T>): Promise<T>;
|
|
15
|
+
/** Stopt vóór er iets gestart wordt als de aanroeper het verzoek al heeft afgebroken. */
|
|
16
|
+
export declare function throwIfAborted(project: string, signal: AbortSignal | undefined): void;
|
|
17
|
+
/**
|
|
18
|
+
* Zet een taak achter in de rij voor deze sleutel, zodat runs binnen hetzelfde
|
|
19
|
+
* project elkaar niet overlappen.
|
|
20
|
+
*/
|
|
21
|
+
export declare function enqueue<T>(key: string, task: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { PhpstanError } from './error.js';
|
|
2
|
+
/** Env-variabele met het aantal analyses dat tegelijk mag draaien. */
|
|
3
|
+
export const MAX_PARALLEL_ENV_VAR = 'PHPSTAN_MCP_MAX_PARALLEL';
|
|
4
|
+
/**
|
|
5
|
+
* Twee tegelijk. De queue per project bestond al voor de resultcache, maar over
|
|
6
|
+
* projecten heen begrensde niets: een model dat na list_projects tien projecten
|
|
7
|
+
* aanroept, start tien PHPStan-processen die elk hun eigen parallelle workers
|
|
8
|
+
* opzetten — en met een geheugenlimiet van 2G is dat tien keer dat plafond.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_MAX_PARALLEL = 2;
|
|
11
|
+
/**
|
|
12
|
+
* Leest hoeveel analyses tegelijk mogen draaien. Een onbruikbare waarde wordt
|
|
13
|
+
* geweigerd en niet stil vervangen door de standaard — net als bij de
|
|
14
|
+
* geheugenlimiet: wie dit zet, heeft er iets mee bedoeld.
|
|
15
|
+
*/
|
|
16
|
+
export function loadMaxParallel(env = process.env) {
|
|
17
|
+
const configured = env[MAX_PARALLEL_ENV_VAR]?.trim();
|
|
18
|
+
if (configured === undefined || configured === '')
|
|
19
|
+
return DEFAULT_MAX_PARALLEL;
|
|
20
|
+
const parsed = Number(configured);
|
|
21
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
22
|
+
throw new PhpstanError(`${MAX_PARALLEL_ENV_VAR} staat op "${configured}", en dat is geen geheel getal van 1 of hoger. ` +
|
|
23
|
+
`Laat hem weg voor de standaard (${String(DEFAULT_MAX_PARALLEL)}).`);
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
/** Hoeveel analyses er nu draaien, en wie op een vrij slot wacht. */
|
|
28
|
+
let running = 0;
|
|
29
|
+
const waiting = [];
|
|
30
|
+
/**
|
|
31
|
+
* Laat `task` pas draaien als er een slot vrij is. Dit zit *binnen* de queue per
|
|
32
|
+
* project en niet ervoor: de queue gaat over PHPStans resultcache in dat ene
|
|
33
|
+
* project, dit over de machine als geheel.
|
|
34
|
+
*/
|
|
35
|
+
export async function withSlot(task) {
|
|
36
|
+
// Per poging opnieuw uitlezen, zodat een gewijzigde omgeving meteen geldt en de
|
|
37
|
+
// grens niet vastligt op wat er bij het opstarten stond.
|
|
38
|
+
while (running >= loadMaxParallel()) {
|
|
39
|
+
await new Promise((resolve) => waiting.push(resolve));
|
|
40
|
+
}
|
|
41
|
+
running += 1;
|
|
42
|
+
try {
|
|
43
|
+
return await task();
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
running -= 1;
|
|
47
|
+
// Precies één wachtende wekken; die controleert de grens zelf opnieuw.
|
|
48
|
+
waiting.shift()?.();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Stopt vóór er iets gestart wordt als de aanroeper het verzoek al heeft afgebroken. */
|
|
52
|
+
export function throwIfAborted(project, signal) {
|
|
53
|
+
if (signal?.aborted === true) {
|
|
54
|
+
throw new PhpstanError(`De analyse van ${project} is niet gestart: het verzoek was al geannuleerd.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Lopende analyses per projectpad, zodat aanroepen per project op volgorde gaan. */
|
|
58
|
+
const queues = new Map();
|
|
59
|
+
/**
|
|
60
|
+
* Zet een taak achter in de rij voor deze sleutel, zodat runs binnen hetzelfde
|
|
61
|
+
* project elkaar niet overlappen.
|
|
62
|
+
*/
|
|
63
|
+
export function enqueue(key, task) {
|
|
64
|
+
const previous = queues.get(key) ?? Promise.resolve();
|
|
65
|
+
// .catch vóór .then: een mislukte vorige analyse mag de volgende niet blokkeren.
|
|
66
|
+
const result = previous.then(task, task);
|
|
67
|
+
const tail = result.catch(() => undefined);
|
|
68
|
+
queues.set(key, tail);
|
|
69
|
+
void tail.then(() => {
|
|
70
|
+
// Alleen opruimen als er ondertussen niets nieuws achteraan is gezet.
|
|
71
|
+
if (queues.get(key) === tail)
|
|
72
|
+
queues.delete(key);
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/phpstan/queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,sEAAsE;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAE/B;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,UAAU,GAAG,GAAG,CAAC,oBAAoB,CAAC,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,EAAE;QAAE,OAAO,oBAAoB,CAAC;IAE/E,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,YAAY,CACpB,GAAG,oBAAoB,cAAc,UAAU,iDAAiD;YAC9F,mCAAmC,MAAM,CAAC,oBAAoB,CAAC,IAAI,CACtE,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qEAAqE;AACrE,IAAI,OAAO,GAAG,CAAC,CAAC;AAEhB,MAAM,OAAO,GAAmB,EAAE,CAAC;AAEnC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAI,IAAsB;IACtD,gFAAgF;IAChF,yDAAyD;IACzD,OAAO,OAAO,IAAI,eAAe,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,IAAI,CAAC,CAAC;IACb,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,OAAO,IAAI,CAAC,CAAC;QACb,uEAAuE;QACvE,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC;IACtB,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,MAA+B;IAC7E,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,YAAY,CAAC,kBAAkB,OAAO,mDAAmD,CAAC,CAAC;IACvG,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;AAEnD;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAI,GAAW,EAAE,IAAsB;IAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IACtD,iFAAiF;IACjF,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAE3C,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;QAClB,sEAAsE;QACtE,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI;YAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|