diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 2490a0f..0000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,69 +0,0 @@
-# Java Maven CircleCI 2.0 configuration file
-#
-# Check https://circleci.com/docs/2.0/language-java/ for more details
-#
-version: 2
-jobs:
- build:
- # Use "machine" instead of docker, as CCI doesnt' support DiD for testcontainers
- machine:
- # available machine images https://circleci.com/docs/2.0/configuration-reference/#available-machine-images
- image: circleci/classic:edge
-
- working_directory: ~/repo
-
- environment:
- # Customize the JVM maximum heap limit
- MAVEN_OPTS: -Xmx6400m
- # Set the GRAALVM_HOME env variable to the location where we will be installing GraalVM
- GRAALVM_HOME: /home/circleci/repo/.graalvm
-
- steps:
- - run:
- name: install java 11
- command: |
- sudo apt-get update
- sudo apt-get install openjdk-11-jre openjdk-11-jdk --fix-missing
- sudo update-alternatives --display java
- sudo update-alternatives --auto java
- sudo update-alternatives --display java
- # sudo update-alternatives --config java
- java --version
- # echo "JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/" >> ~/.profile
- # source ~/.profile
- export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
- export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
- export PATH=$PATH:$JAVA_HOME/bin
- echo $JAVA_HOME
-
- - checkout
-
- # Download and cache dependencies
- - restore_cache:
- keys:
- - v1-dependencies-{{ checksum "pom.xml" }}
- # fallback to using the latest cache if no exact match is found
- - v1-dependencies-
-
- - run: mvn dependency:go-offline
-
- - save_cache:
- paths:
- - ~/.m2
- key: v1-dependencies-{{ checksum "pom.xml" }}
-
- # run tests!
- - run:
- name: run tests
- command: |
- sudo update-alternatives --auto java
- java -version
- mvn -version
- which java
- which javac
- export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
- mvn -version
- mvn verify
- - store_test_results: # uploads the test metadata from the `target/surefire-reports` directory so that it can show up in the CircleCI dashboard.
- # Upload test results for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
- path: target/surefire-reports
diff --git a/.drone.yml b/.drone.yml
deleted file mode 100644
index 684f3bc..0000000
--- a/.drone.yml
+++ /dev/null
@@ -1,32 +0,0 @@
----
-kind: pipeline
-type: docker
-name: default
-
-steps:
-- name: build
- image: maven:3.6.3-jdk-11-slim
- commands:
- # turn off ITs until testcontainers works in drone
- - mvn test -DskipITs
-
-#pipeline:
-# - name: build
-# build:
-# image: quay.io/testcontainers/dind-drone-plugin
-# build_image: maven:3.6.3-jdk-11-slim
-## build_image: openjdk:8-jdk-alpine
-# # This specifies the command that should be executed to perform build, test and integration tests. Not to be confused with Drone's `command`:
-# cmd: mvn test
-# # Not mandatory, but enables pre-fetching of images in parallel with the build, so may save time:
-# # prefetch_images: // prefetch test containers here to save time
-# # - "redis:4.0.6"
-# # - "confluentinc/cp-kafka:5.3.1"
-
-# https://github.com/testcontainers/dind-drone-plugin
-# example https://github.com/testcontainers/dind-drone-plugin/blob/master/.drone.example.yml
-#pipeline:
-# build:
-# image: quay.io/testcontainers/dind-drone-plugin
-# build_image: openjdk:8-jdk
-# cmd: java -v
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..51d3208
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..2556e75
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,6 @@
+Description...
+
+### Checklist
+
+- [ ] Documentation (if applicable)
+- [ ] Changelog
diff --git a/.github/workflows/check-dependencies.yml b/.github/workflows/check-dependencies.yml
new file mode 100644
index 0000000..cd459d9
--- /dev/null
+++ b/.github/workflows/check-dependencies.yml
@@ -0,0 +1,19 @@
+name: PR Dependency Check
+
+on:
+ pull_request_target:
+ types: [opened, edited, closed, reopened]
+
+permissions:
+ issues: read
+ pull-requests: read
+ checks: write
+
+jobs:
+ check_dependencies:
+ runs-on: ubuntu-latest
+ name: Check Dependencies
+ steps:
+ - uses: astubbs/dependencies-action@feat/auto-unblock-children-on-merge
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml
new file mode 100644
index 0000000..9160bde
--- /dev/null
+++ b/.github/workflows/claude-code-review.yml
@@ -0,0 +1,38 @@
+name: Claude Code Review
+
+on:
+ pull_request:
+ types: [opened, synchronize, ready_for_review, reopened]
+ # Optional: Only run on specific file changes
+ # paths:
+ # - "src/**/*.ts"
+ # - "src/**/*.tsx"
+ # - "src/**/*.js"
+ # - "src/**/*.jsx"
+
+jobs:
+ claude-review:
+ if: github.event.sender.type != 'Bot'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+ id-token: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code Review
+ id: claude-review
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+ plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
+ plugins: 'code-review@claude-code-plugins'
+ prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
new file mode 100644
index 0000000..9471a05
--- /dev/null
+++ b/.github/workflows/claude.yml
@@ -0,0 +1,49 @@
+name: Claude Code
+
+on:
+ issue_comment:
+ types: [created]
+ pull_request_review_comment:
+ types: [created]
+ issues:
+ types: [opened, assigned]
+ pull_request_review:
+ types: [submitted]
+
+jobs:
+ claude:
+ if: |
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+ id-token: write
+ actions: read # Required for Claude to read CI results on PRs
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code
+ id: claude
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+
+ # This is an optional setting that allows Claude to read CI results on PRs
+ additional_permissions: |
+ actions: read
+
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
+ # prompt: 'Update the pull request description to include a summary of changes.'
+
+ # Optional: Add claude_args to customize behavior and configuration
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+ # claude_args: '--allowed-tools Bash(gh pr:*)'
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 0000000..1752fcc
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,266 @@
+# PR builds:
+# Unit, Integration - in parallel with fail-fast
+# plus static analysis (SpotBugs), dependency scan, duplicate detection,
+# file similarity, and mutation testing (PIT).
+#
+# Push builds (master):
+# Single-version build (Kafka pinned to 2.3.1 via Confluent Platform 5.3.1).
+
+name: Build and Test
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+
+permissions:
+ contents: read
+ pull-requests: write
+ checks: write
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+
+ # ── PR Builds ──────────────────────────────────────────────────────────
+
+ # Pre-warm the Maven dependency cache so the test jobs can start
+ # with everything local.
+ prepare-deps:
+ if: github.event_name == 'pull_request'
+ name: "Prepare Maven Cache"
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+ # Explicit cache steps below instead of setup-java's built-in
+ # `cache: maven`. Built-in does NOT save when the primary key
+ # was a hit; rotating save key below forces a save every run.
+ - name: Restore Maven cache
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/.m2/repository
+ key: setup-java-Linux-x64-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ setup-java-Linux-x64-maven-
+ - name: Download all dependencies
+ run: mvn --batch-mode dependency:go-offline -DincludeScope=test -U
+ - name: Save Maven cache (rotating key)
+ if: always()
+ uses: actions/cache/save@v4
+ with:
+ path: ~/.m2/repository
+ key: setup-java-Linux-x64-maven-${{ hashFiles('**/pom.xml') }}-${{ github.run_id }}
+
+ # Unit + integration in parallel - fail-fast cancels the rest if any fails
+ test:
+ if: github.event_name == 'pull_request'
+ needs: prepare-deps
+ strategy:
+ fail-fast: true
+ matrix:
+ include:
+ - suite: unit
+ name: "Unit Tests"
+ cmd: "mvn --batch-mode test -DskipITs"
+ timeout: 15
+ - suite: integration
+ name: "Integration Tests"
+ cmd: "mvn --batch-mode verify -DskipUTs -Dsurefire.skip=true"
+ timeout: 60
+ name: "${{ matrix.name }}"
+ runs-on: ubuntu-latest
+ timeout-minutes: ${{ matrix.timeout }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+ - name: Restore Maven cache
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/.m2/repository
+ key: setup-java-Linux-x64-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ setup-java-Linux-x64-maven-
+ - name: ${{ matrix.name }}
+ run: ${{ matrix.cmd }}
+ - name: Upload coverage to Codecov
+ if: always()
+ uses: codecov/codecov-action@v5
+ with:
+ files: '**/target/site/jacoco/jacoco.xml'
+ flags: ${{ matrix.suite }}
+ token: ${{ secrets.CODECOV_TOKEN }}
+
+ # Duplicate code detection using both PMD CPD and jscpd engines.
+ # See https://github.com/astubbs/duplicate-code-cross-check
+ duplicate-detection:
+ if: github.event_name == 'pull_request'
+ name: "Duplicate Code Check"
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: astubbs/duplicate-code-cross-check@v1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ directories: 'streams-module/src streams-main-dagger/src'
+ cpd-max-duplication: '5'
+ jscpd-max-duplication: '4'
+
+ # File similarity detection - finds files that are semantically similar overall
+ file-similarity:
+ if: github.event_name == 'pull_request'
+ name: "File Similarity Check"
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: astubbs/duplicate-code-detection-tool@feat/base-vs-pr-comparison
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ directories: 'streams-module/src,streams-main-dagger/src'
+ file_extensions: 'java'
+ ignore_below: 30
+ fail_above: 80
+ warn_above: 50
+ one_comment: true
+ compare_with_base: true
+ max_increase: 10
+
+ # SpotBugs static analysis - finds null derefs, concurrency issues, resource leaks
+ spotbugs:
+ if: github.event_name == 'pull_request'
+ name: "SpotBugs"
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+ cache: 'maven'
+ - name: Compile and run SpotBugs
+ run: mvn --batch-mode compile spotbugs:spotbugs -pl streams-module -am
+ - name: Post SpotBugs annotations on PR
+ if: always()
+ uses: jwgmeligmeyling/spotbugs-github-action@v1.2
+ with:
+ path: '**/target/spotbugsXml.xml'
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ # Dependency vulnerability scanning - GitHub's own dependency review
+ dependency-scan:
+ if: github.event_name == 'pull_request'
+ name: "Dependency Vulnerabilities"
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/dependency-review-action@v4
+ with:
+ fail-on-severity: high
+ comment-summary-in-pr: always
+
+ # Mutation testing (PIT) - verifies test assertions are meaningful
+ mutation-testing:
+ if: github.event_name == 'pull_request'
+ name: "Mutation Testing (PIT)"
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+ cache: 'maven'
+ - name: Run PIT mutation testing
+ run: mvn --batch-mode test-compile org.pitest:pitest-maven:mutationCoverage -DtargetClasses="io.confluent.ps.streams.referenceapp.*" -DtargetTests="io.confluent.ps.streams.referenceapp.*" -Dthreads=4 -pl streams-module -am
+ - name: Upload PIT report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: pit-report
+ path: '**/target/pit-reports/**'
+ - name: Post PIT summary to PR
+ if: always()
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const { execSync } = require('child_process');
+ const files = execSync('find . -path "*/pit-reports/*/mutations.csv" -type f').toString().trim().split('\n').filter(f => f);
+ let body;
+ if (files.length === 0) {
+ body = `## :x: Mutation Testing (PIT) Report\n\n`;
+ body += `**PIT did not produce a report.** Most commonly this means a test failed in the baseline (PIT runs all tests unmodified first to establish green) and PIT aborted before mutating. See the "Run PIT mutation testing" step logs for the failing test, then either fix it or add it to \`-DexcludedTestClasses\` in the workflow.\n`;
+ } else {
+ let killed = 0, survived = 0, noCov = 0, total = 0;
+ for (const f of files) {
+ const lines = fs.readFileSync(f, 'utf8').split('\n').filter(l => l.trim());
+ for (const line of lines) {
+ total++;
+ if (line.includes('KILLED')) killed++;
+ else if (line.includes('SURVIVED')) survived++;
+ else if (line.includes('NO_COVERAGE')) noCov++;
+ }
+ }
+ const score = total > 0 ? ((killed / total) * 100).toFixed(1) : '0';
+ body = `## Mutation Testing (PIT) Report\n\n`;
+ body += `| Metric | Value |\n|--------|-------|\n`;
+ body += `| Mutations generated | ${total} |\n`;
+ body += `| Killed (detected) | ${killed} |\n`;
+ body += `| Survived (missed) | ${survived} |\n`;
+ body += `| No coverage | ${noCov} |\n`;
+ body += `| **Mutation score** | **${score}%** |\n\n`;
+ body += `Full HTML report available as artifact: \`pit-report\`\n`;
+ }
+ const comments = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number });
+ const existing = comments.data.find(c => c.body.includes('Mutation Testing (PIT) Report'));
+ if (existing) {
+ await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body });
+ } else {
+ await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body });
+ }
+
+ # ── Push Builds (master) ───────────────────────────────────────────────
+
+ # Full build on master push. Kafka version is pinned to 2.3.1 via
+ # Confluent Platform 5.3.1, so no version matrix.
+ build:
+ if: github.event_name == 'push'
+ name: "Build"
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+ cache: 'maven'
+ - name: Build and Test
+ run: mvn --batch-mode clean verify
+ - name: Upload coverage to Codecov
+ if: always()
+ uses: codecov/codecov-action@v5
+ with:
+ files: '**/target/site/jacoco/jacoco.xml'
+ token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f614575..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-language: java
-jdk:
- - openjdk11
-
-sudo: required
-services:
- - docker
-
-before_cache:
- - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
-cache:
- directories:
- - "$HOME/.gradle/caches/"
- - "$HOME/.gradle/wrapper/"
-
-before_install:
- - sudo rm /usr/local/bin/docker-compose
- - curl -L https://github.com/docker/compose/releases/download/1.24.1/docker-compose-Linux-x86_64 > docker-compose
- - chmod +x docker-compose
- - sudo mv docker-compose /usr/local/bin
-
-# Weirdly have to run maven clean, or Dagger complains: "Could not generate unknown file: Attempt to recreate a file for type" for all modules, components. Doesn't happen in Intellij.
-script: mvn clean verify
-
-after_success:
- - bash <(curl -s https://codecov.io/bash)
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9df48d3..9eac4dc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,9 +25,9 @@
5.3.1
5.5.2
1.5.2
-
+
1.8.1
- 1.18.10
+ 1.18.34
2.24
2.22.2
1.12.1
@@ -45,6 +45,8 @@
maven-surefire-plugin
${maven.version}
+
+ @{argLine} --add-opens java.base/java.lang=ALL-UNNAMED
**/integrationTests/**/*.java
@@ -87,7 +89,6 @@
- src/main/scala
${project.build.directory}/generated-sources
@@ -100,7 +101,6 @@
- src/test/scala
${project.build.directory}/generated-sources
@@ -137,7 +137,7 @@
org.jacoco
jacoco-maven-plugin
- 0.8.4
+ 0.8.12
@@ -150,7 +150,6 @@
**/*_Factory.class
**/model/avro/**/*.class
- **/model/finance/avro/**/*.class
@@ -168,12 +167,45 @@
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.8.6.0
+
+ false
+ Max
+
+
+
+ org.pitest
+ pitest-maven
+ 1.17.0
+
+
+ org.pitest
+ pitest-junit5-plugin
+ 1.2.1
+
+
+
+
+ io.confluent.ps.streams.referenceapp.*
+
+
+ io.confluent.ps.streams.referenceapp.*
+
+
+ HTML
+ XML
+ CSV
+
+
+
-
org.apache.kafka
kafka-streams
diff --git a/streams-module/pom.xml b/streams-module/pom.xml
index 0849ddb..b7cde26 100644
--- a/streams-module/pom.xml
+++ b/streams-module/pom.xml
@@ -20,7 +20,6 @@
-
org.apache.kafka
kafka-streams
@@ -204,7 +203,6 @@
- src/main/scala
${project.build.directory}/generated-sources
@@ -217,7 +215,6 @@
- src/test/scala
${project.build.directory}/generated-sources