mediawiki/extensions/Wanda: main (log #2136763)

sourcepatches

This run took 68 seconds.

From 1ef92bfa98e0bcf1f80ad6cb46cef07d3a771239 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Sat, 4 Oct 2025 05:12:57 +0000
Subject: [PATCH] build: Updating dependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

composer:
* mediawiki/mediawiki-codesniffer: 46.0.0 → 48.0.0
  The following sniffs are failing and were disabled:
  * MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate
  * MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic
  * MediaWiki.Commenting.FunctionComment.MissingParamTag
  * MediaWiki.Commenting.FunctionComment.MissingReturn
  * MediaWiki.Usage.ForbiddenFunctions.escapeshellarg
  * MediaWiki.Usage.ForbiddenFunctions.exec

* mediawiki/mediawiki-phan-config: 0.15.1 → 0.17.0

npm:
* eslint-config-wikimedia: 0.28.2 → 0.31.0
  The following rules are failing and were disabled:
  * no-console
* brace-expansion: 1.1.11, 2.0.1, 2.0.2 → 1.1.12, 2.0.2
  * https://github.com/advisories/GHSA-v6h2-p8h4-qcjw
* cross-spawn: 7.0.3 → 7.0.6
  * https://github.com/advisories/GHSA-3xgq-45jj-v275

Additional changes:
* eslint: Replaced `wikimedia/client-es5` with `wikimedia/client`.
* Added .stylelintcache to .gitignore.

Change-Id: If939e749cf526bd34cc88bca2c9a8a716ecbdb4f
---
 .eslintrc.json                      |   5 +-
 .gitignore                          |   3 +-
 .phpcs.xml                          |   9 +-
 Gruntfile.js                        |   2 +-
 composer.json                       |  30 +--
 includes/APIChat.php                |  14 +-
 includes/Hooks/FloatingChatHook.php |   2 +-
 libs/floating-chat.js               |  46 ++---
 libs/index.js                       |  26 +--
 package-lock.json                   | 300 +++++++++++++++++++++++-----
 package.json                        |   2 +-
 11 files changed, 319 insertions(+), 120 deletions(-)

diff --git a/.eslintrc.json b/.eslintrc.json
index 5b39bf7..b1fc2a1 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -4,7 +4,7 @@
 		"ecmaVersion": 6
 	},
 	"extends": [
-		"wikimedia/client-es5",
+		"wikimedia/client",
 		"wikimedia/jquery",
 		"wikimedia/mediawiki"
 	],
@@ -98,6 +98,7 @@
 		"es-x/no-default-parameters": "warn",
 		"es-x/no-for-of-loops": "warn",
 		"es-x/no-string-prototype-includes": "warn",
-		"es-x/no-template-literals": "off"
+		"es-x/no-template-literals": "off",
+		"no-console": "warn"
 	}
 }
diff --git a/.gitignore b/.gitignore
index c38c5c5..917bedd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 node_modules/
 /vendor/
 .eslintcache
-composer.lock
\ No newline at end of file
+composer.lock
+/.stylelintcache
diff --git a/.phpcs.xml b/.phpcs.xml
index e6082cf..a10abcf 100644
--- a/.phpcs.xml
+++ b/.phpcs.xml
@@ -1,6 +1,13 @@
 <?xml version="1.0"?>
 <ruleset>
-	<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki" />
+	<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki">
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate" />
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic" />
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingParamTag" />
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingReturn" />
+		<exclude name="MediaWiki.Usage.ForbiddenFunctions.escapeshellarg" />
+		<exclude name="MediaWiki.Usage.ForbiddenFunctions.exec" />
+	</rule>
 	<file>.</file>
 	<arg name="extensions" value="php" />
 	<arg name="encoding" value="UTF-8" />
diff --git a/Gruntfile.js b/Gruntfile.js
index edfe9e8..c393b18 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,6 +1,6 @@
 /* eslint-env node, es6 */
 module.exports = function ( grunt ) {
-	var conf = grunt.file.readJSON( 'extension.json' );
+	const conf = grunt.file.readJSON( 'extension.json' );
 	grunt.loadNpmTasks( 'grunt-banana-checker' );
 	grunt.loadNpmTasks( 'grunt-eslint' );
 	grunt.initConfig( {
diff --git a/composer.json b/composer.json
index 1ea426a..f533bbb 100644
--- a/composer.json
+++ b/composer.json
@@ -1,16 +1,16 @@
 {
-    "name": "wikiworks/wanda",
-    "description": "Chatbot for MediaWiki",
-    "type": "project",
-    "authors": [
-        {
-            "name": "Sanjay Thiyagarajan",
-            "email": "sanjayipscoc@gmail.com"
-        }
-    ],
-    "require-dev": {
-		"mediawiki/mediawiki-codesniffer": "46.0.0",
-		"mediawiki/mediawiki-phan-config": "0.15.1",
+	"name": "wikiworks/wanda",
+	"description": "Chatbot for MediaWiki",
+	"type": "project",
+	"authors": [
+		{
+			"name": "Sanjay Thiyagarajan",
+			"email": "sanjayipscoc@gmail.com"
+		}
+	],
+	"require-dev": {
+		"mediawiki/mediawiki-codesniffer": "48.0.0",
+		"mediawiki/mediawiki-phan-config": "0.17.0",
 		"mediawiki/minus-x": "1.1.3",
 		"php-parallel-lint/php-console-highlighter": "1.0.0",
 		"php-parallel-lint/php-parallel-lint": "1.4.0"
@@ -30,8 +30,8 @@
 	},
 	"config": {
 		"allow-plugins": {
-            "dealerdirect/phpcodesniffer-composer-installer": true,
-            "composer/installers": true
-        }
+			"dealerdirect/phpcodesniffer-composer-installer": true,
+			"composer/installers": true
+		}
 	}
 }
diff --git a/includes/APIChat.php b/includes/APIChat.php
index c847845..6232797 100644
--- a/includes/APIChat.php
+++ b/includes/APIChat.php
@@ -86,7 +86,7 @@ class APIChat extends ApiBase {
 		}
 
 		// Prepare source data
-		$sourceData = array_map( function( $result ) {
+		$sourceData = array_map( static function ( $result ) {
 			return isset( $result['_source']['page_title'] ) ? $result['_source']['page_title'] : 'Unknown source';
 		}, $searchResults );
 
@@ -149,7 +149,7 @@ class APIChat extends ApiBase {
 
 	private function queryElasticsearch( $queryText ) {
 		$queryEmbedding = $this->generateEmbedding( $queryText );
-		
+
 		if ( $queryEmbedding && isset( $queryEmbedding['embeddings'][0] ) ) {
 			// Use vector similarity search
 			return $this->vectorSearch( $queryEmbedding['embeddings'][0] );
@@ -206,7 +206,7 @@ class APIChat extends ApiBase {
 			"query" => [
 				"multi_match" => [
 					"query" => $queryText,
-					"fields" => ["title^2", "content"],
+					"fields" => [ "title^2", "content" ],
 					"type" => "best_fields",
 					"fuzziness" => "AUTO"
 				]
@@ -395,16 +395,16 @@ class APIChat extends ApiBase {
 	 * Generate response using Ollama
 	 */
 	private function generateOllamaResponse( $prompt ) {
-		$data = json_encode( [ 
-			"model" => self::$llmModel, 
-			"prompt" => $prompt, 
+		$data = json_encode( [
+			"model" => self::$llmModel,
+			"prompt" => $prompt,
 			"stream" => false,
 			"options" => [
 				"temperature" => self::$temperature,
 				"num_predict" => self::$maxTokens
 			]
 		] );
-		
+
 		$ch = curl_init( self::$llmApiEndpoint . "generate" );
 		curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
 		curl_setopt( $ch, CURLOPT_POSTFIELDS, $data );
diff --git a/includes/Hooks/FloatingChatHook.php b/includes/Hooks/FloatingChatHook.php
index e8a3fae..8bda38d 100644
--- a/includes/Hooks/FloatingChatHook.php
+++ b/includes/Hooks/FloatingChatHook.php
@@ -27,7 +27,7 @@ class FloatingChatHook {
 		if ( $out->getTitle() && $out->getTitle()->isSpecial( 'Wanda' ) ) {
 			return;
 		}
-		
+
 		// Add the floating chat module to all other pages
 		$out->addModules( 'ext.wanda.floating' );
 	}
diff --git a/libs/floating-chat.js b/libs/floating-chat.js
index 7fc1a9e..3b8a25e 100644
--- a/libs/floating-chat.js
+++ b/libs/floating-chat.js
@@ -1,30 +1,30 @@
-$(document).ready(function () {
+$(document).ready(() => {
   // Don't load floating chat on the special page itself
   if (mw.config.get('wgCanonicalSpecialPageName') === 'Wanda') {
     return;
   }
   console.log("Initializing floating chat...");
   // Create floating chat button
-  var floatingButton = $('<div>')
+  const floatingButton = $('<div>')
     .addClass('wanda-floating-button')
     .html('💬')
     .attr('title', mw.message( "wanda-floating-chat-title" ).text());
 
   // Create floating chat window
-  var floatingWindow = $('<div>')
+  const floatingWindow = $('<div>')
     .addClass('wanda-floating-window')
     .hide();
 
   // Create chat header
-  var chatHeader = $('<div>')
+  const chatHeader = $('<div>')
     .addClass('wanda-chat-header')
     .html('<span>Wanda AI Assistant</span><button class="wanda-close-btn">×</button>');
 
   // Create chat container (reuse existing chat functionality)
-  var chatContainer = $('<div>').addClass('chat-container wanda-floating-chat');
-  var chatBox = $('<div>').addClass('chat-box');
+  const chatContainer = $('<div>').addClass('chat-container wanda-floating-chat');
+  const chatBox = $('<div>').addClass('chat-box');
 
-  var instructionScreen = $('<div>').addClass('chat-instructions').html(`
+  const instructionScreen = $('<div>').addClass('chat-instructions').html(`
       <h2>${ mw.message( "wanda-chat-welcometext" ).text() }</h2>
       <p>${ mw.message( "wanda-chat-welcomedesc" ).text() }</p>
       <ul>
@@ -33,22 +33,22 @@ $(document).ready(function () {
       </ul>
   `);
 
-  var progressBar = new OO.ui.ProgressBarWidget({
+  const progressBar = new OO.ui.ProgressBarWidget({
       progress: false
   }).$element.addClass('chat-progress-bar').hide();
 
-  var chatInput = new OO.ui.MultilineTextInputWidget({
+  const chatInput = new OO.ui.MultilineTextInputWidget({
       placeholder: 'Type your message...',
       classes: [ 'chat-input-box' ],
       autosize: true,
       rows: 2
   });
-  var sendButton = new OO.ui.ButtonWidget({
+  const sendButton = new OO.ui.ButtonWidget({
       flags: [ 'primary', 'progressive' ],
       label: "Send"
   });
 
-  var inputContainer = $('<div>').addClass('chat-input-container');
+  const inputContainer = $('<div>').addClass('chat-input-container');
   inputContainer.append(chatInput.$element).append(sendButton.$element);
 
   chatContainer.append(chatBox).append(instructionScreen).append(progressBar).append(inputContainer);
@@ -68,8 +68,8 @@ $(document).ready(function () {
   function addMessage(role, text) {
       instructionScreen.hide();
       chatBox.show(); // Ensure chat box is visible when adding messages
-      var msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');
-      var msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);
+      const msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');
+      const msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);
 
       msgWrapper.append(msgBubble);
       chatBox.append(msgWrapper);
@@ -85,7 +85,7 @@ $(document).ready(function () {
   }
 
   function sendMessage() {
-      var userText = chatInput.getValue().trim();
+      const userText = chatInput.getValue().trim();
       if (!userText) return;
       chatBox.show();
 
@@ -102,7 +102,7 @@ $(document).ready(function () {
           },
           dataType: 'json',
           success: function (data) {
-              var response = data.response || 'Error fetching response';
+              let response = data.response || 'Error fetching response';
               response = response + "<br><b>Source</b>: " + data.source;
               addMessage('bot', response);
           },
@@ -116,22 +116,22 @@ $(document).ready(function () {
   }
 
   // Event handlers
-  floatingButton.on('click', function() {
+  floatingButton.on('click', () => {
     floatingWindow.show();
     // Add show class after a small delay to trigger animation
-    setTimeout(function() {
+    setTimeout(() => {
       floatingWindow.addClass('show');
     }, 10);
     floatingButton.hide();
     // Focus on input if window is opened
-    setTimeout(function() {
+    setTimeout(() => {
       chatInput.focus();
     }, 100);
   });
 
-  chatHeader.find('.wanda-close-btn').on('click', function() {
+  chatHeader.find('.wanda-close-btn').on('click', () => {
     floatingWindow.removeClass('show');
-    setTimeout(function() {
+    setTimeout(() => {
       floatingWindow.hide();
       floatingButton.show();
     }, 300);
@@ -139,7 +139,7 @@ $(document).ready(function () {
 
   sendButton.on('click', sendMessage);
 
-  chatInput.$element.on('keydown', function (e) {
+  chatInput.$element.on('keydown', (e) => {
       if (e.which === 13 && !e.shiftKey) {
           e.preventDefault();
           sendMessage();
@@ -152,11 +152,11 @@ $(document).ready(function () {
   }
 
   // Click outside to close
-  $(document).on('click', function(e) {
+  $(document).on('click', (e) => {
     if (!$(e.target).closest('.wanda-floating-window, .wanda-floating-button').length) {
       if (floatingWindow.is(':visible')) {
         floatingWindow.removeClass('show');
-        setTimeout(function() {
+        setTimeout(() => {
           floatingWindow.hide();
           floatingButton.show();
         }, 300);
diff --git a/libs/index.js b/libs/index.js
index 32ce5c9..4a0a648 100644
--- a/libs/index.js
+++ b/libs/index.js
@@ -1,9 +1,9 @@
-$(document).ready(function () {
-  var chatContainer = $('<div>').addClass('chat-container');
-  var chatBox = $('<div>').addClass('chat-box');
+$(document).ready(() => {
+  const chatContainer = $('<div>').addClass('chat-container');
+  const chatBox = $('<div>').addClass('chat-box');
   chatBox.hide();
 
-  var instructionScreen = $('<div>').addClass('chat-instructions').html(`
+  const instructionScreen = $('<div>').addClass('chat-instructions').html(`
       <h2>${ mw.message( "wanda-chat-welcometext" ).text() }</h2>
       <p>${ mw.message( "wanda-chat-welcomedesc" ).text() }</p>
       <ul>
@@ -12,21 +12,21 @@ $(document).ready(function () {
       </ul>
   `);
 
-  var progressBar = new OO.ui.ProgressBarWidget({
+  const progressBar = new OO.ui.ProgressBarWidget({
       progress: false
   }).$element.addClass('chat-progress-bar').hide();
 
-  var chatInput = new OO.ui.MultilineTextInputWidget({
+  const chatInput = new OO.ui.MultilineTextInputWidget({
       placeholder: 'Type your message...',
       classes: [ 'chat-input-box' ],
       autosize: true
   });
-  var sendButton = new OO.ui.ButtonWidget({
+  const sendButton = new OO.ui.ButtonWidget({
       flags: [ 'primary', 'progressive' ],
       label: "Submit"
   });
 
-  var inputContainer = $('<div>').addClass('chat-input-container');
+  const inputContainer = $('<div>').addClass('chat-input-container');
   inputContainer.append(chatInput.$element).append(sendButton.$element);
 
   chatContainer.append(chatBox).append(instructionScreen).append(progressBar).append(inputContainer);
@@ -34,8 +34,8 @@ $(document).ready(function () {
 
   function addMessage(role, text) {
       instructionScreen.hide();
-      var msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');
-      var msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);
+      const msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');
+      const msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);
 
       msgWrapper.append(msgBubble);
       chatBox.append(msgWrapper);
@@ -51,7 +51,7 @@ $(document).ready(function () {
   }
 
   function sendMessage() {
-      var userText = chatInput.getValue().trim();
+      const userText = chatInput.getValue().trim();
       if (!userText) return;
       chatBox.show();
 
@@ -68,7 +68,7 @@ $(document).ready(function () {
           },
           dataType: 'json',
           success: function (data) {
-              var response = data.response || 'Error fetching response';
+              let response = data.response || 'Error fetching response';
               response = response + "<br><b>Source</b>: " + data.source;
               addMessage('bot', response);
           },
@@ -83,7 +83,7 @@ $(document).ready(function () {
 
   sendButton.on('click', sendMessage);
 
-  chatInput.$element.on('keydown', function (e) {
+  chatInput.$element.on('keydown', (e) => {
       if (e.which === 13 && !e.shiftKey) {
           e.preventDefault();
           sendMessage();
diff --git a/package-lock.json b/package-lock.json
index 108d9bb..59a1bce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
 		"": {
 			"name": "Wanda",
 			"devDependencies": {
-				"eslint-config-wikimedia": "0.28.2",
+				"eslint-config-wikimedia": "0.31.0",
 				"grunt": "1.6.1",
 				"grunt-banana-checker": "0.13.0",
 				"grunt-eslint": "24.3.0",
@@ -151,9 +151,9 @@
 			}
 		},
 		"node_modules/@eslint-community/eslint-utils": {
-			"version": "4.5.1",
-			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
-			"integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
+			"version": "4.9.0",
+			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+			"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
 			"dev": true,
 			"dependencies": {
 				"eslint-visitor-keys": "^3.4.3"
@@ -292,6 +292,66 @@
 				"node": ">= 8"
 			}
 		},
+		"node_modules/@stylistic/eslint-plugin": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-3.1.0.tgz",
+			"integrity": "sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/utils": "^8.13.0",
+				"eslint-visitor-keys": "^4.2.0",
+				"espree": "^10.3.0",
+				"estraverse": "^5.3.0",
+				"picomatch": "^4.0.2"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"peerDependencies": {
+				"eslint": ">=8.40.0"
+			}
+		},
+		"node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": {
+			"version": "4.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+			"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/@stylistic/eslint-plugin/node_modules/espree": {
+			"version": "10.4.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+			"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+			"dev": true,
+			"dependencies": {
+				"acorn": "^8.15.0",
+				"acorn-jsx": "^5.3.2",
+				"eslint-visitor-keys": "^4.2.1"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
+			"version": "4.0.3",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+			"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+			"dev": true,
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/jonschlinkert"
+			}
+		},
 		"node_modules/@stylistic/stylelint-config": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/@stylistic/stylelint-config/-/stylelint-config-2.0.0.tgz",
@@ -395,14 +455,97 @@
 			"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
 			"dev": true
 		},
+		"node_modules/@typescript-eslint/eslint-plugin": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
+			"integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
+			"dev": true,
+			"dependencies": {
+				"@eslint-community/regexpp": "^4.10.0",
+				"@typescript-eslint/scope-manager": "8.35.1",
+				"@typescript-eslint/type-utils": "8.35.1",
+				"@typescript-eslint/utils": "8.35.1",
+				"@typescript-eslint/visitor-keys": "8.35.1",
+				"graphemer": "^1.4.0",
+				"ignore": "^7.0.0",
+				"natural-compare": "^1.4.0",
+				"ts-api-utils": "^2.1.0"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"@typescript-eslint/parser": "^8.35.1",
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
+		"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+			"version": "7.0.5",
+			"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+			"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 4"
+			}
+		},
+		"node_modules/@typescript-eslint/parser": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz",
+			"integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/scope-manager": "8.35.1",
+				"@typescript-eslint/types": "8.35.1",
+				"@typescript-eslint/typescript-estree": "8.35.1",
+				"@typescript-eslint/visitor-keys": "8.35.1",
+				"debug": "^4.3.4"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
+		"node_modules/@typescript-eslint/project-service": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
+			"integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/tsconfig-utils": "^8.35.1",
+				"@typescript-eslint/types": "^8.35.1",
+				"debug": "^4.3.4"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
 		"node_modules/@typescript-eslint/scope-manager": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz",
-			"integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
+			"integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.26.1",
-				"@typescript-eslint/visitor-keys": "8.26.1"
+				"@typescript-eslint/types": "8.35.1",
+				"@typescript-eslint/visitor-keys": "8.35.1"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -412,10 +555,49 @@
 				"url": "https://opencollective.com/typescript-eslint"
 			}
 		},
+		"node_modules/@typescript-eslint/tsconfig-utils": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
+			"integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
+		"node_modules/@typescript-eslint/type-utils": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
+			"integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/typescript-estree": "8.35.1",
+				"@typescript-eslint/utils": "8.35.1",
+				"debug": "^4.3.4",
+				"ts-api-utils": "^2.1.0"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
 		"node_modules/@typescript-eslint/types": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz",
-			"integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz",
+			"integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -426,19 +608,21 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz",
-			"integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
+			"integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.26.1",
-				"@typescript-eslint/visitor-keys": "8.26.1",
+				"@typescript-eslint/project-service": "8.35.1",
+				"@typescript-eslint/tsconfig-utils": "8.35.1",
+				"@typescript-eslint/types": "8.35.1",
+				"@typescript-eslint/visitor-keys": "8.35.1",
 				"debug": "^4.3.4",
 				"fast-glob": "^3.3.2",
 				"is-glob": "^4.0.3",
 				"minimatch": "^9.0.4",
 				"semver": "^7.6.0",
-				"ts-api-utils": "^2.0.1"
+				"ts-api-utils": "^2.1.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -452,9 +636,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+			"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
 			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0"
@@ -476,15 +660,15 @@
 			}
 		},
 		"node_modules/@typescript-eslint/utils": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz",
-			"integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz",
+			"integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
 			"dev": true,
 			"dependencies": {
-				"@eslint-community/eslint-utils": "^4.4.0",
-				"@typescript-eslint/scope-manager": "8.26.1",
-				"@typescript-eslint/types": "8.26.1",
-				"@typescript-eslint/typescript-estree": "8.26.1"
+				"@eslint-community/eslint-utils": "^4.7.0",
+				"@typescript-eslint/scope-manager": "8.35.1",
+				"@typescript-eslint/types": "8.35.1",
+				"@typescript-eslint/typescript-estree": "8.35.1"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -499,13 +683,13 @@
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz",
-			"integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
+			"integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.26.1",
-				"eslint-visitor-keys": "^4.2.0"
+				"@typescript-eslint/types": "8.35.1",
+				"eslint-visitor-keys": "^4.2.1"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -516,9 +700,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
-			"version": "4.2.0",
-			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
-			"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+			"version": "4.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+			"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -540,9 +724,9 @@
 			"dev": true
 		},
 		"node_modules/acorn": {
-			"version": "8.14.1",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
-			"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+			"version": "8.15.0",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+			"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
 			"dev": true,
 			"bin": {
 				"acorn": "bin/acorn"
@@ -717,9 +901,9 @@
 			"dev": true
 		},
 		"node_modules/brace-expansion": {
-			"version": "1.1.11",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"version": "1.1.12",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+			"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
 			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0",
@@ -1020,9 +1204,9 @@
 			}
 		},
 		"node_modules/cross-spawn": {
-			"version": "7.0.3",
-			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-			"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+			"version": "7.0.6",
+			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+			"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
 			"dev": true,
 			"dependencies": {
 				"path-key": "^3.1.0",
@@ -1410,11 +1594,14 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.28.2",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz",
-			"integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==",
+			"version": "0.31.0",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.31.0.tgz",
+			"integrity": "sha512-Z/t/zGPdxs/ehxb0EM6THNWAzueT7GtuqzjUvmBpkxcTKzZPJEXWnnpswdj/hgv8Ce8PIeDp0zwQxR4e3c9CIw==",
 			"dev": true,
 			"dependencies": {
+				"@stylistic/eslint-plugin": "^3.1.0",
+				"@typescript-eslint/eslint-plugin": "8.35.1",
+				"@typescript-eslint/parser": "8.35.1",
 				"browserslist-config-wikimedia": "^0.7.0",
 				"eslint": "^8.57.0",
 				"eslint-plugin-compat": "^4.2.0",
@@ -1425,13 +1612,16 @@
 				"eslint-plugin-mediawiki": "^0.7.0",
 				"eslint-plugin-mocha": "^10.4.3",
 				"eslint-plugin-n": "^17.7.0",
-				"eslint-plugin-no-jquery": "^3.0.1",
+				"eslint-plugin-no-jquery": "^3.1.1",
 				"eslint-plugin-qunit": "^8.1.1",
 				"eslint-plugin-security": "^1.7.1",
 				"eslint-plugin-unicorn": "^53.0.0",
 				"eslint-plugin-vue": "^9.26.0",
 				"eslint-plugin-wdio": "^8.24.12",
 				"eslint-plugin-yml": "^1.14.0"
+			},
+			"engines": {
+				"node": ">=18 <25"
 			}
 		},
 		"node_modules/eslint-plugin-compat": {
@@ -1593,9 +1783,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-n/node_modules/brace-expansion": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+			"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
 			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0"
@@ -4814,9 +5004,9 @@
 			}
 		},
 		"node_modules/ts-api-utils": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
-			"integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+			"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
 			"dev": true,
 			"engines": {
 				"node": ">=18.12"
diff --git a/package.json b/package.json
index 244f264..e21a36c 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
 		"test": "grunt test"
 	},
 	"devDependencies": {
-		"eslint-config-wikimedia": "0.28.2",
+		"eslint-config-wikimedia": "0.31.0",
 		"grunt": "1.6.1",
 		"grunt-banana-checker": "0.13.0",
 		"grunt-eslint": "24.3.0",
-- 
2.47.3

$ date
--- stdout ---
Sat Oct  4 05:12:05 UTC 2025

--- end ---
$ git clone file:///srv/git/mediawiki-extensions-Wanda.git repo --depth=1 -b master
--- stderr ---
Cloning into 'repo'...
--- stdout ---

--- end ---
$ git config user.name libraryupgrader
--- stdout ---

--- end ---
$ git config user.email tools.libraryupgrader@tools.wmflabs.org
--- stdout ---

--- end ---
$ git submodule update --init
--- stdout ---

--- end ---
$ grr init
--- stdout ---
Installed commit-msg hook.

--- end ---
$ git show-ref refs/heads/master
--- stdout ---
38f24d136c600ba123800356040e2a8a216918f1 refs/heads/master

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "brace-expansion": {
      "name": "brace-expansion",
      "severity": "low",
      "isDirect": false,
      "via": [
        {
          "source": 1105443,
          "name": "brace-expansion",
          "dependency": "brace-expansion",
          "title": "brace-expansion Regular Expression Denial of Service vulnerability",
          "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw",
          "severity": "low",
          "cwe": [
            "CWE-400"
          ],
          "cvss": {
            "score": 3.1,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": ">=1.0.0 <=1.1.11"
        },
        {
          "source": 1105444,
          "name": "brace-expansion",
          "dependency": "brace-expansion",
          "title": "brace-expansion Regular Expression Denial of Service vulnerability",
          "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw",
          "severity": "low",
          "cwe": [
            "CWE-400"
          ],
          "cvss": {
            "score": 3.1,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": ">=2.0.0 <=2.0.1"
        }
      ],
      "effects": [],
      "range": "1.0.0 - 1.1.11 || 2.0.0 - 2.0.1",
      "nodes": [
        "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion",
        "node_modules/brace-expansion",
        "node_modules/eslint-plugin-n/node_modules/brace-expansion"
      ],
      "fixAvailable": true
    },
    "cross-spawn": {
      "name": "cross-spawn",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1104664,
          "name": "cross-spawn",
          "dependency": "cross-spawn",
          "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn",
          "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275",
          "severity": "high",
          "cwe": [
            "CWE-1333"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": ">=7.0.0 <7.0.5"
        }
      ],
      "effects": [],
      "range": "7.0.0 - 7.0.4",
      "nodes": [
        "node_modules/cross-spawn"
      ],
      "fixAvailable": true
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 1,
      "moderate": 0,
      "high": 1,
      "critical": 0,
      "total": 2
    },
    "dependencies": {
      "prod": 1,
      "dev": 418,
      "optional": 0,
      "peer": 1,
      "peerOptional": 0,
      "total": 418
    }
  }
}

--- end ---
$ /usr/bin/composer install
--- stderr ---
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 38 installs, 0 updates, 0 removals
  - Locking composer/pcre (3.3.2)
  - Locking composer/semver (3.4.3)
  - Locking composer/spdx-licenses (1.5.9)
  - Locking composer/xdebug-handler (3.0.5)
  - Locking dealerdirect/phpcodesniffer-composer-installer (v1.1.2)
  - Locking doctrine/deprecations (1.1.5)
  - Locking felixfbecker/advanced-json-rpc (v3.2.1)
  - Locking mediawiki/mediawiki-codesniffer (v46.0.0)
  - Locking mediawiki/mediawiki-phan-config (0.15.1)
  - Locking mediawiki/minus-x (1.1.3)
  - Locking mediawiki/phan-taint-check-plugin (6.1.0)
  - Locking microsoft/tolerant-php-parser (v0.1.2)
  - Locking netresearch/jsonmapper (v4.5.0)
  - Locking phan/phan (5.4.5)
  - Locking php-parallel-lint/php-console-color (v1.0.1)
  - Locking php-parallel-lint/php-console-highlighter (v1.0.0)
  - Locking php-parallel-lint/php-parallel-lint (v1.4.0)
  - Locking phpcsstandards/phpcsextra (1.2.1)
  - Locking phpcsstandards/phpcsutils (1.0.12)
  - Locking phpdocumentor/reflection-common (2.2.0)
  - Locking phpdocumentor/reflection-docblock (5.6.3)
  - Locking phpdocumentor/type-resolver (1.10.0)
  - Locking phpstan/phpdoc-parser (2.3.0)
  - Locking psr/container (2.0.2)
  - Locking psr/log (3.0.2)
  - Locking sabre/event (5.1.7)
  - Locking squizlabs/php_codesniffer (3.11.3)
  - Locking symfony/console (v7.3.4)
  - Locking symfony/deprecation-contracts (v3.6.0)
  - Locking symfony/polyfill-ctype (v1.33.0)
  - Locking symfony/polyfill-intl-grapheme (v1.33.0)
  - Locking symfony/polyfill-intl-normalizer (v1.33.0)
  - Locking symfony/polyfill-mbstring (v1.33.0)
  - Locking symfony/polyfill-php80 (v1.33.0)
  - Locking symfony/service-contracts (v3.6.0)
  - Locking symfony/string (v7.3.4)
  - Locking tysonandre/var_representation_polyfill (0.1.3)
  - Locking webmozart/assert (1.11.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 38 installs, 0 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]
  - Installing squizlabs/php_codesniffer (3.11.3): Extracting archive
  - Installing dealerdirect/phpcodesniffer-composer-installer (v1.1.2): Extracting archive
  - Installing composer/pcre (3.3.2): Extracting archive
  - Installing symfony/polyfill-php80 (v1.33.0): Extracting archive
  - Installing phpcsstandards/phpcsutils (1.0.12): Extracting archive
  - Installing phpcsstandards/phpcsextra (1.2.1): Extracting archive
  - Installing symfony/polyfill-mbstring (v1.33.0): Extracting archive
  - Installing composer/spdx-licenses (1.5.9): Extracting archive
  - Installing composer/semver (3.4.3): Extracting archive
  - Installing mediawiki/mediawiki-codesniffer (v46.0.0): Extracting archive
  - Installing tysonandre/var_representation_polyfill (0.1.3): Extracting archive
  - Installing symfony/polyfill-intl-normalizer (v1.33.0): Extracting archive
  - Installing symfony/polyfill-intl-grapheme (v1.33.0): Extracting archive
  - Installing symfony/polyfill-ctype (v1.33.0): Extracting archive
  - Installing symfony/string (v7.3.4): Extracting archive
  - Installing symfony/deprecation-contracts (v3.6.0): Extracting archive
  - Installing psr/container (2.0.2): Extracting archive
  - Installing symfony/service-contracts (v3.6.0): Extracting archive
  - Installing symfony/console (v7.3.4): Extracting archive
  - Installing sabre/event (5.1.7): Extracting archive
  - Installing netresearch/jsonmapper (v4.5.0): Extracting archive
  - Installing microsoft/tolerant-php-parser (v0.1.2): Extracting archive
  - Installing webmozart/assert (1.11.0): Extracting archive
  - Installing phpstan/phpdoc-parser (2.3.0): Extracting archive
  - Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
  - Installing doctrine/deprecations (1.1.5): Extracting archive
  - Installing phpdocumentor/type-resolver (1.10.0): Extracting archive
  - Installing phpdocumentor/reflection-docblock (5.6.3): Extracting archive
  - Installing felixfbecker/advanced-json-rpc (v3.2.1): Extracting archive
  - Installing psr/log (3.0.2): Extracting archive
  - Installing composer/xdebug-handler (3.0.5): Extracting archive
  - Installing phan/phan (5.4.5): Extracting archive
  - Installing mediawiki/phan-taint-check-plugin (6.1.0): Extracting archive
  - Installing mediawiki/mediawiki-phan-config (0.15.1): Extracting archive
  - Installing mediawiki/minus-x (1.1.3): Extracting archive
  - Installing php-parallel-lint/php-console-color (v1.0.1): Extracting archive
  - Installing php-parallel-lint/php-console-highlighter (v1.0.0): Extracting archive
  - Installing php-parallel-lint/php-parallel-lint (v1.4.0): Extracting archive
  0/36 [>---------------------------]   0%
 27/36 [=====================>------]  75%
 36/36 [============================] 100%
1 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
17 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
--- stdout ---
PHP CodeSniffer Config installed_paths set to ../../mediawiki/mediawiki-codesniffer,../../phpcsstandards/phpcsextra,../../phpcsstandards/phpcsutils

--- end ---
Upgrading n:eslint-config-wikimedia from 0.28.2 -> 0.31.0
$ /usr/bin/npm install
--- stdout ---

added 428 packages, and audited 429 packages in 5s

105 packages are looking for funding
  run `npm fund` for details

2 vulnerabilities (1 low, 1 high)

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
$ /usr/bin/npm install grunt-eslint@24.3.0 --save-exact
--- stdout ---

up to date, audited 429 packages in 1s

105 packages are looking for funding
  run `npm fund` for details

2 vulnerabilities (1 low, 1 high)

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
$ ./node_modules/.bin/eslint . --fix
--- stdout ---

/src/repo/Gruntfile.js
  3:2  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

/src/repo/libs/floating-chat.js
    6:3   error    Unexpected console statement                 no-console
    8:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   14:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   19:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   24:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   25:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   27:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   36:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   40:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   46:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   51:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   71:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   72:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   88:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  105:15  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

/src/repo/libs/index.js
   2:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   3:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   6:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  15:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  19:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  24:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  29:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  37:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  38:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  54:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  71:15  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

✖ 27 problems (1 error, 26 warnings)


--- end ---
$ ./node_modules/.bin/eslint . -f json
--- stdout ---
[{"filePath":"/src/repo/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/Gruntfile.js","messages":[{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":3,"column":2,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":3,"endColumn":55}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* eslint-env node, es6 */\nmodule.exports = function ( grunt ) {\n\tconst conf = grunt.file.readJSON( 'extension.json' );\n\tgrunt.loadNpmTasks( 'grunt-banana-checker' );\n\tgrunt.loadNpmTasks( 'grunt-eslint' );\n\tgrunt.initConfig( {\n\t\teslint: {\n\t\t\toptions: {\n\t\t\t\tcache: true\n\t\t\t},\n\t\t\tall: '.'\n\t\t},\n\t\tstylelint: {\n\t\t\toptions: {\n\t\t\t\tcache: true\n\t\t\t},\n\t\t\tall: [\n\t\t\t\t'**/*.{css,less}',\n\t\t\t\t'!node_modules/**',\n\t\t\t\t'!vendor/**'\n\t\t\t]\n\t\t},\n\t\tbanana: conf.MessagesDirs\n\t} );\n\tgrunt.registerTask( 'test', [ 'eslint', 'banana' ] );\n\tgrunt.registerTask( 'default', 'test' );\n};\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/composer.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/extension.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/i18n/en.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/i18n/qqq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/libs/floating-chat.js","messages":[{"ruleId":"no-console","severity":2,"message":"Unexpected console statement.","line":6,"column":3,"nodeType":"MemberExpression","messageId":"unexpected","endLine":6,"endColumn":14,"suggestions":[{"messageId":"removeConsole","data":{"propertyName":"log"},"fix":{"range":[166,211],"text":""},"desc":"Remove the console.log()."}]},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":8,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":11,"endColumn":70},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":14,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":16,"endColumn":13},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":19,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":21,"endColumn":88},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":24,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":24,"endColumn":83},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":25,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":25,"endColumn":51},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":27,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":34,"endColumn":6},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":36,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":38,"endColumn":52},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":40,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":45,"endColumn":6},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":46,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":49,"endColumn":6},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":51,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":51,"endColumn":70},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":71,"column":7,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":71,"endColumn":91},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":72,"column":7,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":72,"endColumn":93},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":88,"column":7,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":88,"endColumn":52},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":105,"column":15,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":105,"endColumn":73}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":14,"fixableErrorCount":0,"fixableWarningCount":0,"source":"$(document).ready(() => {\n  // Don't load floating chat on the special page itself\n  if (mw.config.get('wgCanonicalSpecialPageName') === 'Wanda') {\n    return;\n  }\n  console.log(\"Initializing floating chat...\");\n  // Create floating chat button\n  const floatingButton = $('<div>')\n    .addClass('wanda-floating-button')\n    .html('💬')\n    .attr('title', mw.message( \"wanda-floating-chat-title\" ).text());\n\n  // Create floating chat window\n  const floatingWindow = $('<div>')\n    .addClass('wanda-floating-window')\n    .hide();\n\n  // Create chat header\n  const chatHeader = $('<div>')\n    .addClass('wanda-chat-header')\n    .html('<span>Wanda AI Assistant</span><button class=\"wanda-close-btn\">×</button>');\n\n  // Create chat container (reuse existing chat functionality)\n  const chatContainer = $('<div>').addClass('chat-container wanda-floating-chat');\n  const chatBox = $('<div>').addClass('chat-box');\n\n  const instructionScreen = $('<div>').addClass('chat-instructions').html(`\n      <h2>${ mw.message( \"wanda-chat-welcometext\" ).text() }</h2>\n      <p>${ mw.message( \"wanda-chat-welcomedesc\" ).text() }</p>\n      <ul>\n          <li>💬 ${ mw.message( \"wanda-chat-instruction1\" ).text() }</li>\n          <li>🤖 ${ mw.message( \"wanda-chat-instruction2\" ).text() }</li>\n      </ul>\n  `);\n\n  const progressBar = new OO.ui.ProgressBarWidget({\n      progress: false\n  }).$element.addClass('chat-progress-bar').hide();\n\n  const chatInput = new OO.ui.MultilineTextInputWidget({\n      placeholder: 'Type your message...',\n      classes: [ 'chat-input-box' ],\n      autosize: true,\n      rows: 2\n  });\n  const sendButton = new OO.ui.ButtonWidget({\n      flags: [ 'primary', 'progressive' ],\n      label: \"Send\"\n  });\n\n  const inputContainer = $('<div>').addClass('chat-input-container');\n  inputContainer.append(chatInput.$element).append(sendButton.$element);\n\n  chatContainer.append(chatBox).append(instructionScreen).append(progressBar).append(inputContainer);\n  floatingWindow.append(chatHeader).append(chatContainer);\n\n  // Add to body\n  $('body').append(floatingButton).append(floatingWindow);\n\n  // Ensure input container is always visible and properly styled\n  inputContainer.css({\n    'display': 'flex',\n    'visibility': 'visible',\n    'opacity': '1'\n  });\n\n  // Chat functionality (same as original)\n  function addMessage(role, text) {\n      instructionScreen.hide();\n      chatBox.show(); // Ensure chat box is visible when adding messages\n      const msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');\n      const msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);\n\n      msgWrapper.append(msgBubble);\n      chatBox.append(msgWrapper);\n      chatBox.scrollTop(chatBox[0].scrollHeight);\n  }\n\n  function toggleProgressBar(show) {\n      if (show) {\n          progressBar.show();\n      } else {\n          progressBar.hide();\n      }\n  }\n\n  function sendMessage() {\n      const userText = chatInput.getValue().trim();\n      if (!userText) return;\n      chatBox.show();\n\n      addMessage('user', userText);\n      chatInput.setValue('');\n      toggleProgressBar(true);\n\n      $.ajax({\n          url: mw.util.wikiScript('api'),\n          data: {\n              action: 'chatbot',\n              format: 'json',\n              message: userText\n          },\n          dataType: 'json',\n          success: function (data) {\n              let response = data.response || 'Error fetching response';\n              response = response + \"<br><b>Source</b>: \" + data.source;\n              addMessage('bot', response);\n          },\n          error: function () {\n              addMessage('bot', 'Error connecting to chatbot API.');\n          },\n          complete: function () {\n              toggleProgressBar(false);\n          }\n      });\n  }\n\n  // Event handlers\n  floatingButton.on('click', () => {\n    floatingWindow.show();\n    // Add show class after a small delay to trigger animation\n    setTimeout(() => {\n      floatingWindow.addClass('show');\n    }, 10);\n    floatingButton.hide();\n    // Focus on input if window is opened\n    setTimeout(() => {\n      chatInput.focus();\n    }, 100);\n  });\n\n  chatHeader.find('.wanda-close-btn').on('click', () => {\n    floatingWindow.removeClass('show');\n    setTimeout(() => {\n      floatingWindow.hide();\n      floatingButton.show();\n    }, 300);\n  });\n\n  sendButton.on('click', sendMessage);\n\n  chatInput.$element.on('keydown', (e) => {\n      if (e.which === 13 && !e.shiftKey) {\n          e.preventDefault();\n          sendMessage();\n      }\n  });\n\n  // Show instructions initially\n  if (chatBox.children().length === 0) {\n      instructionScreen.show();\n  }\n\n  // Click outside to close\n  $(document).on('click', (e) => {\n    if (!$(e.target).closest('.wanda-floating-window, .wanda-floating-button').length) {\n      if (floatingWindow.is(':visible')) {\n        floatingWindow.removeClass('show');\n        setTimeout(() => {\n          floatingWindow.hide();\n          floatingButton.show();\n        }, 300);\n      }\n    }\n  });\n});\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/libs/index.js","messages":[{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":2,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":2,"endColumn":63},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":3,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":3,"endColumn":51},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":6,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":13,"endColumn":6},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":15,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":17,"endColumn":52},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":19,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":23,"endColumn":6},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":24,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":27,"endColumn":6},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":29,"column":3,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":29,"endColumn":70},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":37,"column":7,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":37,"endColumn":91},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":38,"column":7,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":38,"endColumn":93},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":54,"column":7,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":54,"endColumn":52},{"ruleId":"es-x/no-block-scoped-variables","severity":1,"message":"ES2015 block-scoped variables are forbidden.","line":71,"column":15,"nodeType":"VariableDeclaration","messageId":"forbidden","endLine":71,"endColumn":73}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":11,"fixableErrorCount":0,"fixableWarningCount":0,"source":"$(document).ready(() => {\n  const chatContainer = $('<div>').addClass('chat-container');\n  const chatBox = $('<div>').addClass('chat-box');\n  chatBox.hide();\n\n  const instructionScreen = $('<div>').addClass('chat-instructions').html(`\n      <h2>${ mw.message( \"wanda-chat-welcometext\" ).text() }</h2>\n      <p>${ mw.message( \"wanda-chat-welcomedesc\" ).text() }</p>\n      <ul>\n          <li>💬 ${ mw.message( \"wanda-chat-instruction1\" ).text() }</li>\n          <li>🤖 ${ mw.message( \"wanda-chat-instruction2\" ).text() }</li>\n      </ul>\n  `);\n\n  const progressBar = new OO.ui.ProgressBarWidget({\n      progress: false\n  }).$element.addClass('chat-progress-bar').hide();\n\n  const chatInput = new OO.ui.MultilineTextInputWidget({\n      placeholder: 'Type your message...',\n      classes: [ 'chat-input-box' ],\n      autosize: true\n  });\n  const sendButton = new OO.ui.ButtonWidget({\n      flags: [ 'primary', 'progressive' ],\n      label: \"Submit\"\n  });\n\n  const inputContainer = $('<div>').addClass('chat-input-container');\n  inputContainer.append(chatInput.$element).append(sendButton.$element);\n\n  chatContainer.append(chatBox).append(instructionScreen).append(progressBar).append(inputContainer);\n  $('#chat-bot-container').append(chatContainer);\n\n  function addMessage(role, text) {\n      instructionScreen.hide();\n      const msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');\n      const msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);\n\n      msgWrapper.append(msgBubble);\n      chatBox.append(msgWrapper);\n      chatBox.scrollTop(chatBox[0].scrollHeight);\n  }\n\n  function toggleProgressBar(show) {\n      if (show) {\n          progressBar.show();\n      } else {\n          progressBar.hide();\n      }\n  }\n\n  function sendMessage() {\n      const userText = chatInput.getValue().trim();\n      if (!userText) return;\n      chatBox.show();\n\n      addMessage('user', userText);\n      chatInput.setValue('');\n      toggleProgressBar(true);\n\n      $.ajax({\n          url: mw.util.wikiScript('api'),\n          data: {\n              action: 'chatbot',\n              format: 'json',\n              message: userText\n          },\n          dataType: 'json',\n          success: function (data) {\n              let response = data.response || 'Error fetching response';\n              response = response + \"<br><b>Source</b>: \" + data.source;\n              addMessage('bot', response);\n          },\n          error: function () {\n              addMessage('bot', 'Error connecting to chatbot API.');\n          },\n          complete: function () {\n              toggleProgressBar(false);\n          }\n      });\n  }\n\n  sendButton.on('click', sendMessage);\n\n  chatInput.$element.on('keydown', (e) => {\n      if (e.which === 13 && !e.shiftKey) {\n          e.preventDefault();\n          sendMessage();\n      }\n  });\n\n  if (chatBox.children().length === 0) {\n      instructionScreen.show();\n  }\n});\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/package-lock.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]},{"filePath":"/src/repo/package.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]}]}]

--- end ---
Disabling eslint rule 'no-console' (broken in .eslintrc.json) on .eslintrc.json
$ /usr/bin/npm ci
--- stdout ---

added 428 packages, and audited 429 packages in 6s

105 packages are looking for funding
  run `npm fund` for details

2 vulnerabilities (1 low, 1 high)

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

--- end ---
$ /usr/bin/npm test
--- stdout ---

> test
> grunt test

Running "eslint:all" (eslint) task

/src/repo/Gruntfile.js
  3:2  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

/src/repo/libs/floating-chat.js
    6:3   warning  Unexpected console statement                 no-console
    8:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   14:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   19:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   24:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   25:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   27:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   36:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   40:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   46:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   51:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   71:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   72:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   88:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  105:15  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

/src/repo/libs/index.js
   2:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   3:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   6:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  15:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  19:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  24:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  29:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  37:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  38:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  54:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  71:15  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

✖ 27 problems (0 errors, 27 warnings)


Running "banana:Wanda" (banana) task
>> 1 message directory checked.

Done.

--- end ---
Upgrading c:mediawiki/mediawiki-codesniffer from 46.0.0 -> 48.0.0
Upgrading c:mediawiki/mediawiki-phan-config from 0.15.1 -> 0.17.0
$ /usr/bin/composer update
--- stderr ---
Loading composer repositories with package information
Updating dependencies
Lock file operations: 0 installs, 8 updates, 0 removals
  - Upgrading composer/semver (3.4.3 => 3.4.4)
  - Upgrading mediawiki/mediawiki-codesniffer (v46.0.0 => v48.0.0)
  - Upgrading mediawiki/mediawiki-phan-config (0.15.1 => 0.17.0)
  - Upgrading mediawiki/phan-taint-check-plugin (6.1.0 => 7.0.0)
  - Upgrading phan/phan (5.4.5 => 5.5.1)
  - Upgrading phpcsstandards/phpcsextra (1.2.1 => 1.4.0)
  - Upgrading phpcsstandards/phpcsutils (1.0.12 => 1.1.1)
  - Upgrading squizlabs/php_codesniffer (3.11.3 => 3.13.2)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 8 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]
  - Upgrading squizlabs/php_codesniffer (3.11.3 => 3.13.2): Extracting archive
  - Upgrading phpcsstandards/phpcsutils (1.0.12 => 1.1.1): Extracting archive
  - Upgrading phpcsstandards/phpcsextra (1.2.1 => 1.4.0): Extracting archive
  - Upgrading composer/semver (3.4.3 => 3.4.4): Extracting archive
  - Upgrading mediawiki/mediawiki-codesniffer (v46.0.0 => v48.0.0): Extracting archive
  - Upgrading phan/phan (5.4.5 => 5.5.1): Extracting archive
  - Upgrading mediawiki/phan-taint-check-plugin (6.1.0 => 7.0.0): Extracting archive
  - Upgrading mediawiki/mediawiki-phan-config (0.15.1 => 0.17.0): Extracting archive
 0/8 [>---------------------------]   0%
 7/8 [========================>---]  87%
 8/8 [============================] 100%
Generating autoload files
17 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found.
--- stdout ---
PHP CodeSniffer Config installed_paths set to ../../mediawiki/mediawiki-codesniffer,../../phpcsstandards/phpcsextra,../../phpcsstandards/phpcsutils

--- end ---
$ vendor/bin/phpcs --report=json
--- stdout ---
{"totals":{"errors":49,"warnings":5,"fixable":10},"files":{"\/src\/repo\/Wanda_Aliases.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialAIChat.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/Hooks\/FloatingChatHook.php":{"errors":1,"warnings":0,"messages":[{"message":"Whitespace found at end of line","source":"Squiz.WhiteSpace.SuperfluousWhitespace.EndLine","severity":5,"fixable":true,"type":"ERROR","line":30,"column":1}]},"\/src\/repo\/maintenance\/ReindexAllPages.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/Hooks\/PageIndexUpdater.php":{"errors":12,"warnings":2,"messages":[{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":31,"column":6},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":67,"column":6},{"message":"Doc comment for parameter \"$indexName\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":93,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":173,"column":6},{"message":"exec should not be used","source":"MediaWiki.Usage.ForbiddenFunctions.exec","severity":5,"fixable":false,"type":"WARNING","line":188,"column":9},{"message":"escapeshellarg should not be used","source":"MediaWiki.Usage.ForbiddenFunctions.escapeshellarg","severity":5,"fixable":false,"type":"WARNING","line":188,"column":38},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":193,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":195,"column":6},{"message":"Doc comment for parameter \"$wikiPage\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$user\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$summary\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$flags\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$revision\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$editResult\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5}]},"\/src\/repo\/includes\/APIChat.php":{"errors":36,"warnings":3,"messages":[{"message":"Missing function doc comment","source":"MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic","severity":5,"fixable":false,"type":"ERROR","line":30,"column":12},{"message":"Use static closure","source":"MediaWiki.Usage.StaticClosure.StaticClosure","severity":5,"fixable":true,"type":"WARNING","line":89,"column":34},{"message":"A single space should be after the function keyword in closures","source":"MediaWiki.WhiteSpace.SpaceAfterClosure.NoWhitespaceAfterClosure","severity":5,"fixable":true,"type":"ERROR","line":89,"column":34},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":100,"column":6},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":123,"column":6},{"message":"Missing function doc comment","source":"MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate","severity":5,"fixable":false,"type":"ERROR","line":150,"column":13},{"message":"Whitespace found at end of line","source":"Squiz.WhiteSpace.SuperfluousWhitespace.EndLine","severity":5,"fixable":true,"type":"ERROR","line":152,"column":1},{"message":"Doc comment for parameter \"$queryEmbedding\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":162,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":164,"column":6},{"message":"Doc comment for parameter \"$queryText\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":201,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":203,"column":6},{"message":"Single space expected after opening parenthesis","source":"MediaWiki.WhiteSpace.SpaceyParenthesis.SingleSpaceAfterOpenParenthesis","severity":5,"fixable":true,"type":"WARNING","line":209,"column":34},{"message":"Single space expected before closing parenthesis","source":"MediaWiki.WhiteSpace.SpaceyParenthesis.SingleSpaceBeforeCloseParenthesis","severity":5,"fixable":true,"type":"WARNING","line":209,"column":54},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":239,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":241,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":256,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":258,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":282,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":284,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":322,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":324,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":362,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":364,"column":6},{"message":"Doc comment for parameter \"$query\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":370,"column":5},{"message":"Doc comment for parameter \"$context\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":370,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":372,"column":6},{"message":"Doc comment for parameter \"$prompt\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":394,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":396,"column":6},{"message":"Whitespace found at end of line","source":"Squiz.WhiteSpace.SuperfluousWhitespace.EndLine","severity":5,"fixable":true,"type":"ERROR","line":398,"column":31},{"message":"Whitespace found at end of line","source":"Squiz.WhiteSpace.SuperfluousWhitespace.EndLine","severity":5,"fixable":true,"type":"ERROR","line":399,"column":40},{"message":"Whitespace found at end of line","source":"Squiz.WhiteSpace.SuperfluousWhitespace.EndLine","severity":5,"fixable":true,"type":"ERROR","line":400,"column":33},{"message":"Whitespace found at end of line","source":"Squiz.WhiteSpace.SuperfluousWhitespace.EndLine","severity":5,"fixable":true,"type":"ERROR","line":407,"column":1},{"message":"Doc comment for parameter \"$prompt\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":427,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":429,"column":6},{"message":"Doc comment for parameter \"$prompt\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":466,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":468,"column":6},{"message":"Doc comment for parameter \"$prompt\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":506,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":508,"column":6},{"message":"Missing function doc comment","source":"MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic","severity":5,"fixable":false,"type":"ERROR","line":545,"column":12}]}}}

--- end ---
PHPCS run failed
$ vendor/bin/phpcbf
--- stdout ---

PHPCBF RESULT SUMMARY
----------------------------------------------------------------------
FILE                                                  FIXED  REMAINING
----------------------------------------------------------------------
/src/repo/includes/Hooks/FloatingChatHook.php         1      0
/src/repo/includes/APIChat.php                        9      30
----------------------------------------------------------------------
A TOTAL OF 10 ERRORS WERE FIXED IN 2 FILES
----------------------------------------------------------------------

Time: 621ms; Memory: 8MB



--- end ---
$ vendor/bin/phpcs --report=json
--- stdout ---
{"totals":{"errors":42,"warnings":2,"fixable":0},"files":{"\/src\/repo\/maintenance\/ReindexAllPages.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/Wanda_Aliases.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialAIChat.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/Hooks\/FloatingChatHook.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/Hooks\/PageIndexUpdater.php":{"errors":12,"warnings":2,"messages":[{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":31,"column":6},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":67,"column":6},{"message":"Doc comment for parameter \"$indexName\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":93,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":173,"column":6},{"message":"exec should not be used","source":"MediaWiki.Usage.ForbiddenFunctions.exec","severity":5,"fixable":false,"type":"WARNING","line":188,"column":9},{"message":"escapeshellarg should not be used","source":"MediaWiki.Usage.ForbiddenFunctions.escapeshellarg","severity":5,"fixable":false,"type":"WARNING","line":188,"column":38},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":193,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":195,"column":6},{"message":"Doc comment for parameter \"$wikiPage\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$user\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$summary\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$flags\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$revision\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5},{"message":"Doc comment for parameter \"$editResult\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":220,"column":5}]},"\/src\/repo\/includes\/APIChat.php":{"errors":30,"warnings":0,"messages":[{"message":"Missing function doc comment","source":"MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic","severity":5,"fixable":false,"type":"ERROR","line":30,"column":12},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":100,"column":6},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":123,"column":6},{"message":"Missing function doc comment","source":"MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate","severity":5,"fixable":false,"type":"ERROR","line":150,"column":13},{"message":"Doc comment for parameter \"$queryEmbedding\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":162,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":164,"column":6},{"message":"Doc comment for parameter \"$queryText\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":201,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":203,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":239,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":241,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":256,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":258,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":282,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":284,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":322,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":324,"column":6},{"message":"Doc comment for parameter \"$text\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":362,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":364,"column":6},{"message":"Doc comment for parameter \"$query\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":370,"column":5},{"message":"Doc comment for parameter \"$context\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":370,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":372,"column":6},{"message":"Doc comment for parameter \"$prompt\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":394,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":396,"column":6},{"message":"Doc comment for parameter \"$prompt\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":427,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":429,"column":6},{"message":"Doc comment for parameter \"$prompt\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":466,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":468,"column":6},{"message":"Doc comment for parameter \"$prompt\" missing","source":"MediaWiki.Commenting.FunctionComment.MissingParamTag","severity":5,"fixable":false,"type":"ERROR","line":506,"column":5},{"message":"Missing return type or @return tag in function comment","source":"MediaWiki.Commenting.FunctionComment.MissingReturn","severity":5,"fixable":false,"type":"ERROR","line":508,"column":6},{"message":"Missing function doc comment","source":"MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic","severity":5,"fixable":false,"type":"ERROR","line":545,"column":12}]}}}

--- end ---
 * sniff MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic is now failing
 * sniff MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate is now failing
 * sniff MediaWiki.Usage.ForbiddenFunctions.exec is now failing
 * sniff MediaWiki.Commenting.FunctionComment.MissingReturn is now failing
 * sniff MediaWiki.Commenting.FunctionComment.MissingParamTag is now failing
 * sniff MediaWiki.Usage.ForbiddenFunctions.escapeshellarg is now failing
$ git checkout .phpcs.xml
--- stderr ---
Updated 0 paths from the index
--- stdout ---

--- end ---
$ /usr/bin/composer install
--- stderr ---
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating autoload files
17 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
--- stdout ---

--- end ---
$ /usr/bin/composer test
--- stderr ---
> parallel-lint . --exclude vendor --exclude node_modules
> phpcs -sp --cache
> minus-x check .
--- stdout ---
PHP 8.4.11 | 10 parallel jobs
......                                                       6/6 (100%)


Checked 6 files in 0.1 seconds
No syntax error found
...... 6 / 6 (100%)


Time: 108ms; Memory: 8MB

MinusX
======
Processing /src/repo...
.........................
All good!

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "brace-expansion": {
      "name": "brace-expansion",
      "severity": "low",
      "isDirect": false,
      "via": [
        {
          "source": 1105443,
          "name": "brace-expansion",
          "dependency": "brace-expansion",
          "title": "brace-expansion Regular Expression Denial of Service vulnerability",
          "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw",
          "severity": "low",
          "cwe": [
            "CWE-400"
          ],
          "cvss": {
            "score": 3.1,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": ">=1.0.0 <=1.1.11"
        },
        {
          "source": 1105444,
          "name": "brace-expansion",
          "dependency": "brace-expansion",
          "title": "brace-expansion Regular Expression Denial of Service vulnerability",
          "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw",
          "severity": "low",
          "cwe": [
            "CWE-400"
          ],
          "cvss": {
            "score": 3.1,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": ">=2.0.0 <=2.0.1"
        }
      ],
      "effects": [],
      "range": "1.0.0 - 1.1.11 || 2.0.0 - 2.0.1",
      "nodes": [
        "node_modules/brace-expansion",
        "node_modules/eslint-plugin-n/node_modules/brace-expansion"
      ],
      "fixAvailable": true
    },
    "cross-spawn": {
      "name": "cross-spawn",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1104664,
          "name": "cross-spawn",
          "dependency": "cross-spawn",
          "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn",
          "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275",
          "severity": "high",
          "cwe": [
            "CWE-1333"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": ">=7.0.0 <7.0.5"
        }
      ],
      "effects": [],
      "range": "7.0.0 - 7.0.4",
      "nodes": [
        "node_modules/cross-spawn"
      ],
      "fixAvailable": true
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 1,
      "moderate": 0,
      "high": 1,
      "critical": 0,
      "total": 2
    },
    "dependencies": {
      "prod": 1,
      "dev": 428,
      "optional": 0,
      "peer": 1,
      "peerOptional": 0,
      "total": 428
    }
  }
}

--- end ---
Attempting to npm audit fix
$ /usr/bin/npm audit fix --dry-run --only=dev --json
--- stderr ---
npm WARN invalid config only="dev" set in command line options
npm WARN invalid config Must be one of: null, prod, production
--- stdout ---
{
  "added": 0,
  "removed": 0,
  "changed": 3,
  "audited": 429,
  "funding": 105,
  "audit": {
    "auditReportVersion": 2,
    "vulnerabilities": {
      "brace-expansion": {
        "name": "brace-expansion",
        "severity": "low",
        "isDirect": false,
        "via": [
          {
            "source": 1105443,
            "name": "brace-expansion",
            "dependency": "brace-expansion",
            "title": "brace-expansion Regular Expression Denial of Service vulnerability",
            "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw",
            "severity": "low",
            "cwe": [
              "CWE-400"
            ],
            "cvss": {
              "score": 3.1,
              "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"
            },
            "range": ">=1.0.0 <=1.1.11"
          },
          {
            "source": 1105444,
            "name": "brace-expansion",
            "dependency": "brace-expansion",
            "title": "brace-expansion Regular Expression Denial of Service vulnerability",
            "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw",
            "severity": "low",
            "cwe": [
              "CWE-400"
            ],
            "cvss": {
              "score": 3.1,
              "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"
            },
            "range": ">=2.0.0 <=2.0.1"
          }
        ],
        "effects": [],
        "range": "1.0.0 - 1.1.11 || 2.0.0 - 2.0.1",
        "nodes": [
          "",
          ""
        ],
        "fixAvailable": true
      },
      "cross-spawn": {
        "name": "cross-spawn",
        "severity": "high",
        "isDirect": false,
        "via": [
          {
            "source": 1104664,
            "name": "cross-spawn",
            "dependency": "cross-spawn",
            "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn",
            "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275",
            "severity": "high",
            "cwe": [
              "CWE-1333"
            ],
            "cvss": {
              "score": 7.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
            },
            "range": ">=7.0.0 <7.0.5"
          }
        ],
        "effects": [],
        "range": "7.0.0 - 7.0.4",
        "nodes": [
          ""
        ],
        "fixAvailable": true
      }
    },
    "metadata": {
      "vulnerabilities": {
        "info": 0,
        "low": 1,
        "moderate": 0,
        "high": 1,
        "critical": 0,
        "total": 2
      },
      "dependencies": {
        "prod": 1,
        "dev": 428,
        "optional": 0,
        "peer": 1,
        "peerOptional": 0,
        "total": 428
      }
    }
  }
}

--- end ---
{"added": 0, "removed": 0, "changed": 3, "audited": 429, "funding": 105, "audit": {"auditReportVersion": 2, "vulnerabilities": {"brace-expansion": {"name": "brace-expansion", "severity": "low", "isDirect": false, "via": [{"source": 1105443, "name": "brace-expansion", "dependency": "brace-expansion", "title": "brace-expansion Regular Expression Denial of Service vulnerability", "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw", "severity": "low", "cwe": ["CWE-400"], "cvss": {"score": 3.1, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"}, "range": ">=1.0.0 <=1.1.11"}, {"source": 1105444, "name": "brace-expansion", "dependency": "brace-expansion", "title": "brace-expansion Regular Expression Denial of Service vulnerability", "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw", "severity": "low", "cwe": ["CWE-400"], "cvss": {"score": 3.1, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"}, "range": ">=2.0.0 <=2.0.1"}], "effects": [], "range": "1.0.0 - 1.1.11 || 2.0.0 - 2.0.1", "nodes": ["", ""], "fixAvailable": true}, "cross-spawn": {"name": "cross-spawn", "severity": "high", "isDirect": false, "via": [{"source": 1104664, "name": "cross-spawn", "dependency": "cross-spawn", "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn", "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275", "severity": "high", "cwe": ["CWE-1333"], "cvss": {"score": 7.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}, "range": ">=7.0.0 <7.0.5"}], "effects": [], "range": "7.0.0 - 7.0.4", "nodes": [""], "fixAvailable": true}}, "metadata": {"vulnerabilities": {"info": 0, "low": 1, "moderate": 0, "high": 1, "critical": 0, "total": 2}, "dependencies": {"prod": 1, "dev": 428, "optional": 0, "peer": 1, "peerOptional": 0, "total": 428}}}}
$ /usr/bin/npm audit fix --only=dev
--- stderr ---
npm WARN invalid config only="dev" set in command line options
npm WARN invalid config Must be one of: null, prod, production
--- stdout ---

up to date, audited 429 packages in 1s

105 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

--- end ---
Verifying that tests still pass
$ /usr/bin/npm ci
--- stdout ---

added 428 packages, and audited 429 packages in 4s

105 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

--- end ---
$ /usr/bin/npm test
--- stdout ---

> test
> grunt test

Running "eslint:all" (eslint) task

/src/repo/Gruntfile.js
  3:2  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

/src/repo/libs/floating-chat.js
    6:3   warning  Unexpected console statement                 no-console
    8:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   14:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   19:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   24:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   25:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   27:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   36:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   40:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   46:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   51:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   71:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   72:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   88:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  105:15  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

/src/repo/libs/index.js
   2:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   3:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
   6:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  15:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  19:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  24:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  29:3   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  37:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  38:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  54:7   warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables
  71:15  warning  ES2015 block-scoped variables are forbidden  es-x/no-block-scoped-variables

✖ 27 problems (0 errors, 27 warnings)


Running "banana:Wanda" (banana) task
>> 1 message directory checked.

Done.

--- end ---
{"1105443": {"source": 1105443, "name": "brace-expansion", "dependency": "brace-expansion", "title": "brace-expansion Regular Expression Denial of Service vulnerability", "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw", "severity": "low", "cwe": ["CWE-400"], "cvss": {"score": 3.1, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"}, "range": ">=1.0.0 <=1.1.11"}, "1105444": {"source": 1105444, "name": "brace-expansion", "dependency": "brace-expansion", "title": "brace-expansion Regular Expression Denial of Service vulnerability", "url": "https://github.com/advisories/GHSA-v6h2-p8h4-qcjw", "severity": "low", "cwe": ["CWE-400"], "cvss": {"score": 3.1, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L"}, "range": ">=2.0.0 <=2.0.1"}}
Upgrading n:brace-expansion from 1.1.11, 2.0.1, 2.0.2 -> 1.1.12, 2.0.2
{"1104664": {"source": 1104664, "name": "cross-spawn", "dependency": "cross-spawn", "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn", "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275", "severity": "high", "cwe": ["CWE-1333"], "cvss": {"score": 7.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}, "range": ">=7.0.0 <7.0.5"}}
Upgrading n:cross-spawn from 7.0.3 -> 7.0.6
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
build: Updating dependencies

composer:
* mediawiki/mediawiki-codesniffer: 46.0.0 → 48.0.0
  The following sniffs are failing and were disabled:
  * MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate
  * MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic
  * MediaWiki.Commenting.FunctionComment.MissingParamTag
  * MediaWiki.Commenting.FunctionComment.MissingReturn
  * MediaWiki.Usage.ForbiddenFunctions.escapeshellarg
  * MediaWiki.Usage.ForbiddenFunctions.exec

* mediawiki/mediawiki-phan-config: 0.15.1 → 0.17.0

npm:
* eslint-config-wikimedia: 0.28.2 → 0.31.0
  The following rules are failing and were disabled:
  * no-console
* brace-expansion: 1.1.11, 2.0.1, 2.0.2 → 1.1.12, 2.0.2
  * https://github.com/advisories/GHSA-v6h2-p8h4-qcjw
* cross-spawn: 7.0.3 → 7.0.6
  * https://github.com/advisories/GHSA-3xgq-45jj-v275


Additional changes:
* eslint: Replaced `wikimedia/client-es5` with `wikimedia/client`.
* Added .stylelintcache to .gitignore.

$ git add .
--- stdout ---

--- end ---
$ git commit -F /tmp/tmp8220ymmh
--- stdout ---
[master 1ef92bf] build: Updating dependencies
 11 files changed, 319 insertions(+), 120 deletions(-)

--- end ---
$ git format-patch HEAD~1 --stdout
--- stdout ---
From 1ef92bfa98e0bcf1f80ad6cb46cef07d3a771239 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Sat, 4 Oct 2025 05:12:57 +0000
Subject: [PATCH] build: Updating dependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

composer:
* mediawiki/mediawiki-codesniffer: 46.0.0 → 48.0.0
  The following sniffs are failing and were disabled:
  * MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate
  * MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic
  * MediaWiki.Commenting.FunctionComment.MissingParamTag
  * MediaWiki.Commenting.FunctionComment.MissingReturn
  * MediaWiki.Usage.ForbiddenFunctions.escapeshellarg
  * MediaWiki.Usage.ForbiddenFunctions.exec

* mediawiki/mediawiki-phan-config: 0.15.1 → 0.17.0

npm:
* eslint-config-wikimedia: 0.28.2 → 0.31.0
  The following rules are failing and were disabled:
  * no-console
* brace-expansion: 1.1.11, 2.0.1, 2.0.2 → 1.1.12, 2.0.2
  * https://github.com/advisories/GHSA-v6h2-p8h4-qcjw
* cross-spawn: 7.0.3 → 7.0.6
  * https://github.com/advisories/GHSA-3xgq-45jj-v275

Additional changes:
* eslint: Replaced `wikimedia/client-es5` with `wikimedia/client`.
* Added .stylelintcache to .gitignore.

Change-Id: If939e749cf526bd34cc88bca2c9a8a716ecbdb4f
---
 .eslintrc.json                      |   5 +-
 .gitignore                          |   3 +-
 .phpcs.xml                          |   9 +-
 Gruntfile.js                        |   2 +-
 composer.json                       |  30 +--
 includes/APIChat.php                |  14 +-
 includes/Hooks/FloatingChatHook.php |   2 +-
 libs/floating-chat.js               |  46 ++---
 libs/index.js                       |  26 +--
 package-lock.json                   | 300 +++++++++++++++++++++++-----
 package.json                        |   2 +-
 11 files changed, 319 insertions(+), 120 deletions(-)

diff --git a/.eslintrc.json b/.eslintrc.json
index 5b39bf7..b1fc2a1 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -4,7 +4,7 @@
 		"ecmaVersion": 6
 	},
 	"extends": [
-		"wikimedia/client-es5",
+		"wikimedia/client",
 		"wikimedia/jquery",
 		"wikimedia/mediawiki"
 	],
@@ -98,6 +98,7 @@
 		"es-x/no-default-parameters": "warn",
 		"es-x/no-for-of-loops": "warn",
 		"es-x/no-string-prototype-includes": "warn",
-		"es-x/no-template-literals": "off"
+		"es-x/no-template-literals": "off",
+		"no-console": "warn"
 	}
 }
diff --git a/.gitignore b/.gitignore
index c38c5c5..917bedd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 node_modules/
 /vendor/
 .eslintcache
-composer.lock
\ No newline at end of file
+composer.lock
+/.stylelintcache
diff --git a/.phpcs.xml b/.phpcs.xml
index e6082cf..a10abcf 100644
--- a/.phpcs.xml
+++ b/.phpcs.xml
@@ -1,6 +1,13 @@
 <?xml version="1.0"?>
 <ruleset>
-	<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki" />
+	<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki">
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate" />
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic" />
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingParamTag" />
+		<exclude name="MediaWiki.Commenting.FunctionComment.MissingReturn" />
+		<exclude name="MediaWiki.Usage.ForbiddenFunctions.escapeshellarg" />
+		<exclude name="MediaWiki.Usage.ForbiddenFunctions.exec" />
+	</rule>
 	<file>.</file>
 	<arg name="extensions" value="php" />
 	<arg name="encoding" value="UTF-8" />
diff --git a/Gruntfile.js b/Gruntfile.js
index edfe9e8..c393b18 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,6 +1,6 @@
 /* eslint-env node, es6 */
 module.exports = function ( grunt ) {
-	var conf = grunt.file.readJSON( 'extension.json' );
+	const conf = grunt.file.readJSON( 'extension.json' );
 	grunt.loadNpmTasks( 'grunt-banana-checker' );
 	grunt.loadNpmTasks( 'grunt-eslint' );
 	grunt.initConfig( {
diff --git a/composer.json b/composer.json
index 1ea426a..f533bbb 100644
--- a/composer.json
+++ b/composer.json
@@ -1,16 +1,16 @@
 {
-    "name": "wikiworks/wanda",
-    "description": "Chatbot for MediaWiki",
-    "type": "project",
-    "authors": [
-        {
-            "name": "Sanjay Thiyagarajan",
-            "email": "sanjayipscoc@gmail.com"
-        }
-    ],
-    "require-dev": {
-		"mediawiki/mediawiki-codesniffer": "46.0.0",
-		"mediawiki/mediawiki-phan-config": "0.15.1",
+	"name": "wikiworks/wanda",
+	"description": "Chatbot for MediaWiki",
+	"type": "project",
+	"authors": [
+		{
+			"name": "Sanjay Thiyagarajan",
+			"email": "sanjayipscoc@gmail.com"
+		}
+	],
+	"require-dev": {
+		"mediawiki/mediawiki-codesniffer": "48.0.0",
+		"mediawiki/mediawiki-phan-config": "0.17.0",
 		"mediawiki/minus-x": "1.1.3",
 		"php-parallel-lint/php-console-highlighter": "1.0.0",
 		"php-parallel-lint/php-parallel-lint": "1.4.0"
@@ -30,8 +30,8 @@
 	},
 	"config": {
 		"allow-plugins": {
-            "dealerdirect/phpcodesniffer-composer-installer": true,
-            "composer/installers": true
-        }
+			"dealerdirect/phpcodesniffer-composer-installer": true,
+			"composer/installers": true
+		}
 	}
 }
diff --git a/includes/APIChat.php b/includes/APIChat.php
index c847845..6232797 100644
--- a/includes/APIChat.php
+++ b/includes/APIChat.php
@@ -86,7 +86,7 @@ class APIChat extends ApiBase {
 		}
 
 		// Prepare source data
-		$sourceData = array_map( function( $result ) {
+		$sourceData = array_map( static function ( $result ) {
 			return isset( $result['_source']['page_title'] ) ? $result['_source']['page_title'] : 'Unknown source';
 		}, $searchResults );
 
@@ -149,7 +149,7 @@ class APIChat extends ApiBase {
 
 	private function queryElasticsearch( $queryText ) {
 		$queryEmbedding = $this->generateEmbedding( $queryText );
-		
+
 		if ( $queryEmbedding && isset( $queryEmbedding['embeddings'][0] ) ) {
 			// Use vector similarity search
 			return $this->vectorSearch( $queryEmbedding['embeddings'][0] );
@@ -206,7 +206,7 @@ class APIChat extends ApiBase {
 			"query" => [
 				"multi_match" => [
 					"query" => $queryText,
-					"fields" => ["title^2", "content"],
+					"fields" => [ "title^2", "content" ],
 					"type" => "best_fields",
 					"fuzziness" => "AUTO"
 				]
@@ -395,16 +395,16 @@ class APIChat extends ApiBase {
 	 * Generate response using Ollama
 	 */
 	private function generateOllamaResponse( $prompt ) {
-		$data = json_encode( [ 
-			"model" => self::$llmModel, 
-			"prompt" => $prompt, 
+		$data = json_encode( [
+			"model" => self::$llmModel,
+			"prompt" => $prompt,
 			"stream" => false,
 			"options" => [
 				"temperature" => self::$temperature,
 				"num_predict" => self::$maxTokens
 			]
 		] );
-		
+
 		$ch = curl_init( self::$llmApiEndpoint . "generate" );
 		curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
 		curl_setopt( $ch, CURLOPT_POSTFIELDS, $data );
diff --git a/includes/Hooks/FloatingChatHook.php b/includes/Hooks/FloatingChatHook.php
index e8a3fae..8bda38d 100644
--- a/includes/Hooks/FloatingChatHook.php
+++ b/includes/Hooks/FloatingChatHook.php
@@ -27,7 +27,7 @@ class FloatingChatHook {
 		if ( $out->getTitle() && $out->getTitle()->isSpecial( 'Wanda' ) ) {
 			return;
 		}
-		
+
 		// Add the floating chat module to all other pages
 		$out->addModules( 'ext.wanda.floating' );
 	}
diff --git a/libs/floating-chat.js b/libs/floating-chat.js
index 7fc1a9e..3b8a25e 100644
--- a/libs/floating-chat.js
+++ b/libs/floating-chat.js
@@ -1,30 +1,30 @@
-$(document).ready(function () {
+$(document).ready(() => {
   // Don't load floating chat on the special page itself
   if (mw.config.get('wgCanonicalSpecialPageName') === 'Wanda') {
     return;
   }
   console.log("Initializing floating chat...");
   // Create floating chat button
-  var floatingButton = $('<div>')
+  const floatingButton = $('<div>')
     .addClass('wanda-floating-button')
     .html('💬')
     .attr('title', mw.message( "wanda-floating-chat-title" ).text());
 
   // Create floating chat window
-  var floatingWindow = $('<div>')
+  const floatingWindow = $('<div>')
     .addClass('wanda-floating-window')
     .hide();
 
   // Create chat header
-  var chatHeader = $('<div>')
+  const chatHeader = $('<div>')
     .addClass('wanda-chat-header')
     .html('<span>Wanda AI Assistant</span><button class="wanda-close-btn">×</button>');
 
   // Create chat container (reuse existing chat functionality)
-  var chatContainer = $('<div>').addClass('chat-container wanda-floating-chat');
-  var chatBox = $('<div>').addClass('chat-box');
+  const chatContainer = $('<div>').addClass('chat-container wanda-floating-chat');
+  const chatBox = $('<div>').addClass('chat-box');
 
-  var instructionScreen = $('<div>').addClass('chat-instructions').html(`
+  const instructionScreen = $('<div>').addClass('chat-instructions').html(`
       <h2>${ mw.message( "wanda-chat-welcometext" ).text() }</h2>
       <p>${ mw.message( "wanda-chat-welcomedesc" ).text() }</p>
       <ul>
@@ -33,22 +33,22 @@ $(document).ready(function () {
       </ul>
   `);
 
-  var progressBar = new OO.ui.ProgressBarWidget({
+  const progressBar = new OO.ui.ProgressBarWidget({
       progress: false
   }).$element.addClass('chat-progress-bar').hide();
 
-  var chatInput = new OO.ui.MultilineTextInputWidget({
+  const chatInput = new OO.ui.MultilineTextInputWidget({
       placeholder: 'Type your message...',
       classes: [ 'chat-input-box' ],
       autosize: true,
       rows: 2
   });
-  var sendButton = new OO.ui.ButtonWidget({
+  const sendButton = new OO.ui.ButtonWidget({
       flags: [ 'primary', 'progressive' ],
       label: "Send"
   });
 
-  var inputContainer = $('<div>').addClass('chat-input-container');
+  const inputContainer = $('<div>').addClass('chat-input-container');
   inputContainer.append(chatInput.$element).append(sendButton.$element);
 
   chatContainer.append(chatBox).append(instructionScreen).append(progressBar).append(inputContainer);
@@ -68,8 +68,8 @@ $(document).ready(function () {
   function addMessage(role, text) {
       instructionScreen.hide();
       chatBox.show(); // Ensure chat box is visible when adding messages
-      var msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');
-      var msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);
+      const msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');
+      const msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);
 
       msgWrapper.append(msgBubble);
       chatBox.append(msgWrapper);
@@ -85,7 +85,7 @@ $(document).ready(function () {
   }
 
   function sendMessage() {
-      var userText = chatInput.getValue().trim();
+      const userText = chatInput.getValue().trim();
       if (!userText) return;
       chatBox.show();
 
@@ -102,7 +102,7 @@ $(document).ready(function () {
           },
           dataType: 'json',
           success: function (data) {
-              var response = data.response || 'Error fetching response';
+              let response = data.response || 'Error fetching response';
               response = response + "<br><b>Source</b>: " + data.source;
               addMessage('bot', response);
           },
@@ -116,22 +116,22 @@ $(document).ready(function () {
   }
 
   // Event handlers
-  floatingButton.on('click', function() {
+  floatingButton.on('click', () => {
     floatingWindow.show();
     // Add show class after a small delay to trigger animation
-    setTimeout(function() {
+    setTimeout(() => {
       floatingWindow.addClass('show');
     }, 10);
     floatingButton.hide();
     // Focus on input if window is opened
-    setTimeout(function() {
+    setTimeout(() => {
       chatInput.focus();
     }, 100);
   });
 
-  chatHeader.find('.wanda-close-btn').on('click', function() {
+  chatHeader.find('.wanda-close-btn').on('click', () => {
     floatingWindow.removeClass('show');
-    setTimeout(function() {
+    setTimeout(() => {
       floatingWindow.hide();
       floatingButton.show();
     }, 300);
@@ -139,7 +139,7 @@ $(document).ready(function () {
 
   sendButton.on('click', sendMessage);
 
-  chatInput.$element.on('keydown', function (e) {
+  chatInput.$element.on('keydown', (e) => {
       if (e.which === 13 && !e.shiftKey) {
           e.preventDefault();
           sendMessage();
@@ -152,11 +152,11 @@ $(document).ready(function () {
   }
 
   // Click outside to close
-  $(document).on('click', function(e) {
+  $(document).on('click', (e) => {
     if (!$(e.target).closest('.wanda-floating-window, .wanda-floating-button').length) {
       if (floatingWindow.is(':visible')) {
         floatingWindow.removeClass('show');
-        setTimeout(function() {
+        setTimeout(() => {
           floatingWindow.hide();
           floatingButton.show();
         }, 300);
diff --git a/libs/index.js b/libs/index.js
index 32ce5c9..4a0a648 100644
--- a/libs/index.js
+++ b/libs/index.js
@@ -1,9 +1,9 @@
-$(document).ready(function () {
-  var chatContainer = $('<div>').addClass('chat-container');
-  var chatBox = $('<div>').addClass('chat-box');
+$(document).ready(() => {
+  const chatContainer = $('<div>').addClass('chat-container');
+  const chatBox = $('<div>').addClass('chat-box');
   chatBox.hide();
 
-  var instructionScreen = $('<div>').addClass('chat-instructions').html(`
+  const instructionScreen = $('<div>').addClass('chat-instructions').html(`
       <h2>${ mw.message( "wanda-chat-welcometext" ).text() }</h2>
       <p>${ mw.message( "wanda-chat-welcomedesc" ).text() }</p>
       <ul>
@@ -12,21 +12,21 @@ $(document).ready(function () {
       </ul>
   `);
 
-  var progressBar = new OO.ui.ProgressBarWidget({
+  const progressBar = new OO.ui.ProgressBarWidget({
       progress: false
   }).$element.addClass('chat-progress-bar').hide();
 
-  var chatInput = new OO.ui.MultilineTextInputWidget({
+  const chatInput = new OO.ui.MultilineTextInputWidget({
       placeholder: 'Type your message...',
       classes: [ 'chat-input-box' ],
       autosize: true
   });
-  var sendButton = new OO.ui.ButtonWidget({
+  const sendButton = new OO.ui.ButtonWidget({
       flags: [ 'primary', 'progressive' ],
       label: "Submit"
   });
 
-  var inputContainer = $('<div>').addClass('chat-input-container');
+  const inputContainer = $('<div>').addClass('chat-input-container');
   inputContainer.append(chatInput.$element).append(sendButton.$element);
 
   chatContainer.append(chatBox).append(instructionScreen).append(progressBar).append(inputContainer);
@@ -34,8 +34,8 @@ $(document).ready(function () {
 
   function addMessage(role, text) {
       instructionScreen.hide();
-      var msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');
-      var msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);
+      const msgWrapper = $('<div>').addClass('chat-message-wrapper ' + role + '-wrapper');
+      const msgBubble = $('<div>').addClass('chat-message ' + role + '-message').html(text);
 
       msgWrapper.append(msgBubble);
       chatBox.append(msgWrapper);
@@ -51,7 +51,7 @@ $(document).ready(function () {
   }
 
   function sendMessage() {
-      var userText = chatInput.getValue().trim();
+      const userText = chatInput.getValue().trim();
       if (!userText) return;
       chatBox.show();
 
@@ -68,7 +68,7 @@ $(document).ready(function () {
           },
           dataType: 'json',
           success: function (data) {
-              var response = data.response || 'Error fetching response';
+              let response = data.response || 'Error fetching response';
               response = response + "<br><b>Source</b>: " + data.source;
               addMessage('bot', response);
           },
@@ -83,7 +83,7 @@ $(document).ready(function () {
 
   sendButton.on('click', sendMessage);
 
-  chatInput.$element.on('keydown', function (e) {
+  chatInput.$element.on('keydown', (e) => {
       if (e.which === 13 && !e.shiftKey) {
           e.preventDefault();
           sendMessage();
diff --git a/package-lock.json b/package-lock.json
index 108d9bb..59a1bce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
 		"": {
 			"name": "Wanda",
 			"devDependencies": {
-				"eslint-config-wikimedia": "0.28.2",
+				"eslint-config-wikimedia": "0.31.0",
 				"grunt": "1.6.1",
 				"grunt-banana-checker": "0.13.0",
 				"grunt-eslint": "24.3.0",
@@ -151,9 +151,9 @@
 			}
 		},
 		"node_modules/@eslint-community/eslint-utils": {
-			"version": "4.5.1",
-			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
-			"integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
+			"version": "4.9.0",
+			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+			"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
 			"dev": true,
 			"dependencies": {
 				"eslint-visitor-keys": "^3.4.3"
@@ -292,6 +292,66 @@
 				"node": ">= 8"
 			}
 		},
+		"node_modules/@stylistic/eslint-plugin": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-3.1.0.tgz",
+			"integrity": "sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/utils": "^8.13.0",
+				"eslint-visitor-keys": "^4.2.0",
+				"espree": "^10.3.0",
+				"estraverse": "^5.3.0",
+				"picomatch": "^4.0.2"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"peerDependencies": {
+				"eslint": ">=8.40.0"
+			}
+		},
+		"node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": {
+			"version": "4.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+			"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/@stylistic/eslint-plugin/node_modules/espree": {
+			"version": "10.4.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+			"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+			"dev": true,
+			"dependencies": {
+				"acorn": "^8.15.0",
+				"acorn-jsx": "^5.3.2",
+				"eslint-visitor-keys": "^4.2.1"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
+			"version": "4.0.3",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+			"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+			"dev": true,
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/jonschlinkert"
+			}
+		},
 		"node_modules/@stylistic/stylelint-config": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/@stylistic/stylelint-config/-/stylelint-config-2.0.0.tgz",
@@ -395,14 +455,97 @@
 			"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
 			"dev": true
 		},
+		"node_modules/@typescript-eslint/eslint-plugin": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz",
+			"integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==",
+			"dev": true,
+			"dependencies": {
+				"@eslint-community/regexpp": "^4.10.0",
+				"@typescript-eslint/scope-manager": "8.35.1",
+				"@typescript-eslint/type-utils": "8.35.1",
+				"@typescript-eslint/utils": "8.35.1",
+				"@typescript-eslint/visitor-keys": "8.35.1",
+				"graphemer": "^1.4.0",
+				"ignore": "^7.0.0",
+				"natural-compare": "^1.4.0",
+				"ts-api-utils": "^2.1.0"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"@typescript-eslint/parser": "^8.35.1",
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
+		"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+			"version": "7.0.5",
+			"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+			"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+			"dev": true,
+			"engines": {
+				"node": ">= 4"
+			}
+		},
+		"node_modules/@typescript-eslint/parser": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz",
+			"integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/scope-manager": "8.35.1",
+				"@typescript-eslint/types": "8.35.1",
+				"@typescript-eslint/typescript-estree": "8.35.1",
+				"@typescript-eslint/visitor-keys": "8.35.1",
+				"debug": "^4.3.4"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
+		"node_modules/@typescript-eslint/project-service": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz",
+			"integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/tsconfig-utils": "^8.35.1",
+				"@typescript-eslint/types": "^8.35.1",
+				"debug": "^4.3.4"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
 		"node_modules/@typescript-eslint/scope-manager": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz",
-			"integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz",
+			"integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.26.1",
-				"@typescript-eslint/visitor-keys": "8.26.1"
+				"@typescript-eslint/types": "8.35.1",
+				"@typescript-eslint/visitor-keys": "8.35.1"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -412,10 +555,49 @@
 				"url": "https://opencollective.com/typescript-eslint"
 			}
 		},
+		"node_modules/@typescript-eslint/tsconfig-utils": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz",
+			"integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
+		"node_modules/@typescript-eslint/type-utils": {
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz",
+			"integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==",
+			"dev": true,
+			"dependencies": {
+				"@typescript-eslint/typescript-estree": "8.35.1",
+				"@typescript-eslint/utils": "8.35.1",
+				"debug": "^4.3.4",
+				"ts-api-utils": "^2.1.0"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			},
+			"peerDependencies": {
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.9.0"
+			}
+		},
 		"node_modules/@typescript-eslint/types": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz",
-			"integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz",
+			"integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -426,19 +608,21 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz",
-			"integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz",
+			"integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.26.1",
-				"@typescript-eslint/visitor-keys": "8.26.1",
+				"@typescript-eslint/project-service": "8.35.1",
+				"@typescript-eslint/tsconfig-utils": "8.35.1",
+				"@typescript-eslint/types": "8.35.1",
+				"@typescript-eslint/visitor-keys": "8.35.1",
 				"debug": "^4.3.4",
 				"fast-glob": "^3.3.2",
 				"is-glob": "^4.0.3",
 				"minimatch": "^9.0.4",
 				"semver": "^7.6.0",
-				"ts-api-utils": "^2.0.1"
+				"ts-api-utils": "^2.1.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -452,9 +636,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+			"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
 			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0"
@@ -476,15 +660,15 @@
 			}
 		},
 		"node_modules/@typescript-eslint/utils": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz",
-			"integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz",
+			"integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==",
 			"dev": true,
 			"dependencies": {
-				"@eslint-community/eslint-utils": "^4.4.0",
-				"@typescript-eslint/scope-manager": "8.26.1",
-				"@typescript-eslint/types": "8.26.1",
-				"@typescript-eslint/typescript-estree": "8.26.1"
+				"@eslint-community/eslint-utils": "^4.7.0",
+				"@typescript-eslint/scope-manager": "8.35.1",
+				"@typescript-eslint/types": "8.35.1",
+				"@typescript-eslint/typescript-estree": "8.35.1"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -499,13 +683,13 @@
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys": {
-			"version": "8.26.1",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz",
-			"integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==",
+			"version": "8.35.1",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz",
+			"integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.26.1",
-				"eslint-visitor-keys": "^4.2.0"
+				"@typescript-eslint/types": "8.35.1",
+				"eslint-visitor-keys": "^4.2.1"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -516,9 +700,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
-			"version": "4.2.0",
-			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
-			"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+			"version": "4.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+			"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -540,9 +724,9 @@
 			"dev": true
 		},
 		"node_modules/acorn": {
-			"version": "8.14.1",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
-			"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+			"version": "8.15.0",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+			"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
 			"dev": true,
 			"bin": {
 				"acorn": "bin/acorn"
@@ -717,9 +901,9 @@
 			"dev": true
 		},
 		"node_modules/brace-expansion": {
-			"version": "1.1.11",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"version": "1.1.12",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+			"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
 			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0",
@@ -1020,9 +1204,9 @@
 			}
 		},
 		"node_modules/cross-spawn": {
-			"version": "7.0.3",
-			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
-			"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+			"version": "7.0.6",
+			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+			"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
 			"dev": true,
 			"dependencies": {
 				"path-key": "^3.1.0",
@@ -1410,11 +1594,14 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.28.2",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz",
-			"integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==",
+			"version": "0.31.0",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.31.0.tgz",
+			"integrity": "sha512-Z/t/zGPdxs/ehxb0EM6THNWAzueT7GtuqzjUvmBpkxcTKzZPJEXWnnpswdj/hgv8Ce8PIeDp0zwQxR4e3c9CIw==",
 			"dev": true,
 			"dependencies": {
+				"@stylistic/eslint-plugin": "^3.1.0",
+				"@typescript-eslint/eslint-plugin": "8.35.1",
+				"@typescript-eslint/parser": "8.35.1",
 				"browserslist-config-wikimedia": "^0.7.0",
 				"eslint": "^8.57.0",
 				"eslint-plugin-compat": "^4.2.0",
@@ -1425,13 +1612,16 @@
 				"eslint-plugin-mediawiki": "^0.7.0",
 				"eslint-plugin-mocha": "^10.4.3",
 				"eslint-plugin-n": "^17.7.0",
-				"eslint-plugin-no-jquery": "^3.0.1",
+				"eslint-plugin-no-jquery": "^3.1.1",
 				"eslint-plugin-qunit": "^8.1.1",
 				"eslint-plugin-security": "^1.7.1",
 				"eslint-plugin-unicorn": "^53.0.0",
 				"eslint-plugin-vue": "^9.26.0",
 				"eslint-plugin-wdio": "^8.24.12",
 				"eslint-plugin-yml": "^1.14.0"
+			},
+			"engines": {
+				"node": ">=18 <25"
 			}
 		},
 		"node_modules/eslint-plugin-compat": {
@@ -1593,9 +1783,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-n/node_modules/brace-expansion": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+			"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
 			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0"
@@ -4814,9 +5004,9 @@
 			}
 		},
 		"node_modules/ts-api-utils": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
-			"integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+			"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
 			"dev": true,
 			"engines": {
 				"node": ">=18.12"
diff --git a/package.json b/package.json
index 244f264..e21a36c 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
 		"test": "grunt test"
 	},
 	"devDependencies": {
-		"eslint-config-wikimedia": "0.28.2",
+		"eslint-config-wikimedia": "0.31.0",
 		"grunt": "1.6.1",
 		"grunt-banana-checker": "0.13.0",
 		"grunt-eslint": "24.3.0",
-- 
2.47.3


--- end ---
Source code is licensed under the AGPL.