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.
Files changed (43) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +3 -3
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/phpstan/error.d.ts +8 -0
  6. package/dist/phpstan/error.js +9 -0
  7. package/dist/phpstan/error.js.map +1 -0
  8. package/dist/phpstan/output.d.ts +57 -0
  9. package/dist/phpstan/output.js +76 -0
  10. package/dist/phpstan/output.js.map +1 -0
  11. package/dist/phpstan/process.d.ts +45 -0
  12. package/dist/phpstan/process.js +148 -0
  13. package/dist/phpstan/process.js.map +1 -0
  14. package/dist/phpstan/queue.d.ts +21 -0
  15. package/dist/phpstan/queue.js +76 -0
  16. package/dist/phpstan/queue.js.map +1 -0
  17. package/dist/phpstan.d.ts +7 -65
  18. package/dist/phpstan.js +8 -289
  19. package/dist/phpstan.js.map +1 -1
  20. package/dist/server.js +4 -0
  21. package/dist/server.js.map +1 -1
  22. package/dist/tools/analysis.d.ts +8 -0
  23. package/dist/tools/analysis.js +131 -0
  24. package/dist/tools/analysis.js.map +1 -0
  25. package/dist/tools/inspect.d.ts +14 -0
  26. package/dist/tools/inspect.js +218 -0
  27. package/dist/tools/inspect.js.map +1 -0
  28. package/dist/tools/prompts.d.ts +3 -0
  29. package/dist/tools/prompts.js +45 -0
  30. package/dist/tools/prompts.js.map +1 -0
  31. package/dist/tools/shared.d.ts +160 -0
  32. package/dist/tools/shared.js +219 -0
  33. package/dist/tools/shared.js.map +1 -0
  34. package/dist/tools/writes.d.ts +17 -0
  35. package/dist/tools/writes.js +131 -0
  36. package/dist/tools/writes.js.map +1 -0
  37. package/dist/tools.d.ts +0 -11
  38. package/dist/tools.js +4 -671
  39. package/dist/tools.js.map +1 -1
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/dist/version.js.map +1 -1
  43. 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 20, 22 en 24 de versies die `engines` belooft —
525
- en nog een keer bij `npm publish` via `prepublishOnly`. Draai je ze lokaal op één Node, dan
526
- zegt dat dus nog niets over de oudste versie die dit pakket ondersteunt.
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,YAAY,CAAC;AAC3D,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"}
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"}