format-code: Print tool versions and describe version policy

The updated output improves our ability to spot differences in tools
across CI worker containers:

```
$ ./openbmc-build-scripts/scripts/format-code.sh ./libpldm/origin/
    Formatting code under .../libpldm/origin
    markdownlint: cannot find .markdownlint.yaml; using .../openbmc-build-scripts/config/markdownlint.yaml
    prettier: cannot find .prettierrc.yaml; using .../openbmc-build-scripts/config/prettierrc.yaml
    Running commit_gitlint (0.19.1)
    Running commit_spelling (codespell: 2.4.1)
codespell-dictionary - misspelling count >> 0
generic-dictionary - misspelling count >> 0
    Running beautysh (6.2.1)
    Running black (black, 25.1.0 (compiled: yes))
All done! ✨ 🍰 ✨
2 files left unchanged.
    Running clang_format (Ubuntu clang-format version 20.1.7 (++20250613062657+6146a88f6049-1~exp1~20250613062712.130))
    Running clang_tidy (openbmc-build-scripts: effec66f389b614089f46e1ae5b41ffa05c3e817)
    Running flake8 (7.2.0 (mccabe: 0.7.0, pycodestyle: 2.13.0, pyflakes: 3.3.2) CPython 3.12.7 on Linux)
    Running isort (6.0.1)
    Running markdownlint (0.45.0)
    Running meson (1.7.0)
    Running prettier (3.5.3)
    ...
    Running shellcheck (0.10.0)
    Result differences...
Format: PASSED
```

Change-Id: I1a14b5ba765a794f2aec37302fc35173a13ba4e7
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
diff --git a/README.md b/README.md
index 39900d0..bca948b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,28 @@
 # openbmc-build-scripts
 
 Build script for CI jobs in Jenkins.
+
+## Linter policy and related build failures
+
+Formatting linters sometimes change stylistic output across releases.
+Separately, [some linters are not version-pinned in the CI
+container][no-pin-policy], as pinning would drive either frequent maintenance
+with upgrades or stagnation of the code-base against older versions.
+
+The combination may result in inconsistent formatting opinions across CI worker
+nodes[^1].
+
+If you see such behaviour consider [changing the thing][force-container-refresh]
+to force a container refresh.
+
+[no-pin-policy]:
+  https://discord.com/channels/775381525260664832/867820390406422538/1387500393243869265
+[force-container-refresh]:
+  https://github.com/openbmc/openbmc-build-scripts/commit/a1cbd4041f94193e1c43e767156c8a2dd117b99d
+
+[^1]:
+    The collection of container builds across all worker nodes may not hold a
+    consistent set of tool versions despite being built from the same
+    specification: The inconsistencies emerge from the cadence of upstream tool
+    package updates beating against the cadence of container rebuilds on the
+    worker nodes.
diff --git a/scripts/format-code.sh b/scripts/format-code.sh
index f49a027..1337b2f 100755
--- a/scripts/format-code.sh
+++ b/scripts/format-code.sh
@@ -196,6 +196,9 @@
     echo -n "generic-dictionary - misspelling count >> "
     codespell --builtin clear,rare,en-GB_to_en-US -d --count "$commit_filename"
 }
+function do_version_commit_spelling() {
+    echo codespell: "$(codespell --version)"
+}
 
 LINTER_REQUIRE+=([commit_gitlint]="gitlint")
 LINTER_TYPES+=([commit_gitlint]="commit")
@@ -203,6 +206,9 @@
     gitlint --extra-path "${CONFIG_PATH}/gitlint/" \
         --config "${CONFIG_PATH}/.gitlint"
 }
+function do_version_commit_gitlint() {
+    gitlint --version | awk '{ print $3 }'
+}
 
 # We need different function style for bash/zsh vs plain sh, so beautysh is
 # split into two linters.  "function foo()" is not traditionally accepted
@@ -213,18 +219,27 @@
 function do_beautysh() {
     beautysh --force-function-style fnpar "$@"
 }
+function do_version_beautysh() {
+    beautysh --version
+}
 LINTER_REQUIRE+=([beautysh_sh]="beautysh")
 LINTER_IGNORE+=([beautysh_sh]=".beautysh-ignore")
 LINTER_TYPES+=([beautysh_sh]="sh")
 function do_beautysh_sh() {
     beautysh --force-function-style paronly "$@"
 }
+function do_version_beautysh_sh() {
+    beautysh --version
+}
 
 LINTER_REQUIRE+=([black]="black")
 LINTER_TYPES+=([black]="python")
 function do_black() {
     black -l 79 "$@"
 }
+function do_version_black() {
+    black --version | head -n1
+}
 
 LINTER_REQUIRE+=([eslint]="eslint;.eslintrc.json;${CONFIG_PATH}/eslint-global-config.json")
 LINTER_IGNORE+=([eslint]=".eslintignore")
@@ -235,6 +250,9 @@
         --resolve-plugins-relative-to /usr/local/lib/node_modules \
         --no-error-on-unmatched-pattern "$@"
 }
+function do_version_eslint() {
+    eslint --version
+}
 
 LINTER_REQUIRE+=([flake8]="flake8")
 LINTER_IGNORE+=([flake8]=".flake8-ignore")
@@ -244,12 +262,18 @@
     # We disable E203 and E501 because 'black' is handling these and they
     # disagree on best practices.
 }
+function do_version_flake8() {
+    flake8 --version
+}
 
 LINTER_REQUIRE+=([isort]="isort")
 LINTER_TYPES+=([isort]="python")
 function do_isort() {
     isort --profile black "$@"
 }
+function do_version_isort() {
+    isort --version-number
+}
 
 LINTER_REQUIRE+=([markdownlint]="markdownlint;.markdownlint.yaml;${CONFIG_PATH}/markdownlint.yaml")
 LINTER_IGNORE+=([markdownlint]=".markdownlint-ignore")
@@ -257,12 +281,18 @@
 function do_markdownlint() {
     markdownlint --config "${LINTER_CONFIG[markdownlint]}" -- "$@"
 }
+function do_version_markdownlint() {
+    markdownlint --version
+}
 
 LINTER_REQUIRE+=([meson]="meson;meson.build")
 LINTER_TYPES+=([meson]="meson")
 function do_meson() {
     meson format -i "$@"
 }
+function do_version_meson() {
+    meson --version
+}
 
 LINTER_REQUIRE+=([prettier]="prettier;.prettierrc.yaml;${CONFIG_PATH}/prettierrc.yaml")
 LINTER_IGNORE+=([prettier]=".prettierignore")
@@ -270,6 +300,9 @@
 function do_prettier() {
     prettier --config "${LINTER_CONFIG[prettier]}" --write "$@"
 }
+function do_version_prettier() {
+    prettier --version
+}
 
 LINTER_REQUIRE+=([shellcheck]="shellcheck")
 LINTER_IGNORE+=([shellcheck]=".shellcheck-ignore")
@@ -277,6 +310,9 @@
 function do_shellcheck() {
     shellcheck --color=never -x "$@"
 }
+function do_version_shellcheck() {
+    shellcheck --version | awk '/^version/ { print $2 }'
+}
 
 LINTER_REQUIRE+=([clang_format]="clang-format;.clang-format")
 LINTER_IGNORE+=([clang_format]=".clang-ignore;.clang-format-ignore")
@@ -284,12 +320,18 @@
 function do_clang_format() {
     "${CLANG_FORMAT}" -i "$@"
 }
+function do_version_clang_format() {
+    "${CLANG_FORMAT}" --version
+}
 
 LINTER_REQUIRE+=([clang_tidy]="true")
 LINTER_TYPES+=([clang_tidy]="clang-tidy-config")
 function do_clang_tidy() {
     "${TOOLS_PATH}/config-clang-tidy" format
 }
+function do_version_clang_tidy() {
+    echo openbmc-build-scripts: "$(git rev-parse HEAD)"
+}
 
 function get_file_type()
 {
@@ -429,7 +471,7 @@
 
     # Call the linter now with all the files.
     if [ 0 -ne ${#LINTER_FILES[@]} ]; then
-        echo -e "    ${BLUE}Running $op${NORMAL}"
+        echo -e "    ${BLUE}Running $op${NORMAL} ($(do_version_"$op"))"
         if ! "do_$op" "${LINTER_FILES[@]}" ; then
             LINTERS_FAILED+=([$op]=1)
             echo -e "    ${RED}$op - FAILED${NORMAL}"