This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 959c7e87610e chore(ci): rationalize CI into single workflow with 
unified comment (#22247)
959c7e87610e is described below

commit 959c7e87610e121dc0e4d80acf931b8fdc75ac3f
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Apr 8 09:08:28 2026 +0200

    chore(ci): rationalize CI into single workflow with unified comment (#22247)
    
    Consolidate CI test infrastructure into a single incremental-build
    action with a unified PR comment, replacing the previous scattered
    approach with separate component-test and detect-dependencies actions.
    
    Key changes:
    
    - Merge component-test and detect-dependencies actions into
      incremental-build, which now handles file-path analysis, POM
      dependency detection, and /component-test extra modules in one script
    
    - Add POM dependency analysis: when parent/pom.xml properties change,
      grep all module pom.xml files for references to the changed properties
      and include those modules in the test set
    
    - Add pr-test-commenter.yml workflow: posts CI test summary comments
      using workflow_run trigger so it works on fork PRs (where GITHUB_TOKEN
      is read-only)
    
    - Refactor pr-manual-component-test.yml: instead of running its own
      build, it now resolves component paths and dispatches the main
      "Build and test" workflow with extra_modules and skip_full_build
    
    - Add manual-it-mapping.txt: maps source modules to their associated
      integration test suites. When a changed module is in the exclusion
      list but has a mapping entry, CI advises contributors to run the
      integration tests manually
    
    - Add CI-ARCHITECTURE.md documenting the full CI workflow ecosystem
    
    - Fix empty associative array crash under set -u in checkManualItTests
      by using a counter variable instead of ${#it_sources[@]}
    
    - Fix duplicate modules in CI comment by stripping Maven [packaging]
      suffix before deduplication
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .github/CI-ARCHITECTURE.md                         | 164 +++++
 .github/actions/component-test/action.yaml         | 121 ----
 .github/actions/component-test/component-test.sh   |  71 ---
 .github/actions/detect-dependencies/action.yaml    |  39 --
 .github/actions/detect-dependencies/detect-test.sh |  96 ---
 .github/actions/incremental-build/action.yaml      |  28 +-
 .../actions/incremental-build/incremental-build.sh | 710 ++++++++++++++++-----
 .../incremental-build/manual-it-mapping.txt        |  15 +
 .github/workflows/main-build.yml                   |   1 -
 .github/workflows/pr-build-main.yml                |  97 ++-
 .github/workflows/pr-commenter.yml                 |   2 +-
 .github/workflows/pr-manual-component-test.yml     |  90 +--
 .github/workflows/pr-test-commenter.yml            | 121 ++++
 13 files changed, 958 insertions(+), 597 deletions(-)

diff --git a/.github/CI-ARCHITECTURE.md b/.github/CI-ARCHITECTURE.md
new file mode 100644
index 000000000000..72982d383d7d
--- /dev/null
+++ b/.github/CI-ARCHITECTURE.md
@@ -0,0 +1,164 @@
+# CI Architecture
+
+Overview of the GitHub Actions CI/CD ecosystem for Apache Camel.
+
+## Workflow Overview
+
+```
+PR opened/updated
+       │
+       ├──► pr-id.yml ──► pr-commenter.yml (welcome message)
+       │
+       ├──► pr-build-main.yml (Build and test)
+       │        │
+       │        ├── regen.sh (full build, no tests)
+       │        ├── incremental-build (test affected modules)
+       │        │       ├── File-path analysis
+       │        │       ├── POM dependency analysis
+       │        │       └── Extra modules (/component-test)
+       │        │
+       │        └──► pr-test-commenter.yml (post unified comment)
+       │
+       └──► sonar-build.yml ──► sonar-scan.yml (SonarCloud analysis)
+                                    [currently disabled — INFRA-27808]
+
+PR comment: /component-test kafka http
+       │
+       └──► pr-manual-component-test.yml
+                │
+                └── dispatches "Build and test" with extra_modules
+```
+
+## Workflows
+
+### `pr-build-main.yml` — Build and test
+
+- **Trigger**: `pull_request` (main branch), `workflow_dispatch`
+- **Matrix**: JDK 17, 21, 25 (25 is experimental)
+- **Steps**:
+  1. Full build via `regen.sh` (`mvn install -DskipTests -Pregen`)
+  2. Check for uncommitted generated files
+  3. Run incremental tests (only affected modules)
+  4. Upload test comment as artifact
+- **Inputs** (workflow_dispatch): `pr_number`, `pr_ref`, `extra_modules`, 
`skip_full_build`
+
+### `pr-test-commenter.yml` — Post CI test comment
+
+- **Trigger**: `workflow_run` on "Build and test" completion
+- **Purpose**: Posts the unified test summary comment on the PR
+- **Why separate**: Uses `workflow_run` to run in base repo context, allowing 
comment posting on fork PRs (where `GITHUB_TOKEN` is read-only)
+
+### `pr-manual-component-test.yml` — /component-test handler
+
+- **Trigger**: `issue_comment` with `/component-test` prefix
+- **Who**: MEMBER, OWNER, or CONTRIBUTOR only
+- **What**: Resolves component names to module paths, dispatches the main 
"Build and test" workflow with `extra_modules` and `skip_full_build=true`
+- **Build**: Uses a quick targeted build (`-Dquickly`) of the requested 
modules and their dependencies instead of the full `regen.sh` build
+
+### `pr-id.yml` + `pr-commenter.yml` — Welcome message
+
+- **Trigger**: `pull_request` (all branches)
+- **Purpose**: Posts the one-time welcome message on new PRs
+- **Why two workflows**: `pr-id.yml` runs in PR context (uploads PR number), 
`pr-commenter.yml` runs via `workflow_run` with write permissions
+
+### `main-build.yml` — Main branch build
+
+- **Trigger**: `push` to main, camel-4.14.x, camel-4.18.x
+- **Steps**: Same as PR build but without comment posting
+
+### `sonar-build.yml` + `sonar-scan.yml` — SonarCloud PR analysis
+
+- **Status**: Temporarily disabled (INFRA-27808 — SonarCloud quality gate 
adjustment pending)
+- **Trigger**: `pull_request` (main branch) → `workflow_run` on SonarBuild 
completion
+- **Why two workflows**: `sonar-build.yml` runs in PR context (builds with 
JaCoCo coverage on core modules, uploads compiled classes artifact), 
`sonar-scan.yml` runs via `workflow_run` with secrets access to run the Sonar 
scanner and post results
+- **Coverage scope**: Currently limited to core modules (`camel-api`, 
`camel-core`, etc.) and `coverage` aggregator. Component coverage planned for 
future integration with `incremental-build.sh` module detection
+
+### Other workflows
+
+- `pr-labeler.yml` — Auto-labels PRs based on changed files
+- `pr-doc-validation.yml` — Validates documentation changes
+- `pr-cleanup-branches.yml` — Cleans up merged PR branches
+- `alternative-os-build-main.yml` — Tests on non-Linux OSes
+- `check-container-versions.yml` — Checks test container version updates
+- `generate-sbom-main.yml` — Generates SBOM for releases
+- `security-scan.yml` — Security vulnerability scanning
+
+## Actions
+
+### `incremental-build`
+
+The core test runner. Determines which modules to test using:
+
+1. **File-path analysis**: Maps changed files to Maven modules
+2. **POM dependency analysis**: For `parent/pom.xml` changes, detects property 
changes and finds modules that reference the affected properties in their 
`pom.xml` files (uses simple grep, not Maveniverse Toolbox — see Known 
Limitations below)
+3. **Extra modules**: Additional modules passed via `/component-test`
+
+Results are merged, deduplicated, and tested. The script also:
+
+- Detects tests disabled in CI (`@DisabledIfSystemProperty(named = 
"ci.env.name")`)
+- Applies an exclusion list for generated/meta modules
+- Checks for excluded modules with associated integration tests (via 
`manual-it-mapping.txt`) and advises contributors to run them manually
+- Generates a unified PR comment with all test information
+
+### `install-mvnd`
+
+Installs the Maven Daemon (mvnd) for faster builds.
+
+### `install-packages`
+
+Installs system packages required for the build.
+
+## PR Labels
+
+| Label | Effect |
+| --- | --- |
+| `skip-tests` | Skip all tests |
+| `test-dependents` | Force testing dependent modules even if threshold 
exceeded |
+
+## CI Environment
+
+The CI sets `-Dci.env.name=github.com` via `MVND_OPTS` (in `install-mvnd`). 
Tests can use `@DisabledIfSystemProperty(named = "ci.env.name")` to skip flaky 
tests in CI. The test comment warns about these skipped tests.
+
+## Known Limitations of POM Dependency Detection
+
+The property-grep approach has structural limitations that can cause missed 
modules:
+
+1. **Managed dependencies without explicit** `<version>` — Most Camel modules 
inherit dependency versions via `<dependencyManagement>` in the parent POM and 
do not declare `<version>${property}</version>` themselves. When a managed 
dependency version property changes, only modules that explicitly reference the 
property are detected — modules relying on inheritance are missed.
+
+2. **Maven plugin version changes are completely invisible** — Plugin version 
properties (e.g. `<maven-surefire-plugin-version>`) are both defined and 
consumed in `parent/pom.xml` via `<pluginManagement>`. Since the module search 
excludes `parent/pom.xml`, no modules are found and **no tests run at all** for 
plugin updates. Modules inherit plugins from the parent without any 
`${property}` reference in their own `pom.xml`.
+
+3. **BOM imports** — When a BOM version property changes (e.g. 
`<spring-boot-bom-version>`), modules using artifacts from that BOM are not 
detected because they reference the BOM's artifacts, not the BOM property.
+
+4. **Transitive dependency changes** — Modules affected only via transitive 
dependencies are not detected.
+
+5. **Non-property version changes** — Direct edits to `<version>` values (not 
using `${property}` substitution) or structural changes to 
`<dependencyManagement>` sections are not caught.
+
+These limitations mean the incremental build may under-test when parent POM 
properties change. A future improvement could use [Maveniverse 
Toolbox](https://github.com/maveniverse/toolbox) `tree-find` or 
[Scalpel](https://github.com/maveniverse/scalpel) to resolve the full 
dependency graph and detect all affected modules.
+
+## Manual Integration Test Advisories
+
+Some modules are excluded from CI's `-amd` expansion (the `EXCLUSION_LIST`) 
because they are generated code, meta-modules, or expensive integration test 
suites. When a contributor changes one of these modules, CI cannot 
automatically test all downstream effects.
+
+The file `manual-it-mapping.txt` (co-located with the incremental build 
script) maps source modules to their associated integration test suites. When a 
changed module has a mapping entry, CI posts an advisory in the PR comment:
+
+> You modified `dsl/camel-jbang/camel-jbang-core`. The related integration 
tests in `dsl/camel-jbang/camel-jbang-it` are excluded from CI. Consider 
running them manually:
+>
+> ```
+> mvn verify -f dsl/camel-jbang/camel-jbang-it -Djbang-it-test
+> ```
+
+To add new mappings, edit `manual-it-mapping.txt` using the format:
+
+```
+source-artifact-id:it-module-path:command
+```
+
+## Multi-JDK Artifact Behavior
+
+All non-experimental JDK matrix entries (17, 21) upload the CI comment 
artifact with `overwrite: true`. This ensures a comment is posted even if one 
JDK build fails. Since the comment content is identical across JDKs (same 
modules are tested regardless of JDK version), last writer wins.
+
+## Comment Markers
+
+PR comments use HTML markers for upsert (create-or-update) behavior:
+
+- `<!-- ci-tested-modules -->` — Unified test summary comment
\ No newline at end of file
diff --git a/.github/actions/component-test/action.yaml 
b/.github/actions/component-test/action.yaml
deleted file mode 100644
index 72eace547d68..000000000000
--- a/.github/actions/component-test/action.yaml
+++ /dev/null
@@ -1,121 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-name: "Component Test Runner"
-description: "Runs tests of corresponding to the given comment"
-inputs:
-  run-id:
-    description: 'Id of the job'
-    required: true
-  pr-id:
-    description: 'Id of the pull request to update'
-    required: true
-  comment-id:
-    description: 'Id of the comment (unused, kept for backward compatibility 
with older PR branches)'
-    required: false
-    default: ''
-  comment-body:
-    description: 'Body of the comment to process'
-    required: true
-  artifact-upload-suffix:
-    description: 'Suffix for artifacts stored'
-    required: false
-    default: ''
-runs:
-  using: "composite"
-  steps:
-    - id: install-mvnd
-      uses: ./.github/actions/install-mvnd
-    - name: maven build
-      shell: bash
-      run: ${{ github.action_path }}/component-test.sh
-      env:
-        MAVEN_BINARY: ${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd
-        COMMENT_BODY: ${{ inputs.comment-body }}
-        FAST_BUILD: "true"
-        LOG_FILE: build.log
-    - name: archive logs
-      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 
v4.6.2
-      if: always()
-      with:
-        name: build-${{ inputs.artifact-upload-suffix }}.log
-        path: build.log
-    - name: maven test
-      shell: bash
-      run: ${{ github.action_path }}/component-test.sh
-      env:
-        MAVEN_BINARY: ${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd
-        COMMENT_BODY: ${{ inputs.comment-body }}
-        FAST_BUILD: "false"
-        LOG_FILE: tests.log
-    - name: archive logs
-      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 
v4.6.2
-      if: always()
-      with:
-        name: tests-${{ inputs.artifact-upload-suffix }}.log
-        path: tests.log
-    - name: Check for disabled tests
-      id: disabled-tests
-      if: always()
-      shell: bash
-      env:
-        COMMENT_BODY: ${{ inputs.comment-body }}
-      run: |
-        # Resolve component paths from the comment (same logic as 
component-test.sh)
-        componentList="${COMMENT_BODY:16}"
-        skipped_modules=""
-        for component in ${componentList}; do
-          if [[ ${component} = camel-* ]]; then
-            componentPath="components/${component}"
-          else
-            componentPath="components/camel-${component}"
-          fi
-          if [[ -d "${componentPath}" ]]; then
-            # Search for @DisabledIfSystemProperty(named = "ci.env.name") in 
test sources
-            matches=$(grep -rl 'DisabledIfSystemProperty' "${componentPath}" 
--include="*.java" 2>/dev/null \
-              | xargs grep -l 'ci.env.name' 2>/dev/null || true)
-            if [[ -n "$matches" ]]; then
-              count=$(echo "$matches" | wc -l | tr -d ' ')
-              skipped_modules="${skipped_modules}\n- \`${componentPath}\`: 
${count} test(s) disabled on GitHub Actions"
-            fi
-          fi
-        done
-        if [[ -n "$skipped_modules" ]]; then
-          {
-            echo 'warning<<SKIPPED_EOF'
-            echo ""
-            echo ":warning: **Some integration tests are disabled on GitHub 
Actions** (\`@DisabledIfSystemProperty(named = \"ci.env.name\")\`) and require 
manual verification:"
-            echo -e "$skipped_modules"
-            echo 'SKIPPED_EOF'
-          } >> $GITHUB_OUTPUT
-        fi
-    - name: Success comment
-      if: success()
-      uses: 
peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # 
v5.0.0
-      with:
-        issue-number: ${{ inputs.pr-id }}
-        body: |
-          :white_check_mark: `${{ inputs.comment-body }}` tests passed 
successfully.
-          ${{ steps.disabled-tests.outputs.warning }}
-    - name: Failure comment
-      if: failure()
-      uses: 
peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # 
v5.0.0
-      with:
-        issue-number: ${{ inputs.pr-id }}
-        body: |
-          :x: `${{ inputs.comment-body }}` tests failed. Please [check the 
logs](https://github.com/${{ github.repository }}/actions/runs/${{ 
inputs.run-id }}).
-          ${{ steps.disabled-tests.outputs.warning }}
diff --git a/.github/actions/component-test/component-test.sh 
b/.github/actions/component-test/component-test.sh
deleted file mode 100755
index 736ebdd4836b..000000000000
--- a/.github/actions/component-test/component-test.sh
+++ /dev/null
@@ -1,71 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-echo "Using MVND_OPTS=$MVND_OPTS"
-
-function main() {
-  local mavenBinary=$MAVEN_BINARY
-  local commentBody=$COMMENT_BODY
-  local fastBuild=$FAST_BUILD
-  local log=$LOG_FILE
-
-  if [[ ${commentBody} = /component-test* ]] ; then
-    local componentList="${commentBody:16}"
-    echo "The list of components to test is ${componentList}"
-  else
-    echo "No components have been detected, the expected format is 
'/component-test (camel-)component-name1 (camel-)component-name2...'"
-    exit 1
-  fi
-  local pl=""
-  for component in ${componentList}
-  do
-    if [[ ${component} = camel-* ]] ; then
-      componentPath="components/${component}"
-    else
-      componentPath="components/camel-${component}"
-    fi
-    if [[ -d "${componentPath}" ]] ; then
-      pl="$pl$(find "${componentPath}" -name pom.xml -not -path "*/src/it/*" 
-not -path "*/target/*" -exec dirname {} \; | sort | tr -s "\n" ",")"
-    fi
-  done
-  len=${#pl}
-  if [[ "$len" -gt "0" ]] ; then
-    pl="${pl::len-1}"
-  else
-    echo "The components to test don't exist"
-    exit 1
-  fi
-
-  if [[ ${fastBuild} = "true" ]] ; then
-    echo "Launching a fast build against the projects ${pl} and their 
dependencies"
-    $mavenBinary -l $log $MVND_OPTS -Dquickly install -pl "$pl" -am
-  else
-    echo "Launching tests of the projects ${pl}"
-    $mavenBinary -l $log $MVND_OPTS install -pl "$pl"
-    ret=$?
-
-    if [[ ${ret} -ne 0 ]] ; then
-      echo "Processing surefire and failsafe reports to create the summary"
-      echo -e "| Failed Test | Duration | Failure Type |\n| --- | --- | --- |" 
 > "$GITHUB_STEP_SUMMARY"
-      find . -path '*target/*-reports*' -iname '*.txt' -exec 
.github/actions/incremental-build/parse_errors.sh {} \;
-    fi
-
-    exit $ret
-  fi
-}
-
-main "$@"
diff --git a/.github/actions/detect-dependencies/action.yaml 
b/.github/actions/detect-dependencies/action.yaml
deleted file mode 100644
index 3db5cf5bcc70..000000000000
--- a/.github/actions/detect-dependencies/action.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-name: "Parent pom dependencies detector"
-description: "Test only projects which have a dependency changed in the parent 
pom (typically dependabot)"
-inputs:
-  github-token:
-    description: 'The github token to access to the API'
-    required: false
-  base-ref:
-    description: 'The base branch to compare against (defaults to 
github.base_ref)'
-    required: false
-    default: ''
-runs:
-  using: "composite"
-  steps:
-    - id: install-mvnd
-      uses: apache/camel/.github/actions/install-mvnd@main
-      with:
-        dry-run: ${{ inputs.skip-mvnd-install }}
-    - name: maven test
-      env:
-        GITHUB_TOKEN: ${{ inputs.github-token }}
-      shell: bash
-      run: ${{ github.action_path }}/detect-test.sh ${{ inputs.base-ref || 
github.base_ref }} ${{ steps.install-mvnd.outputs.mvnd-dir }}/mvnd
diff --git a/.github/actions/detect-dependencies/detect-test.sh 
b/.github/actions/detect-dependencies/detect-test.sh
deleted file mode 100755
index dcdb3a9e218f..000000000000
--- a/.github/actions/detect-dependencies/detect-test.sh
+++ /dev/null
@@ -1,96 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-set -euo pipefail
-
-detect_changed_properties() {
-  local base_branch="$1"
-
-  git diff "${base_branch}" -- parent/pom.xml | \
-    grep -E '^[+-]\s*<[^>]+>[^<]*</[^>]+>' | \
-    grep -vE '^\+\+\+|---' | \
-    grep -E 'version|dependency|artifact' | \
-    sed -E 's/^[+-]\s*<([^>]+)>.*/\1/' | \
-    sort -u || true
-}
-
-find_affected_modules() {
-  local property_name="$1"
-  local mavenBinary=${2}
-  local affected=()
-
-  while IFS= read -r pom; do
-    # skip any target that may have been built previously
-    if [[ "$pom" != */target/* ]]; then
-      # only consider certain modules, nothing else
-      if [[ "$pom" == */catalog/* ]] || \
-          [[ "$pom" == */components/* ]] || \
-          [[ "$pom" == */core/* ]] || \
-          ([[ "$pom" == */dsl/* ]] && [[ "$pom" != */dsl/camel-jbang* ]]); then
-        if grep -q "\${${property_name}}" "$pom"; then
-          affected+=("$pom")
-        fi
-      fi
-    fi
-  done < <(find . -name "pom.xml")
-
-  affected_transformed=""
-
-  for pom in "${affected[@]}"; do
-    if [[ -f "$pom" ]]; then
-      artifactId=$($mavenBinary -f "$pom" help:evaluate 
-Dexpression=project.artifactId -q --raw-streams -DforceStdout)
-      if [ ! -z "$artifactId" ]; then
-        affected_transformed+=":$artifactId,"
-      fi
-    fi
-  done
-
-  echo "$affected_transformed"
-}
-
-main() {
-  echo "Using MVND_OPTS=$MVND_OPTS"
-  local base_branch=${1}
-  local mavenBinary=${2}
-  local 
exclusionList="!:camel-allcomponents,!:dummy-component,!:camel-catalog,!:camel-catalog-console,!:camel-catalog-lucene,!:camel-catalog-maven,!:camel-catalog-suggest,!:camel-route-parser,!:camel-csimple-maven-plugin,!:camel-report-maven-plugin,!:camel-endpointdsl,!:camel-componentdsl,!:camel-endpointdsl-support,!:camel-yaml-dsl,!:camel-kamelet-main,!:camel-yaml-dsl-deserializers,!:camel-yaml-dsl-maven-plugin,!:camel-jbang-core,!:camel-jbang-main,!:camel-jbang-plugin-generate,!:came
 [...]
-
-  git fetch origin $base_branch:$base_branch
-
-  changed_props=$(detect_changed_properties "$base_branch")
-
-  if [ -z "$changed_props" ]; then
-    echo "✅ No property changes detected."
-    exit 0
-  fi
-
-  modules_affected=""
-
-  while read -r prop; do
-    modules=$(find_affected_modules "$prop" $mavenBinary)
-    modules_affected+="$modules"
-  done <<< "$changed_props"
-
-  if [ -z "$modules_affected" ]; then
-    echo "✅ No components affected by property changes detected."
-    exit 0
-  fi
-
-  echo "🧪 Testing the following modules $modules_affected and its dependents"
-  $mavenBinary $MVND_OPTS test -pl "$modules_affected$exclusionList" -amd
-}
-
-main "$@"
diff --git a/.github/actions/incremental-build/action.yaml 
b/.github/actions/incremental-build/action.yaml
index 0166f9d3aad1..a880b9137419 100644
--- a/.github/actions/incremental-build/action.yaml
+++ b/.github/actions/incremental-build/action.yaml
@@ -15,15 +15,13 @@
 # limitations under the License.
 #
 
-name: "Incremental Build Runner"
-description: "Build only affected projects"
+name: "Incremental Test Runner"
+description: "Test only affected projects, using file-path analysis and POM 
dependency detection"
 inputs:
-  mode:
-    description: 'The mode to launch, it can be build or test'
-    required: true
   pr-id:
-    description: 'Id of the pull request'
-    required: true
+    description: 'Id of the pull request (optional for push builds)'
+    required: false
+    default: ''
   github-token:
     description: 'The github token to access to the API'
     required: false
@@ -34,11 +32,15 @@ inputs:
   github-repo:
     description: 'The GitHub repository name (example, apache/camel)'
     required: false
-    default: 'apache/camel'
+    default: ${{ github.repository }}
   artifact-upload-suffix:
     description: 'Suffix for artifacts stored'
     required: false
     default: ''
+  extra-modules:
+    description: 'Additional modules to test (comma-separated paths, e.g. from 
/component-test)'
+    required: false
+    default: ''
 runs:
   using: "composite"
   steps:
@@ -46,17 +48,17 @@ runs:
       uses: apache/camel/.github/actions/install-mvnd@main
       with:
         dry-run: ${{ inputs.skip-mvnd-install }}
-    - name: maven build
+    - name: maven test
       env:
         GITHUB_TOKEN: ${{ inputs.github-token }}
-        MODE: ${{ inputs.mode }}
         PR_ID: ${{ inputs.pr-id }}
         GITHUB_REPO: ${{ inputs.github-repo }}
+        EXTRA_MODULES: ${{ inputs.extra-modules }}
       shell: bash
-      run: ${{ github.action_path }}/incremental-build.sh ${{ 
steps.install-mvnd.outputs.mvnd-dir }}/mvnd $MODE $PR_ID $GITHUB_REPO
+      run: ${{ github.action_path }}/incremental-build.sh ${{ 
steps.install-mvnd.outputs.mvnd-dir }}/mvnd "$PR_ID" "$GITHUB_REPO" 
"$EXTRA_MODULES"
     - name: archive logs
       uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 
v4.6.2
       if: always()
       with:
-        name: incremental-${{ inputs.mode }}-${{ inputs.artifact-upload-suffix 
}}.log
-        path: incremental-${{ inputs.mode }}.log
+        name: incremental-test-${{ inputs.artifact-upload-suffix }}.log
+        path: incremental-test.log
diff --git a/.github/actions/incremental-build/incremental-build.sh 
b/.github/actions/incremental-build/incremental-build.sh
index 37eb34395c90..1938b7c8cefb 100755
--- a/.github/actions/incremental-build/incremental-build.sh
+++ b/.github/actions/incremental-build/incremental-build.sh
@@ -15,18 +15,34 @@
 # limitations under the License.
 #
 
+# Incremental test runner for Apache Camel PRs.
+#
+# Determines which modules to test by:
+#   1. File-path analysis: maps changed files to their Maven modules
+#   2. POM dependency analysis: for changed pom.xml files, detects property
+#      changes and finds modules that reference the affected properties
+#
+# Both sets of affected modules are merged and deduplicated before testing.
+
+set -euo pipefail
+
 echo "Using MVND_OPTS=$MVND_OPTS"
 
-maxNumberOfBuildableProjects=100
 maxNumberOfTestableProjects=50
 
-function findProjectRoot () {
+# Modules excluded from targeted testing (generated code, meta-modules, etc.)
+EXCLUSION_LIST="!:camel-allcomponents,!:dummy-component,!:camel-catalog,!:camel-catalog-console,!:camel-catalog-lucene,!:camel-catalog-maven,!:camel-catalog-suggest,!:camel-route-parser,!:camel-csimple-maven-plugin,!:camel-report-maven-plugin,!:camel-endpointdsl,!:camel-componentdsl,!:camel-endpointdsl-support,!:camel-yaml-dsl,!:camel-kamelet-main,!:camel-yaml-dsl-deserializers,!:camel-yaml-dsl-maven-plugin,!:camel-jbang-core,!:camel-jbang-main,!:camel-jbang-plugin-generate,!:camel-jbang
 [...]
+
+# ── Utility functions ──────────────────────────────────────────────────
+
+# Walk up from a file path to find the nearest directory containing a pom.xml
+findProjectRoot() {
   local path=${1}
   while [[ "$path" != "." ]]; do
-    if [[ ! -e "$path/pom.xml" ]] ; then
-      path=$(dirname $path)
-    elif [[ $(dirname $path) == */src/it ]] ; then
-      path=$(dirname $(dirname $path))
+    if [[ ! -e "$path/pom.xml" ]]; then
+      path=$(dirname "$path")
+    elif [[ $(dirname "$path") == */src/it ]]; then
+      path=$(dirname "$(dirname "$path")")
     else
       break
     fi
@@ -34,210 +50,572 @@ function findProjectRoot () {
   echo "$path"
 }
 
-function hasLabel() {
-    local issueNumber=${1}
-    local label="incremental-${2}"
-    local repository=${3}
-    curl -s \
-      -H "Accept: application/vnd.github+json" \
-      -H "Authorization: Bearer ${GITHUB_TOKEN}"\
-      -H "X-GitHub-Api-Version: 2022-11-28" \
-      
"https://api.github.com/repos/${repository}/issues/${issueNumber}/labels"; | jq 
-r '.[].name' | grep -c "$label"
+# Check whether a PR label exists
+hasLabel() {
+  local issueNumber=${1}
+  local label="incremental-${2}"
+  local repository=${3}
+  curl -s \
+    -H "Accept: application/vnd.github+json" \
+    -H "Authorization: Bearer ${GITHUB_TOKEN}" \
+    -H "X-GitHub-Api-Version: 2022-11-28" \
+    "https://api.github.com/repos/${repository}/issues/${issueNumber}/labels"; 
| jq -r '.[].name' | { grep -c "$label" || true; }
 }
 
-function main() {
-  local mavenBinary=${1}
-  local mode=${2}
-  local log="incremental-${mode}.log"
-  local prId=${3}
-  local ret=0
-  local repository=${4}
-  local testedDependents=""
+# Fetch the PR diff from the GitHub API.  Returns the full unified diff.
+fetchDiff() {
+  local prId="$1"
+  local repository="$2"
 
-  echo "Searching for affected projects"
-  local projects
   local diff_output
-  diff_output=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer 
${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3.diff" 
"https://api.github.com/repos/${repository}/pulls/${prId}";)
+  diff_output=$(curl -s -w "\n%{http_code}" \
+    -H "Authorization: Bearer ${GITHUB_TOKEN}" \
+    -H "Accept: application/vnd.github.v3.diff" \
+    "https://api.github.com/repos/${repository}/pulls/${prId}";)
+
   local http_code
   http_code=$(echo "$diff_output" | tail -n 1)
   local diff_body
   diff_body=$(echo "$diff_output" | sed '$d')
-  if [[ "$http_code" -lt 200 || "$http_code" -ge 300 || -z "$diff_body" ]] ; 
then
-    echo "WARNING: Failed to fetch PR diff (HTTP $http_code). Falling back to 
full build."
-    diff_body=""
+
+  if [[ "$http_code" -lt 200 || "$http_code" -ge 300 || -z "$diff_body" ]]; 
then
+    echo "WARNING: Failed to fetch PR diff (HTTP $http_code). Falling back to 
full build." >&2
+    return
+  fi
+  echo "$diff_body"
+}
+
+# ── POM dependency analysis (previously detect-dependencies) ───────────
+#
+# Uses the pre-#22022 approach: grep for ${property-name} references in
+# module pom.xml files. The Maveniverse Toolbox approach (introduced in
+# #22022, reverted in #22279) is intentionally not used here. See
+# CI-ARCHITECTURE.md for known limitations of the grep approach.
+
+# Extract the diff section for a specific pom.xml file from the full diff
+extractPomDiff() {
+  local diff_body="$1"
+  local pom_path="$2"
+
+  echo "$diff_body" | awk -v target="a/${pom_path}" '
+    /^diff --git/ && found { exit }
+    /^diff --git/ && index($0, target) { found=1 }
+    found { print }
+  '
+}
+
+# Detect which properties changed in a pom.xml diff.
+# Returns one property name per line.
+# Filters out structural XML elements (groupId, artifactId, version, etc.)
+# to only return actual property names (e.g. openai-java-version).
+detectChangedProperties() {
+  local diff_content="$1"
+
+  # Known structural POM elements that are NOT property names
+  local 
structural_elements="groupId|artifactId|version|scope|type|classifier|optional|systemPath|exclusions|exclusion|dependency|dependencies|dependencyManagement|parent|modules|module|packaging|name|description|url|relativePath"
+
+  echo "$diff_content" | \
+    grep -E '^[+-][[:space:]]*<[^>]+>[^<]*</[^>]+>' | \
+    grep -vE '^\+\+\+|^---' | \
+    sed -E 's/^[+-][[:space:]]*<([^>]+)>.*/\1/' | \
+    grep -vE "^(${structural_elements})$" | \
+    sort -u || true
+}
+
+# Find modules that reference a property in their pom.xml.
+# Searches pom.xml files under catalog/, components/, core/, dsl/ for
+# ${property_name} references and extracts the module's artifactId.
+# Adds discovered artifactIds to the dep_module_ids variable
+# (which must be declared in the caller).
+findAffectedModules() {
+  local property="$1"
+
+  local matches
+  matches=$(grep -rl "\${${property}}" --include="pom.xml" . 2>/dev/null | \
+    grep -v "^\./parent/pom.xml" | \
+    grep -v "/target/" || true)
+
+  if [ -z "$matches" ]; then
+    return
+  fi
+
+  while read -r pom_file; do
+    [ -z "$pom_file" ] && continue
+
+    # Only consider catalog, components, core, dsl paths (same as original 
detect-test.sh)
+    if [[ "$pom_file" == */catalog/* ]] || \
+       [[ "$pom_file" == */components/* ]] || \
+       [[ "$pom_file" == */core/* ]] || \
+       ([[ "$pom_file" == */dsl/* ]] && [[ "$pom_file" != */dsl/camel-jbang* 
]]); then
+      local mod_artifact
+      mod_artifact=$(sed -n '/<parent>/,/<\/parent>/!{ 
s/.*<artifactId>\([^<]*\)<\/artifactId>.*/\1/p }' "$pom_file" | head -1)
+      if [ -n "$mod_artifact" ] && ! echo ",$dep_module_ids," | grep -q 
",:${mod_artifact},"; then
+        echo "    Property '\${${property}}' referenced by: $mod_artifact"
+        dep_module_ids="${dep_module_ids:+${dep_module_ids},}:${mod_artifact}"
+      fi
+    fi
+  done <<< "$matches"
+}
+
+# Analyze pom.xml changes to find affected modules via property grep.
+# Adds discovered module artifactIds to the dep_module_ids variable
+# (which must be declared in the caller).
+analyzePomDependencies() {
+  local diff_body="$1"
+  local pom_path="$2"  # e.g. "parent/pom.xml" or 
"components/camel-foo/pom.xml"
+
+  local pom_diff
+  pom_diff=$(extractPomDiff "$diff_body" "$pom_path")
+  if [ -z "$pom_diff" ]; then
+    return
+  fi
+
+  local changed_props
+  changed_props=$(detectChangedProperties "$pom_diff")
+  if [ -z "$changed_props" ]; then
+    return
+  fi
+
+  echo "  Property changes detected in ${pom_path}:"
+  echo "$changed_props" | while read -r p; do echo "    - $p"; done
+
+  while read -r prop; do
+    [ -z "$prop" ] && continue
+    findAffectedModules "$prop"
+  done <<< "$changed_props"
+}
+
+# ── Disabled-test detection ─────────────────────────────────────────────
+
+# Scan tested modules for @DisabledIfSystemProperty(named = "ci.env.name")
+# and return a markdown warning listing affected files.
+detectDisabledTests() {
+  local final_pl="$1"
+  local skipped=""
+
+  for mod_path in $(echo "$final_pl" | tr ',' '\n'); do
+    # Skip artifactId-style references (e.g. :camel-openai) — only scan paths
+    if [[ "$mod_path" == :* ]]; then
+      continue
+    fi
+    if [ -d "$mod_path" ]; then
+      local matches
+      matches=$(grep -rl 'DisabledIfSystemProperty' "$mod_path" 
--include="*.java" 2>/dev/null \
+        | xargs grep -l 'ci.env.name' 2>/dev/null || true)
+      if [ -n "$matches" ]; then
+        local count
+        count=$(echo "$matches" | wc -l | tr -d ' ')
+        skipped="${skipped}\n- \`${mod_path}\`: ${count} test(s) disabled on 
GitHub Actions"
+      fi
+    fi
+  done
+
+  if [ -n "$skipped" ]; then
+    echo -e "$skipped"
+  fi
+}
+
+# Check if changed modules have associated integration tests excluded from CI.
+# Reads manual-it-mapping.txt and appends advisories to the PR comment.
+checkManualItTests() {
+  local final_pl="$1"
+  local comment_file="$2"
+  local script_dir
+  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+  local mapping_file="${script_dir}/manual-it-mapping.txt"
+
+  [[ ! -f "$mapping_file" ]] && return
+
+  declare -A it_commands
+  declare -A it_sources
+  local it_found=0
+
+  while IFS=: read -r source_id it_module command; do
+    # Skip comments and empty lines
+    [[ -z "$source_id" || "$source_id" == \#* ]] && continue
+    source_id="${source_id// /}"
+    it_module="${it_module// /}"
+    command="${command#"${command%%[![:space:]]*}"}"
+
+    # Check if any module in final_pl matches this source_id
+    for module_path in $(echo "$final_pl" | tr ',' '\n'); do
+      if [[ "$(basename "$module_path")" == "$source_id" ]]; then
+        it_commands["$it_module"]="$command"
+        
it_sources["$it_module"]="${it_sources[$it_module]:-}${it_sources[$it_module]:+,
 }\`${module_path}\`"
+        it_found=1
+      fi
+    done
+  done < "$mapping_file"
+
+  if [[ "$it_found" -eq 1 ]]; then
+    echo "" >> "$comment_file"
+    echo ":bulb: **Manual integration tests recommended:**" >> "$comment_file"
+    for it_module in "${!it_sources[@]}"; do
+      echo "" >> "$comment_file"
+      echo "> You modified ${it_sources[$it_module]}. The related integration 
tests in \`${it_module}\` are excluded from CI. Consider running them 
manually:" >> "$comment_file"
+      echo '> ```' >> "$comment_file"
+      echo "> ${it_commands[$it_module]}" >> "$comment_file"
+      echo '> ```' >> "$comment_file"
+    done
+  fi
+}
+
+# ── Comment generation ─────────────────────────────────────────────────
+
+writeComment() {
+  local comment_file="$1"
+  local pl="$2"
+  local dep_ids="$3"
+  local changed_props_summary="$4"
+  local testedDependents="$5"
+  local extra_modules="$6"
+
+  echo "<!-- ci-tested-modules -->" > "$comment_file"
+
+  # Section 1: file-path-based modules
+  if [ -n "$pl" ]; then
+    echo ":test_tube: **CI tested the following changed modules:**" >> 
"$comment_file"
+    echo "" >> "$comment_file"
+    for w in $(echo "$pl" | tr ',' '\n'); do
+      echo "- \`$w\`" >> "$comment_file"
+    done
+
+    if [[ "${testedDependents}" = "false" ]]; then
+      echo "" >> "$comment_file"
+      echo "> :information_source: Dependent modules were not tested because 
the total number of affected modules exceeded the threshold 
(${maxNumberOfTestableProjects}). Use the \`test-dependents\` label to force 
testing all dependents." >> "$comment_file"
+    fi
+  fi
+
+  # Section 2: pom dependency-detected modules
+  if [ -n "$dep_ids" ]; then
+    echo "" >> "$comment_file"
+    if [ -n "$changed_props_summary" ]; then
+      echo ":white_check_mark: **POM dependency changes: targeted tests 
included**" >> "$comment_file"
+      echo "" >> "$comment_file"
+      echo "Changed properties: ${changed_props_summary}" >> "$comment_file"
+      echo "" >> "$comment_file"
+      local dep_count
+      dep_count=$(echo "$dep_ids" | tr ',' '\n' | wc -l | tr -d ' ')
+      echo "<details><summary>Modules affected by dependency changes 
(${dep_count})</summary>" >> "$comment_file"
+      echo "" >> "$comment_file"
+      echo "$dep_ids" | tr ',' '\n' | while read -r m; do
+        echo "- \`$m\`" >> "$comment_file"
+      done
+      echo "" >> "$comment_file"
+      echo "</details>" >> "$comment_file"
+    fi
+  fi
+
+  # Section 3: extra modules (from /component-test)
+  if [ -n "$extra_modules" ]; then
+    echo "" >> "$comment_file"
+    echo ":heavy_plus_sign: **Additional modules tested** (via 
\`/component-test\`):" >> "$comment_file"
+    echo "" >> "$comment_file"
+    for w in $(echo "$extra_modules" | tr ',' '\n'); do
+      echo "- \`$w\`" >> "$comment_file"
+    done
+  fi
+
+  if [ -z "$pl" ] && [ -z "$dep_ids" ] && [ -z "$extra_modules" ]; then
+    echo ":information_source: CI did not run targeted module tests." >> 
"$comment_file"
+  fi
+}
+
+# ── Main ───────────────────────────────────────────────────────────────
+
+main() {
+  local mavenBinary=${1}
+  local prId=${2}
+  local repository=${3}
+  local extraModules=${4:-}
+  local log="incremental-test.log"
+  local ret=0
+  local testedDependents=""
+
+  # Check for skip-tests label (only for PR builds)
+  if [ -n "$prId" ]; then
+    local mustSkipTests
+    mustSkipTests=$(hasLabel "${prId}" "skip-tests" "${repository}")
+    if [[ ${mustSkipTests} = "1" ]]; then
+      echo "The skip-tests label has been detected, no tests will be launched"
+      echo "<!-- ci-tested-modules -->" > "incremental-test-comment.md"
+      echo ":information_source: CI did not run targeted module tests 
(skip-tests label detected)." >> "incremental-test-comment.md"
+      exit 0
+    fi
+  fi
+
+  # Fetch the diff (PR diff via API, or git diff for push builds)
+  local diff_body
+  if [ -n "$prId" ]; then
+    echo "Fetching PR #${prId} diff..."
+    diff_body=$(fetchDiff "$prId" "$repository")
+  else
+    echo "No PR ID, using git diff HEAD~1..."
+    diff_body=$(git diff HEAD~1 2>/dev/null || true)
+  fi
+
+  if [ -z "$diff_body" ]; then
+    echo "Could not fetch diff, skipping tests"
+    exit 0
   fi
+
+  # ── Step 1: File-path analysis ──
+  echo "Searching for affected projects by file path..."
+  local projects
   projects=$(echo "$diff_body" | sed -n -e '/^diff --git a/p' | awk '{print 
$3}' | cut -b 3- | sed 's|\(.*\)/.*|\1|' | uniq | sort)
+
   local pl=""
   local lastProjectRoot=""
-  local buildAll=false
   local totalAffected=0
-  for project in ${projects}
-  do
-    if [[ ${project} == */archetype-resources ]] ; then
+  local pom_files=""
+
+  for project in ${projects}; do
+    if [[ ${project} == */archetype-resources ]]; then
       continue
-    elif [[ ${project} != .* ]] ; then
+    elif [[ ${project} != .* ]]; then
       local projectRoot
-      projectRoot=$(findProjectRoot ${project})
-      if [[ ${projectRoot} = "." ]] ; then
-        echo "The root project is affected, so a complete build is triggered"
-        buildAll=true
-        totalAffected=1
-        break
-      elif [[ ${projectRoot} != "${lastProjectRoot}" ]] ; then
-        (( totalAffected ++ ))
+      projectRoot=$(findProjectRoot "${project}")
+      if [[ ${projectRoot} = "." ]]; then
+        echo "The root project is affected, skipping targeted module testing"
+        echo "<!-- ci-tested-modules -->" > "incremental-test-comment.md"
+        echo ":information_source: CI did not run targeted module tests (root 
project files changed)." >> "incremental-test-comment.md"
+        exit 0
+      elif [[ ${projectRoot} != "${lastProjectRoot}" ]]; then
+        totalAffected=$((totalAffected + 1))
         pl="$pl,${projectRoot}"
         lastProjectRoot=${projectRoot}
       fi
     fi
   done
+  pl="${pl:1}"  # strip leading comma
 
-  if [[ ${totalAffected} = 0 ]] ; then
-    echo "There is nothing to build"
-    exit 0
-  elif [[ ${totalAffected} -gt ${maxNumberOfBuildableProjects} ]] ; then
-    echo "There are too many affected projects, so a complete build is 
triggered"
-    buildAll=true
-  fi
-  pl="${pl:1}"
-
-  if [[ ${mode} = "build" ]] ; then
-    local mustBuildAll
-    mustBuildAll=$(hasLabel ${prId} "build-all" ${repository})
-    if [[ ${mustBuildAll} = "1" ]] ; then
-      echo "The build-all label has been detected thus all projects must be 
built"
-      buildAll=true
-    fi
-    if [[ ${buildAll} = "true" ]] ; then
-      echo "Building all projects"
-      $mavenBinary -l $log $MVND_OPTS -DskipTests install
-      ret=$?
-    else
-      local buildDependents
-      buildDependents=$(hasLabel ${prId} "build-dependents" ${repository})
-      local totalTestableProjects
-      if [[ ${buildDependents} = "1" ]] ; then
-        echo "The build-dependents label has been detected thus the projects 
that depend on the affected projects will be built"
-        totalTestableProjects=0
-      else
-        for w in $pl; do
-          echo "$w"
-        done
-        totalTestableProjects=$(./mvnw -B -q -amd exec:exec 
-Dexec.executable="pwd" -pl "$pl" | wc -l)
-      fi
-      if [[ ${totalTestableProjects} -gt ${maxNumberOfTestableProjects} ]] ; 
then
-        echo "Launching fast build command against the projects ${pl}, their 
dependencies and the projects that depend on them"
-        for w in $pl; do
-          echo "$w"
-        done
-        $mavenBinary -l $log $MVND_OPTS -DskipTests install -pl "$pl" -amd -am
-        ret=$?
-      else
-        echo "Launching fast build command against the projects ${pl} and 
their dependencies"
-        for w in $pl; do
-          echo "$w"
-        done
-        $mavenBinary -l $log $MVND_OPTS -DskipTests install -pl "$pl" -am
-        ret=$?
+  # Only analyze parent/pom.xml for dependency detection
+  # (matches original detect-test.sh behavior; detection improvements deferred 
to follow-up PR)
+  if echo "$diff_body" | grep -q '^diff --git a/parent/pom.xml'; then
+    pom_files="parent/pom.xml"
+  fi
+
+  # ── Step 2: POM dependency analysis ──
+  # Variables shared with analyzePomDependencies/findAffectedModules
+  local dep_module_ids=""
+  local all_changed_props=""
+
+  if [ -n "$pom_files" ]; then
+    echo ""
+    echo "Analyzing parent POM dependency changes..."
+    while read -r pom_file; do
+      [ -z "$pom_file" ] && continue
+
+      # Capture changed props for this pom before calling analyze
+      local pom_diff
+      pom_diff=$(extractPomDiff "$diff_body" "$pom_file")
+      if [ -n "$pom_diff" ]; then
+        local props
+        props=$(detectChangedProperties "$pom_diff")
+        if [ -n "$props" ]; then
+          all_changed_props="${all_changed_props:+${all_changed_props}, 
}$(echo "$props" | tr '\n' ',' | sed 's/,$//')"
+        fi
       fi
+
+      analyzePomDependencies "$diff_body" "$pom_file"
+    done <<< "$pom_files"
+  fi
+
+  # ── Step 3: Merge and deduplicate ──
+  # Separate file-path modules into testable (has src/test) and pom-only.
+  # Pom-only modules (e.g. "parent") are kept in the build list but must NOT
+  # be expanded with -amd, since that would pull in every dependent module.
+  local testable_pl=""
+  local pom_only_pl=""
+  for w in $(echo "$pl" | tr ',' '\n'); do
+    if [ -d "$w/src/test" ]; then
+      testable_pl="${testable_pl:+${testable_pl},}${w}"
+    else
+      pom_only_pl="${pom_only_pl:+${pom_only_pl},}${w}"
+      echo "  Pom-only module (no src/test, won't expand dependents): $w"
     fi
-    [[ -z $(git status --porcelain | grep -v antora.yml) ]] || { echo 'There 
are uncommitted changes'; git status; echo; echo; git diff; exit 1; }
-  else
-    local mustSkipTests
-    mustSkipTests=$(hasLabel ${prId} "skip-tests" ${repository})
-    if [[ ${mustSkipTests} = "1" ]] ; then
-      echo "The skip-tests label has been detected thus no test will be 
launched"
-      buildAll=true
-    elif [[ ${buildAll} = "true" ]] ; then
-      echo "Cannot launch the tests of all projects, so no test will be 
launched"
+  done
+
+  # Build final_pl: testable file-path modules + dependency-detected + 
pom-only + extra
+  local final_pl=""
+  if [ -n "$testable_pl" ]; then
+    final_pl="$testable_pl"
+  fi
+  if [ -n "$dep_module_ids" ]; then
+    final_pl="${final_pl:+${final_pl},}${dep_module_ids}"
+  fi
+  if [ -n "$pom_only_pl" ]; then
+    final_pl="${final_pl:+${final_pl},}${pom_only_pl}"
+  fi
+
+  # Merge extra modules (e.g. from /component-test)
+  if [ -n "$extraModules" ]; then
+    echo ""
+    echo "Extra modules requested: $extraModules"
+    final_pl="${final_pl:+${final_pl},}${extraModules}"
+  fi
+
+  if [ -z "$final_pl" ]; then
+    echo ""
+    echo "No modules to test"
+    writeComment "incremental-test-comment.md" "" "" "" "" ""
+    exit 0
+  fi
+
+  echo ""
+  echo "Modules to test:"
+  for w in $(echo "$final_pl" | tr ',' '\n'); do
+    echo "  - $w"
+  done
+  echo ""
+
+  # ── Step 4: Run tests ──
+  # Decide whether to use -amd (also-make-dependents):
+  # - Use -amd when there are testable file-path modules (to test their 
dependents)
+  # - Subject to threshold check to avoid testing too many modules
+  # - Pom-only modules are excluded from -pl to prevent -amd from pulling in 
everything
+  #   (Maven builds them implicitly as dependencies of child modules)
+  local use_amd=false
+  local testDependents="0"
+
+  if [ -n "$testable_pl" ]; then
+    # File-path modules with tests — use -amd to catch dependents
+    if [ -n "$prId" ]; then
+      testDependents=$(hasLabel "${prId}" "test-dependents" "${repository}")
+    fi
+
+    if [[ ${testDependents} = "1" ]]; then
+      echo "The test-dependents label has been detected, testing dependents 
too"
+      use_amd=true
+      testedDependents=true
     else
-      local testDependents
-      testDependents=$(hasLabel ${prId} "test-dependents" ${repository})
-      local totalTestableProjects
-      if [[ ${testDependents} = "1" ]] ; then
-        echo "The test-dependents label has been detected thus the projects 
that depend on affected projects will be tested"
-        testedDependents=true
-        totalTestableProjects=0
-      else
-        totalTestableProjects=$(./mvnw -B -q -amd exec:exec 
-Dexec.executable="pwd" -pl "$pl" | wc -l)
+      # Include extra modules in the count — with -amd, Maven expands all of 
them
+      local threshold_pl="$testable_pl"
+      if [ -n "$extraModules" ]; then
+        threshold_pl="${threshold_pl},${extraModules}"
       fi
-      if [[ ${totalTestableProjects} -gt ${maxNumberOfTestableProjects} ]] ; 
then
-        echo "There are too many projects to test (${totalTestableProjects} > 
${maxNumberOfTestableProjects}) so only the affected projects are tested:"
+      local totalTestableProjects
+      totalTestableProjects=$(./mvnw -B -q -amd exec:exec 
-Dexec.executable="pwd" -pl "$threshold_pl" 2>/dev/null | wc -l) || true
+      totalTestableProjects=$(echo "$totalTestableProjects" | tail -1 | tr -d 
'[:space:]')
+      totalTestableProjects=${totalTestableProjects:-0}
+
+      if [[ ${totalTestableProjects} -gt ${maxNumberOfTestableProjects} ]]; 
then
+        echo "Too many dependent modules (${totalTestableProjects} > 
${maxNumberOfTestableProjects}), testing only the affected modules"
         testedDependents=false
-        for w in $pl; do
-          echo "$w"
-        done
-        # This need to install, other commands like test are not enough, 
otherwise test-infra will fail due to jandex maven plugin
-        $mavenBinary -l $log $MVND_OPTS install -pl "$pl"
-        ret=$?
       else
-        echo "Testing the affected projects and the projects that depend on 
them (${totalTestableProjects} modules):"
+        echo "Testing affected modules and their dependents 
(${totalTestableProjects} modules)"
+        use_amd=true
         testedDependents=true
-        for w in $pl; do
-          echo "$w"
-        done
-        # This need to install, other commands like test are not enough, 
otherwise test-infra will fail due to jandex maven plugin
-        $mavenBinary -l $log $MVND_OPTS install -pl "$pl" -amd
-        ret=$?
       fi
     fi
+  elif [ -n "$dep_module_ids" ]; then
+    # Only dependency-detected modules (no file-path code changes)
+    echo "POM dependency analysis found affected modules — testing specific 
modules"
+    testedDependents=true
+  else
+    # Only pom-only modules, no testable code and no dependency results
+    echo "Only pom-only modules changed with no detected dependency impact"
+    testedDependents=true
   fi
 
-  # Write the list of tested modules to the step summary and PR comment file
-  local comment_file="incremental-${mode}-comment.md"
-  if [[ -n "$pl" && ${buildAll} != "true" ]] ; then
-    echo "### Changed modules" >> "$GITHUB_STEP_SUMMARY"
-    echo "" >> "$GITHUB_STEP_SUMMARY"
-    echo "<!-- ci-tested-modules -->" > "$comment_file"
-    echo ":test_tube: **CI tested the following changed modules:**" >> 
"$comment_file"
+  # Build the -pl argument:
+  # - Exclude pom-only modules from -pl when using -amd (they'd pull in 
everything)
+  # - Append exclusion list when dependency-detected modules are present
+  local build_pl="$final_pl"
+  if [[ "$use_amd" = true ]] && [ -n "$pom_only_pl" ]; then
+    # Remove pom-only modules — Maven builds them implicitly as dependencies
+    build_pl=""
+    if [ -n "$testable_pl" ]; then
+      build_pl="$testable_pl"
+    fi
+    if [ -n "$dep_module_ids" ]; then
+      build_pl="${build_pl:+${build_pl},}${dep_module_ids}"
+    fi
+    if [ -n "$extraModules" ]; then
+      build_pl="${build_pl:+${build_pl},}${extraModules}"
+    fi
+  fi
+  # This needs to install, not just test, otherwise test-infra will fail due 
to jandex maven plugin
+  # Exclusion list is only needed with -amd (to prevent testing generated/meta 
modules);
+  # without -amd, only the explicitly listed modules are built.
+  if [[ "$use_amd" = true ]]; then
+    $mavenBinary -l "$log" $MVND_OPTS install -pl 
"${build_pl},${EXCLUSION_LIST}" -amd || ret=$?
+  else
+    $mavenBinary -l "$log" $MVND_OPTS install -pl "$build_pl" || ret=$?
+  fi
+
+  # ── Step 5: Write comment and summary ──
+  local comment_file="incremental-test-comment.md"
+  writeComment "$comment_file" "$pl" "$dep_module_ids" "$all_changed_props" 
"$testedDependents" "$extraModules"
+
+  # Check for tests disabled in CI via @DisabledIfSystemProperty(named = 
"ci.env.name")
+  local disabled_tests
+  disabled_tests=$(detectDisabledTests "$final_pl")
+  if [ -n "$disabled_tests" ]; then
     echo "" >> "$comment_file"
-    for w in $(echo "$pl" | tr ',' '\n'); do
-      echo "- \`$w\`" >> "$GITHUB_STEP_SUMMARY"
-      echo "- \`$w\`" >> "$comment_file"
-    done
-    echo "" >> "$GITHUB_STEP_SUMMARY"
-    # Add note about dependent modules testing scope
-    if [[ ${mode} = "test" && "${testedDependents:-}" = "false" ]] ; then
+    echo ":warning: **Some tests are disabled on GitHub Actions** 
(\`@DisabledIfSystemProperty(named = \"ci.env.name\")\`) and require manual 
verification:" >> "$comment_file"
+    echo "$disabled_tests" >> "$comment_file"
+  fi
+
+  # Check for excluded IT suites that should be run manually
+  checkManualItTests "$final_pl" "$comment_file"
+
+  # Append reactor module list from build log
+  if [[ -f "$log" ]]; then
+    local reactor_modules
+    reactor_modules=$(grep '^\[INFO\] Camel ::' "$log" | sed 's/\[INFO\] //' | 
sed 's/ \..*$//' | sed 's/  *\[.*\]$//' | sort -u || true)
+    if [[ -n "$reactor_modules" ]]; then
+      local count
+      count=$(echo "$reactor_modules" | wc -l | tr -d ' ')
+      local reactor_label
+      if [[ "${testedDependents}" = "false" ]]; then
+        reactor_label="Build reactor — dependencies compiled but only changed 
modules were tested"
+      else
+        reactor_label="All tested modules"
+      fi
+
       echo "" >> "$comment_file"
-      echo "> :information_source: Dependent modules were not tested because 
the total number of affected modules exceeded the threshold 
(${maxNumberOfTestableProjects}). Use the \`test-dependents\` label to force 
testing all dependents." >> "$comment_file"
+      echo "<details><summary>${reactor_label} ($count modules)</summary>" >> 
"$comment_file"
       echo "" >> "$comment_file"
-    fi
-    # Extract full reactor module list from the build log
-    if [[ -f "$log" ]] ; then
-      local reactor_modules
-      reactor_modules=$(grep '^\[INFO\] Camel ::' "$log" | sed 's/\[INFO\] //' 
| sed 's/ \..*$//' | sort -u)
-      if [[ -n "$reactor_modules" ]] ; then
-        local count
-        count=$(echo "$reactor_modules" | wc -l | tr -d ' ')
-        local reactor_label
-        if [[ ${mode} = "test" && "${testedDependents:-}" = "false" ]] ; then
-          reactor_label="Build reactor — dependencies compiled but only 
changed modules were tested"
-        else
-          reactor_label="All tested modules"
-        fi
+
+      if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
+        echo "" >> "$GITHUB_STEP_SUMMARY"
         echo "<details><summary><b>${reactor_label} ($count)</b></summary>" >> 
"$GITHUB_STEP_SUMMARY"
         echo "" >> "$GITHUB_STEP_SUMMARY"
-        echo "" >> "$comment_file"
-        echo "<details><summary>${reactor_label} ($count modules)</summary>" 
>> "$comment_file"
-        echo "" >> "$comment_file"
-        echo "$reactor_modules" | while read -r m; do
-          echo "- $m" >> "$GITHUB_STEP_SUMMARY"
-          echo "- $m" >> "$comment_file"
-        done
+      fi
+
+      echo "$reactor_modules" | while read -r m; do
+        [ -n "${GITHUB_STEP_SUMMARY:-}" ] && echo "- $m" >> 
"$GITHUB_STEP_SUMMARY"
+        echo "- $m" >> "$comment_file"
+      done
+
+      if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
         echo "" >> "$GITHUB_STEP_SUMMARY"
         echo "</details>" >> "$GITHUB_STEP_SUMMARY"
-        echo "" >> "$GITHUB_STEP_SUMMARY"
-        echo "" >> "$comment_file"
-        echo "</details>" >> "$comment_file"
       fi
+      echo "" >> "$comment_file"
+      echo "</details>" >> "$comment_file"
     fi
-  elif [[ ${buildAll} = "true" ]] ; then
-    echo "<!-- ci-tested-modules -->" > "$comment_file"
-    echo ":information_source: CI did not run targeted module tests (all 
projects built or tests skipped)." >> "$comment_file"
   fi
 
-  if [[ ${ret} -ne 0 ]] ; then
+  # Write step summary header
+  if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
+    {
+      echo ""
+      echo "### Tested modules"
+      echo ""
+      for w in $(echo "$final_pl" | tr ',' '\n'); do
+        echo "- \`$w\`"
+      done
+      echo ""
+    } >> "$GITHUB_STEP_SUMMARY"
+  fi
+
+  if [[ ${ret} -ne 0 ]]; then
     echo "Processing surefire and failsafe reports to create the summary"
-    echo -e "| Failed Test | Duration | Failure Type |\n| --- | --- | --- |"  
>> "$GITHUB_STEP_SUMMARY"
+    if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
+      echo -e "| Failed Test | Duration | Failure Type |\n| --- | --- | --- |" 
>> "$GITHUB_STEP_SUMMARY"
+    fi
     find . -path '*target/*-reports*' -iname '*.txt' -exec 
.github/actions/incremental-build/parse_errors.sh {} \;
   fi
 
diff --git a/.github/actions/incremental-build/manual-it-mapping.txt 
b/.github/actions/incremental-build/manual-it-mapping.txt
new file mode 100644
index 000000000000..042bf0cebdf1
--- /dev/null
+++ b/.github/actions/incremental-build/manual-it-mapping.txt
@@ -0,0 +1,15 @@
+# Manual integration test mapping
+#
+# Maps source modules (excluded from CI -amd expansion) to their
+# associated integration test suites that must be run manually.
+#
+# Format: source-artifact-id : it-module-path : command
+#
+# JBang integration tests
+camel-jbang-core:dsl/camel-jbang/camel-jbang-it:mvn verify -f 
dsl/camel-jbang/camel-jbang-it -Djbang-it-test
+camel-jbang-main:dsl/camel-jbang/camel-jbang-it:mvn verify -f 
dsl/camel-jbang/camel-jbang-it -Djbang-it-test
+camel-jbang-plugin-generate:dsl/camel-jbang/camel-jbang-it:mvn verify -f 
dsl/camel-jbang/camel-jbang-it -Djbang-it-test
+camel-jbang-plugin-edit:dsl/camel-jbang/camel-jbang-it:mvn verify -f 
dsl/camel-jbang/camel-jbang-it -Djbang-it-test
+camel-jbang-plugin-kubernetes:dsl/camel-jbang/camel-jbang-it:mvn verify -f 
dsl/camel-jbang/camel-jbang-it -Djbang-it-test
+camel-jbang-plugin-test:dsl/camel-jbang/camel-jbang-it:mvn verify -f 
dsl/camel-jbang/camel-jbang-it -Djbang-it-test
+camel-launcher:dsl/camel-jbang/camel-jbang-it:mvn verify -f 
dsl/camel-jbang/camel-jbang-it -Djbang-it-test
diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml
index 95419d8c7d2e..dcfc80711e73 100644
--- a/.github/workflows/main-build.yml
+++ b/.github/workflows/main-build.yml
@@ -68,7 +68,6 @@ jobs:
       - name: mvn test
         uses: ./.github/actions/incremental-build
         with:
-          mode: test
           github-token: ${{ secrets.GITHUB_TOKEN }}
           skip-mvnd-install: 'true'
           artifact-upload-suffix: main-java-${{ matrix.java }}
diff --git a/.github/workflows/pr-build-main.yml 
b/.github/workflows/pr-build-main.yml
index 539a1250555c..46145ecfc366 100644
--- a/.github/workflows/pr-build-main.yml
+++ b/.github/workflows/pr-build-main.yml
@@ -21,6 +21,8 @@ on:
   pull_request:
     branches:
       - main
+    # CI-only changes don't need a full build. Use workflow_dispatch to
+    # test CI changes: gh workflow run "Build and test" -f pr_number=XXXX -f 
pr_ref=branch-name
     paths-ignore:
       - .github/**
       - README.md
@@ -38,9 +40,19 @@ on:
         description: 'Git ref of the pull request branch'
         required: true
         type: string
+      extra_modules:
+        description: 'Additional modules to test (comma-separated paths, e.g. 
from /component-test)'
+        required: false
+        type: string
+        default: ''
+      skip_full_build:
+        description: 'Skip full regen build — use quick targeted build instead 
(for /component-test)'
+        required: false
+        type: boolean
+        default: false
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.event.pull_request.number || 
inputs.pr_number || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || 
inputs.pr_number || github.ref }}${{ inputs.extra_modules && '-component-test' 
|| '' }}
   cancel-in-progress: true
 
 permissions:
@@ -51,7 +63,6 @@ jobs:
     if: github.repository == 'apache/camel'
     permissions:
       contents: read
-      pull-requests: write
     runs-on: ubuntu-latest
     continue-on-error: ${{ matrix.experimental }}
     strategy:
@@ -78,7 +89,14 @@ jobs:
           java-version: ${{ matrix.java }}
           cache: 'maven'
       - name: maven build
+        if: ${{ !inputs.skip_full_build }}
         run: ./etc/scripts/regen.sh
+      - name: Quick dependency build
+        if: ${{ inputs.skip_full_build }}
+        shell: bash
+        env:
+          EXTRA_MODULES: ${{ inputs.extra_modules }}
+        run: ./mvnw -l build.log install -B -DskipTests -Dquickly -pl 
"$EXTRA_MODULES" -am
       - name: archive logs
         uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 
# v7.0.0
         if: always()
@@ -86,64 +104,41 @@ jobs:
           name: build-${{ matrix.java }}.log
           path: build.log
       - name: Fail if there are uncommitted changes
+        if: ${{ !inputs.skip_full_build }}
         shell: bash
         run: |
           [[ -z $(git status --porcelain) ]] || { echo 'There are uncommitted 
changes'; git status; echo; echo; git diff; exit 1; }
       - name: mvn test
         uses: ./.github/actions/incremental-build
         with:
-          mode: test
           pr-id: ${{ github.event.number || inputs.pr_number }}
           github-token: ${{ secrets.GITHUB_TOKEN }}
           skip-mvnd-install: 'true'
           artifact-upload-suffix: java-${{ matrix.java }}
-      - name: Post CI test summary comment
+          extra-modules: ${{ inputs.extra_modules || '' }}
+      # All non-experimental JDK matrix entries upload with overwrite: true.
+      # This ensures a comment is posted even if one JDK build fails — the
+      # content is identical across JDKs (same modules tested), so last writer 
wins.
+      - name: Save PR number and test comment for commenter workflow
         if: always() && !matrix.experimental
-        uses: actions/github-script@v8
-        with:
-          script: |
-            const fs = require('fs');
-            const commentFile = 'incremental-test-comment.md';
-            if (!fs.existsSync(commentFile)) return;
-            const body = fs.readFileSync(commentFile, 'utf8').trim();
-            if (!body) return;
-
-            const prNumber = ${{ github.event.number || inputs.pr_number || 0 
}};
-            if (!prNumber) {
-              core.warning('Could not determine PR number, skipping test 
summary comment');
-              return;
-            }
-
-            const marker = '<!-- ci-tested-modules -->';
-
-            try {
-              const { data: comments } = await 
github.rest.issues.listComments({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: prNumber,
-              });
-              const existing = comments.find(c => c.body && 
c.body.includes(marker));
-
-              if (existing) {
-                await github.rest.issues.updateComment({
-                  owner: context.repo.owner,
-                  repo: context.repo.repo,
-                  comment_id: existing.id,
-                  body: body,
-                });
-              } else {
-                await github.rest.issues.createComment({
-                  owner: context.repo.owner,
-                  repo: context.repo.repo,
-                  issue_number: prNumber,
-                  body: body,
-                });
-              }
-            } catch (error) {
-              core.warning(`Failed to post CI test summary comment: 
${error.message}`);
-            }
-      - name: mvn test parent pom dependencies changed
-        uses: ./.github/actions/detect-dependencies
+        shell: bash
+        env:
+          RUN_URL: ${{ github.server_url }}/${{ github.repository 
}}/actions/runs/${{ github.run_id }}
+        run: |
+          mkdir -p ci-comment-artifact
+          prNumber="${{ github.event.number || inputs.pr_number }}"
+          echo "$prNumber" > ci-comment-artifact/pr-number
+          if [ -f incremental-test-comment.md ]; then
+            cp incremental-test-comment.md ci-comment-artifact/
+            # Append link to the workflow run for detailed results
+            echo "" >> ci-comment-artifact/incremental-test-comment.md
+            echo "---" >> ci-comment-artifact/incremental-test-comment.md
+            echo ":gear: [View full build and test results](${RUN_URL})" >> 
ci-comment-artifact/incremental-test-comment.md
+          fi
+      - name: Upload CI comment artifact
+        if: always() && !matrix.experimental
+        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 
# v7.0.0
         with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          base-ref: ${{ github.base_ref || 'main' }}
+          name: ci-comment
+          path: ci-comment-artifact/
+          overwrite: true
diff --git a/.github/workflows/pr-commenter.yml 
b/.github/workflows/pr-commenter.yml
index 71a0cb190af5..6f1cd620e73f 100644
--- a/.github/workflows/pr-commenter.yml
+++ b/.github/workflows/pr-commenter.yml
@@ -95,7 +95,7 @@ jobs:
   
               * First-time contributors **require MANUAL approval** for the 
GitHub Actions to run
               * You can use the command \`/component-test 
(camel-)component-name1 (camel-)component-name2..\` to request a test from the 
test bot although they are normally detected and executed by CI.
-              * You can label PRs using \`build-all\`, \`build-dependents\`, 
\`skip-tests\` and \`test-dependents\` to fine-tune the checks executed by this 
PR.
+              * You can label PRs using \`skip-tests\` and \`test-dependents\` 
to fine-tune the checks executed by this PR.
               * Build and test logs are available in the summary page. 
**Only** [Apache Camel 
committers](https://camel.apache.org/community/team/#committers) have access to 
the summary.
               
               :warning: Be careful when sharing logs. Review their contents 
before sharing them publicly.`
diff --git a/.github/workflows/pr-manual-component-test.yml 
b/.github/workflows/pr-manual-component-test.yml
index 91beef14c671..915ffba1805d 100644
--- a/.github/workflows/pr-manual-component-test.yml
+++ b/.github/workflows/pr-manual-component-test.yml
@@ -28,20 +28,18 @@ jobs:
     name: PR comment
     if: ${{ github.repository == 'apache/camel' && 
github.event.issue.pull_request && (github.event.comment.author_association == 
'MEMBER' || github.event.comment.author_association == 'OWNER' || 
github.event.comment.author_association == 'CONTRIBUTOR') && 
startsWith(github.event.comment.body, '/component-test') }}
     permissions:
-      pull-requests: write # to comment on a pull request
-      actions: read # to download artifact
+      pull-requests: write
+      actions: write # to dispatch workflows
     runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        java: [ '21' ]
     steps:
       - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
         with:
           persist-credentials: false
-          submodules: recursive
+          sparse-checkout: components
+          sparse-checkout-cone-mode: true
       - name: Check Permission
         uses: 
actions-cool/check-user-permission@c21884f3dda18dafc2f8b402fe807ccc9ec1aa5e
-      - name: Retrieve sha
+      - name: Retrieve PR sha and ref
         id: pr
         env:
           PR_NUMBER: ${{ github.event.issue.number }}
@@ -51,9 +49,8 @@ jobs:
         run: |
           pr="$(gh api /repos/${GH_REPO}/pulls/${PR_NUMBER})"
           head_sha="$(echo "$pr" | jq -r .head.sha)"
+          head_ref="$(echo "$pr" | jq -r .head.ref)"
           # Check that the PR branch was not pushed to after the comment was 
created.
-          # Use the head commit date (not head.repo.pushed_at which is 
repo-level
-          # and changes whenever any branch is pushed, causing false 
negatives).
           commit="$(gh api /repos/${GH_REPO}/commits/${head_sha})"
           committed_at="$(echo "$commit" | jq -r .commit.committer.date)"
           if [[ $(date -d "$committed_at" +%s) -gt $(date -d "$COMMENT_AT" 
+%s) ]]; then
@@ -61,39 +58,56 @@ jobs:
               exit 1
           fi
           echo "pr_sha=$head_sha" >> $GITHUB_OUTPUT
+          echo "pr_ref=$head_ref" >> $GITHUB_OUTPUT
       - name: React to comment
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           GH_REPO: ${{ github.repository }}
         run: |
           gh api /repos/${GH_REPO}/issues/comments/${{ github.event.comment.id 
}}/reactions -f content="+1"
-      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
-        with:
-          ref: ${{ steps.pr.outputs.pr_sha }}
-          submodules: recursive
-      - id: install-packages
-        uses: ./.github/actions/install-packages
-      - name: Set up JDK ${{ matrix.java }}
-        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 
v5.2.0
-        with:
-          distribution: 'temurin'
-          java-version: ${{ matrix.java }}
-          cache: 'maven'
-      - id: test
+      - name: Resolve component paths and dispatch
         env:
-          comment_body: ${{ github.event.comment.body }}
-        name: Component test execution
-        uses: ./.github/actions/component-test
-        with:
-          run-id: ${{ github.run_id }}
-          pr-id: ${{ github.event.issue.number }}
-          comment-id: ${{ github.event.comment.id }}
-          comment-body: ${{ env.comment_body }}
-          artifact-upload-suffix: java-${{ matrix.java }}
-      - name: Post failure comment
-        if: failure() && steps.test.outcome != 'failure'
-        uses: 
peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # 
v5.0.0
-        with:
-          issue-number: ${{ github.event.issue.number }}
-          body: |
-            :x: The `/component-test` run failed. Please [check the 
logs](https://github.com/${{ github.repository }}/actions/runs/${{ 
github.run_id }}) for details.
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GH_REPO: ${{ github.repository }}
+          COMMENT_BODY: ${{ github.event.comment.body }}
+          PR_NUMBER: ${{ github.event.issue.number }}
+          PR_REF: ${{ steps.pr.outputs.pr_ref }}
+        run: |
+          componentList="${COMMENT_BODY:16}"
+          if [[ -z "$componentList" ]]; then
+            echo "No components specified. Expected format: /component-test 
component1 component2..."
+            exit 1
+          fi
+
+          # Resolve component names to module paths
+          pl=""
+          for component in ${componentList}; do
+            if [[ ${component} = camel-* ]]; then
+              componentPath="components/${component}"
+            else
+              componentPath="components/camel-${component}"
+            fi
+            if [[ -d "${componentPath}" ]]; then
+              # Find all sub-modules (pom.xml dirs) under the component
+              modules=$(find "${componentPath}" -name pom.xml -not -path 
"*/src/it/*" -not -path "*/target/*" -exec dirname {} \; | sort | tr -s "\n" 
",")
+              pl="${pl}${modules}"
+            else
+              echo "WARNING: Component path '${componentPath}' not found, 
skipping"
+            fi
+          done
+
+          # Strip trailing comma
+          pl="${pl%,}"
+
+          if [[ -z "$pl" ]]; then
+            echo "No valid component paths found"
+            exit 1
+          fi
+
+          echo "Dispatching main workflow with extra modules: $pl"
+          gh workflow run "Build and test" \
+            --repo "${GH_REPO}" \
+            -f pr_number="${PR_NUMBER}" \
+            -f pr_ref="${PR_REF}" \
+            -f extra_modules="${pl}" \
+            -f skip_full_build=true
diff --git a/.github/workflows/pr-test-commenter.yml 
b/.github/workflows/pr-test-commenter.yml
new file mode 100644
index 000000000000..3462f12b3ebe
--- /dev/null
+++ b/.github/workflows/pr-test-commenter.yml
@@ -0,0 +1,121 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Posts the CI test summary comment on PRs.
+# Uses workflow_run trigger so it runs in the base repo context with
+# full permissions — this allows posting comments on fork PRs too.
+
+name: Post CI test comment
+
+on:
+  workflow_run:
+    workflows: ["Build and test"]
+    types:
+      - completed
+
+jobs:
+  comment:
+    runs-on: ubuntu-latest
+    if: >
+      github.event.workflow_run.event == 'pull_request' ||
+      github.event.workflow_run.event == 'workflow_dispatch'
+    permissions:
+      pull-requests: write
+      actions: read
+    steps:
+      - name: Download CI comment artifact
+        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # 
v8
+        with:
+          script: |
+            const artifacts = await 
github.rest.actions.listWorkflowRunArtifacts({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              run_id: ${{ github.event.workflow_run.id }},
+            });
+            const match = artifacts.data.artifacts.find(a => a.name === 
'ci-comment');
+            if (!match) {
+              core.info('No ci-comment artifact found, skipping');
+              return;
+            }
+            const download = await github.rest.actions.downloadArtifact({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              artifact_id: match.id,
+              archive_format: 'zip',
+            });
+            const fs = require('fs');
+            fs.writeFileSync('${{ github.workspace }}/ci-comment.zip', 
Buffer.from(download.data));
+      - name: Extract artifact
+        run: |
+          if [ -f ci-comment.zip ]; then
+            unzip -o ci-comment.zip -d ci-comment-artifact
+          fi
+      - name: Post or update PR comment
+        if: always()
+        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # 
v8
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const fs = require('fs');
+            const prFile = 'ci-comment-artifact/pr-number';
+            const commentFile = 
'ci-comment-artifact/incremental-test-comment.md';
+
+            if (!fs.existsSync(prFile)) {
+              core.info('No PR number file found, skipping');
+              return;
+            }
+            const prNumber = parseInt(fs.readFileSync(prFile, 'utf8').trim(), 
10);
+            if (!prNumber) {
+              core.warning('Invalid PR number, skipping');
+              return;
+            }
+
+            if (!fs.existsSync(commentFile)) {
+              core.info('No comment file found, skipping');
+              return;
+            }
+            const body = fs.readFileSync(commentFile, 'utf8').trim();
+            if (!body) return;
+
+            const marker = '<!-- ci-tested-modules -->';
+
+            try {
+              const { data: comments } = await 
github.rest.issues.listComments({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: prNumber,
+              });
+              const existing = comments.find(c => c.body && 
c.body.includes(marker));
+
+              if (existing) {
+                await github.rest.issues.updateComment({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  comment_id: existing.id,
+                  body: body,
+                });
+              } else {
+                await github.rest.issues.createComment({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  issue_number: prNumber,
+                  body: body,
+                });
+              }
+            } catch (error) {
+              core.warning(`Failed to post CI test summary comment: 
${error.message}`);
+            }

Reply via email to