From 2331cbf0a7d2edfeed9310d11f99bcd25a6b4165 Mon Sep 17 00:00:00 2001 From: Thibault Pensec Date: Wed, 10 Jun 2026 09:59:34 +0200 Subject: [PATCH 1/6] Publish SNAPSHOT artifacts during PR builder execution --- .github/workflows/build.yml | 6 +++ CLAUDE.md | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 CLAUDE.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d48f197a1..35c3dcccd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,3 +25,9 @@ jobs: uses: gradle/gradle-build-action@v2.4.2 with: arguments: build jacocoTestReport jacocoTestCoverageVerification + + - name: Publish SNAPSHOT artifacts + if: github.event_name == 'pull_request' + uses: gradle/gradle-build-action@v2.4.2 + with: + arguments: publishToSonatype -PmavenRepoUsername=${{ secrets.MAVEN_USERNAME }} -PmavenRepoPassword=${{ secrets.MAVEN_PASSWORD }} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..efff0dfc9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,89 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build & Test Commands + +```bash +# Full build (default task) +./gradlew build + +# Build with coverage reports (used in CI) +./gradlew build jacocoTestReport jacocoTestCoverageVerification + +# Run all tests +./gradlew test + +# Run tests for a specific module +./gradlew :symphony-bdk-core:test + +# Run a single test class or method +./gradlew :symphony-bdk-core:test --tests "com.symphony.bdk.core.service.MessageServiceTest" +./gradlew :symphony-bdk-core:test --tests "com.symphony.bdk.core.service.MessageServiceTest.shouldSendMessage" + +# Publish to local Maven repository +./gradlew publishToMavenLocal + +# OWASP dependency vulnerability check (requires NVD_API_KEY env var for reasonable speed) +./gradlew dependencyCheck + +# Check for dependency updates +./gradlew dependencyUpdates +``` + +## Module Architecture + +The project is a multi-module Gradle build. All modules import the `symphony-bdk-bom` platform for dependency version alignment. + +### Core modules + +| Module | Role | +|--------|------| +| `symphony-bdk-bom` | Bill of Materials — version constraints for all dependencies | +| `symphony-bdk-config` | Loads and parses `bdk-config.yaml` into `BdkConfig` | +| `symphony-bdk-core` | Entry point, services, auth, datafeed loop, activity framework | +| `symphony-bdk-extension-api` | SPI for third-party BDK extensions | + +### Abstraction layers with multiple implementations + +| API module | Implementations | +|------------|-----------------| +| `symphony-bdk-http:symphony-bdk-http-api` | `symphony-bdk-http-jersey2`, `symphony-bdk-http-webclient` | +| `symphony-bdk-template:symphony-bdk-template-api` | `symphony-bdk-template-freemarker`, `symphony-bdk-template-handlebars` | + +### Spring Boot integration + +`symphony-bdk-spring:symphony-bdk-core-spring-boot-starter` and `symphony-bdk-app-spring-boot-starter` wrap the core in auto-configured Spring beans. These do not change core behaviour — they only wire configuration and lifecycle. + +### Test utilities + +`symphony-bdk-test:symphony-bdk-test-jupiter` provides JUnit 5 extensions; `symphony-bdk-test-spring-boot` wraps them for Spring context tests. + +## symphony-bdk-core Internals + +`SymphonyBdk` is the user-facing entry point. It is built via `SymphonyBdkBuilder` and owns a `ServiceFactory` that instantiates all services lazily. + +Key sub-packages: + +- **`auth`** — session authentication and JWT token management (`AuthSession`, `BotAuthenticator`, `OboAuthenticator`) +- **`service`** — one service class per Symphony API domain (`MessageService`, `StreamService`, `UserService`, `DatafeedService`, etc.) +- **`activity`** — activity framework: `ActivityRegistry` dispatches `DatafeedEvent`s to registered `AbstractActivity` handlers (command, form reply, room events) +- **`retry`** — Resilience4j-backed retry decorators applied to all HTTP calls +- **`client`** — HTTP client load-balancing and exception translation + +OBO (On-Behalf-Of) flows are surfaced through `OboServices` / `OboService`, which mirror the main services but authenticate with a delegated session. + +## Build Conventions (`buildSrc/`) + +Four Groovy convention plugins used by sub-modules: + +- `bdk.java-common-conventions` — Java 17, UTF-8, JaCoCo, JUnit Platform, sources+javadoc jars, BOM platform import +- `bdk.java-library-conventions` — extends common + `java-library` plugin (used by all published libs) +- `bdk.java-publish-conventions` — `maven-publish` + `signing`; signing is **only required for release versions** (`isReleaseVersion = !version.endsWith('SNAPSHOT')`) +- `bdk.java-codegen-conventions` — OpenAPI Generator (Jersey2, Java 8 date library) reading `src/main/resources/api.yaml`; generated sources land in `build/generated/openapi` + +## Publishing + +Snapshots are published to Sonatype OSSRH via the `publishToSonatype` Gradle task (nexus-publish-plugin). This task is automatically triggered in CI on every PR build. Releases use `publishToSonatype closeAndReleaseStagingRepository` and are triggered by a GitHub Release event. + +Credentials are passed as Gradle properties: `-PmavenRepoUsername` and `-PmavenRepoPassword`. From a350e4bb4a0faedf79bb83df3b5cb9b14f86ab31 Mon Sep 17 00:00:00 2001 From: Thibault Pensec Date: Wed, 10 Jun 2026 10:20:11 +0200 Subject: [PATCH 2/6] Update Gradle wrapper validation to use gradle/actions/wrapper-validation@v4 The old gradle/wrapper-validation-action is deprecated and was causing CI failures. --- .github/workflows/gradle-wrapper-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 611ae04b8..332400905 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -9,4 +9,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 + - uses: gradle/actions/wrapper-validation@v4 From 21f422e448c73e02e57fb247bfa3007354b04c79 Mon Sep 17 00:00:00 2001 From: Thibault Pensec Date: Wed, 10 Jun 2026 10:54:28 +0200 Subject: [PATCH 3/6] Fix SNAPSHOT repository URL for Sonatype Central Portal --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 547a5dbd3..c8edbbc9e 100644 --- a/build.gradle +++ b/build.gradle @@ -49,7 +49,7 @@ nexusPublishing { username = project.ext.mavenRepoUsername password = project.ext.mavenRepoPassword nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) - snapshotRepositoryUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/content/repositories/snapshots/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) } } } From 7c38d22fea74817a6e1929652648840114e53310 Mon Sep 17 00:00:00 2001 From: Thibault Pensec Date: Wed, 10 Jun 2026 17:03:40 +0200 Subject: [PATCH 4/6] Do not try to publish snapshot in case of a fork PR --- .github/workflows/build.yml | 2 +- AGENTS.md | 152 ++++++++++++++++++++++++++++++++++++ CLAUDE.md | 63 +++++++++++++++ 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 35c3dcccd..d233dc0da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: arguments: build jacocoTestReport jacocoTestCoverageVerification - name: Publish SNAPSHOT artifacts - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && secrets.MAVEN_USERNAME != '' && secrets.MAVEN_PASSWORD != '' uses: gradle/gradle-build-action@v2.4.2 with: arguments: publishToSonatype -PmavenRepoUsername=${{ secrets.MAVEN_USERNAME }} -PmavenRepoPassword=${{ secrets.MAVEN_PASSWORD }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..2cb03ee50 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,152 @@ +# AGENTS.md + +This file provides guidance to AI Coding Assistants when working with code in this repository. + +## Build & Test Commands + +```bash +# Full build (default task) +./gradlew build + +# Build with coverage reports (used in CI) +./gradlew build jacocoTestReport jacocoTestCoverageVerification + +# Run all tests +./gradlew test + +# Run tests for a specific module +./gradlew :symphony-bdk-core:test + +# Run a single test class or method +./gradlew :symphony-bdk-core:test --tests "com.symphony.bdk.core.service.MessageServiceTest" +./gradlew :symphony-bdk-core:test --tests "com.symphony.bdk.core.service.MessageServiceTest.shouldSendMessage" + +# Publish to local Maven repository +./gradlew publishToMavenLocal + +# OWASP dependency vulnerability check (requires NVD_API_KEY env var for reasonable speed) +./gradlew dependencyCheck + +# Check for dependency updates +./gradlew dependencyUpdates +``` + +## Module Architecture + +The project is a multi-module Gradle build. All modules import the `symphony-bdk-bom` platform for dependency version alignment. + +### Core modules + +| Module | Role | +|--------|------| +| `symphony-bdk-bom` | Bill of Materials — version constraints for all dependencies | +| `symphony-bdk-config` | Loads and parses `bdk-config.yaml` into `BdkConfig` | +| `symphony-bdk-core` | Entry point, services, auth, datafeed loop, activity framework | +| `symphony-bdk-extension-api` | SPI for third-party BDK extensions | + +### Abstraction layers with multiple implementations + +| API module | Implementations | +|------------|-----------------| +| `symphony-bdk-http:symphony-bdk-http-api` | `symphony-bdk-http-jersey2`, `symphony-bdk-http-webclient` | +| `symphony-bdk-template:symphony-bdk-template-api` | `symphony-bdk-template-freemarker`, `symphony-bdk-template-handlebars` | + +### Spring Boot integration + +`symphony-bdk-spring:symphony-bdk-core-spring-boot-starter` and `symphony-bdk-app-spring-boot-starter` wrap the core in auto-configured Spring beans. These do not change core behaviour — they only wire configuration and lifecycle. + +### Test utilities + +`symphony-bdk-test:symphony-bdk-test-jupiter` provides JUnit 5 extensions; `symphony-bdk-test-spring-boot` wraps them for Spring context tests. + +## symphony-bdk-core Internals + +`SymphonyBdk` is the user-facing entry point. It is built via `SymphonyBdkBuilder` and owns a `ServiceFactory` that instantiates all services lazily. + +Key sub-packages: + +- **`auth`** — session authentication and JWT token management (`AuthSession`, `BotAuthenticator`, `OboAuthenticator`) +- **`service`** — one service class per Symphony API domain (`MessageService`, `StreamService`, `UserService`, `DatafeedService`, etc.) +- **`activity`** — activity framework: `ActivityRegistry` dispatches `DatafeedEvent`s to registered `AbstractActivity` handlers (command, form reply, room events) +- **`retry`** — Resilience4j-backed retry decorators applied to all HTTP calls +- **`client`** — HTTP client load-balancing and exception translation + +OBO (On-Behalf-Of) flows are surfaced through `OboServices` / `OboService`, which mirror the main services but authenticate with a delegated session. + +## Build Conventions (`buildSrc/`) + +Four Groovy convention plugins used by sub-modules: + +- `bdk.java-common-conventions` — Java 17, UTF-8, JaCoCo, JUnit Platform, sources+javadoc jars, BOM platform import +- `bdk.java-library-conventions` — extends common + `java-library` plugin (used by all published libs) +- `bdk.java-publish-conventions` — `maven-publish` + `signing`; signing is **only required for release versions** (`isReleaseVersion = !version.endsWith('SNAPSHOT')`) +- `bdk.java-codegen-conventions` — OpenAPI Generator (Jersey2, Java 8 date library) reading `src/main/resources/api.yaml`; generated sources land in `build/generated/openapi` + +## Publishing + +Snapshots are published to Sonatype OSSRH via the `publishToSonatype` Gradle task (nexus-publish-plugin). This task is automatically triggered in CI on every PR build. Releases use `publishToSonatype closeAndReleaseStagingRepository` and are triggered by a GitHub Release event. + +Credentials are passed as Gradle properties: `-PmavenRepoUsername` and `-PmavenRepoPassword`. + +# context-mode — MANDATORY routing rules + +You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session. + +## BLOCKED commands — do NOT attempt these + +### curl / wget — BLOCKED +Any Bash command containing `curl` or `wget` is intercepted and replaced with an error message. Do NOT retry. +Instead use: +- `ctx_fetch_and_index(url, source)` to fetch and index web pages +- `ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox + +### Inline HTTP — BLOCKED +Any Bash command containing `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, or `http.request(` is intercepted and replaced with an error message. Do NOT retry with Bash. +Instead use: +- `ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context + +### WebFetch — BLOCKED +WebFetch calls are denied entirely. The URL is extracted and you are told to use `ctx_fetch_and_index` instead. +Instead use: +- `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` to query the indexed content + +## REDIRECTED tools — use sandbox equivalents + +### Bash (>20 lines output) +Bash is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands. +For everything else, use: +- `ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call +- `ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context + +### Read (for analysis) +If you are reading a file to **Edit** it → Read is correct (Edit needs content in context). +If you are reading to **analyze, explore, or summarize** → use `ctx_execute_file(path, language, code)` instead. Only your printed summary enters context. The raw file content stays in the sandbox. + +### Grep (large results) +Grep results can flood context. Use `ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context. + +## Tool selection hierarchy + +1. **GATHER**: `ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls. +2. **FOLLOW-UP**: `ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call. +3. **PROCESSING**: `ctx_execute(language, code)` | `ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context. +4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context. +5. **INDEX**: `ctx_index(content, source)` — Store content in FTS5 knowledge base for later search. + +## Subagent routing + +When spawning subagents (Agent/Task tool), the routing block is automatically injected into their prompt. Bash-type subagents are upgraded to general-purpose so they have access to MCP tools. You do NOT need to manually instruct subagents about context-mode. + +## Output constraints + +- Keep responses under 500 words. +- Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description. +- When indexing content, use descriptive source labels so others can `ctx_search(source: "label")` later. + +## ctx commands + +| Command | Action | +|---------|--------| +| `ctx stats` | Call the `ctx_stats` MCP tool and display the full output verbatim | +| `ctx doctor` | Call the `ctx_doctor` MCP tool, run the returned shell command, display as checklist | +| `ctx upgrade` | Call the `ctx_upgrade` MCP tool, run the returned shell command, display as checklist | diff --git a/CLAUDE.md b/CLAUDE.md index efff0dfc9..04eeeb34f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -87,3 +87,66 @@ Four Groovy convention plugins used by sub-modules: Snapshots are published to Sonatype OSSRH via the `publishToSonatype` Gradle task (nexus-publish-plugin). This task is automatically triggered in CI on every PR build. Releases use `publishToSonatype closeAndReleaseStagingRepository` and are triggered by a GitHub Release event. Credentials are passed as Gradle properties: `-PmavenRepoUsername` and `-PmavenRepoPassword`. + +# context-mode — MANDATORY routing rules + +You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session. + +## BLOCKED commands — do NOT attempt these + +### curl / wget — BLOCKED +Any Bash command containing `curl` or `wget` is intercepted and replaced with an error message. Do NOT retry. +Instead use: +- `ctx_fetch_and_index(url, source)` to fetch and index web pages +- `ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox + +### Inline HTTP — BLOCKED +Any Bash command containing `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, or `http.request(` is intercepted and replaced with an error message. Do NOT retry with Bash. +Instead use: +- `ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context + +### WebFetch — BLOCKED +WebFetch calls are denied entirely. The URL is extracted and you are told to use `ctx_fetch_and_index` instead. +Instead use: +- `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` to query the indexed content + +## REDIRECTED tools — use sandbox equivalents + +### Bash (>20 lines output) +Bash is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands. +For everything else, use: +- `ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call +- `ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context + +### Read (for analysis) +If you are reading a file to **Edit** it → Read is correct (Edit needs content in context). +If you are reading to **analyze, explore, or summarize** → use `ctx_execute_file(path, language, code)` instead. Only your printed summary enters context. The raw file content stays in the sandbox. + +### Grep (large results) +Grep results can flood context. Use `ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context. + +## Tool selection hierarchy + +1. **GATHER**: `ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls. +2. **FOLLOW-UP**: `ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call. +3. **PROCESSING**: `ctx_execute(language, code)` | `ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context. +4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context. +5. **INDEX**: `ctx_index(content, source)` — Store content in FTS5 knowledge base for later search. + +## Subagent routing + +When spawning subagents (Agent/Task tool), the routing block is automatically injected into their prompt. Bash-type subagents are upgraded to general-purpose so they have access to MCP tools. You do NOT need to manually instruct subagents about context-mode. + +## Output constraints + +- Keep responses under 500 words. +- Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description. +- When indexing content, use descriptive source labels so others can `ctx_search(source: "label")` later. + +## ctx commands + +| Command | Action | +|---------|--------| +| `ctx stats` | Call the `ctx_stats` MCP tool and display the full output verbatim | +| `ctx doctor` | Call the `ctx_doctor` MCP tool, run the returned shell command, display as checklist | +| `ctx upgrade` | Call the `ctx_upgrade` MCP tool, run the returned shell command, display as checklist | From 240b9660c9b9ad21f4a6546868b045c104d14b6e Mon Sep 17 00:00:00 2001 From: Thibault Pensec Date: Wed, 10 Jun 2026 17:15:40 +0200 Subject: [PATCH 5/6] Restart PR Builder --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d233dc0da..9cd96bd31 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,3 +31,4 @@ jobs: uses: gradle/gradle-build-action@v2.4.2 with: arguments: publishToSonatype -PmavenRepoUsername=${{ secrets.MAVEN_USERNAME }} -PmavenRepoPassword=${{ secrets.MAVEN_PASSWORD }} + From 82aa285ee610d465e2cb3d6a08ea4f5cf03625f8 Mon Sep 17 00:00:00 2001 From: Thibault Pensec Date: Wed, 10 Jun 2026 17:26:40 +0200 Subject: [PATCH 6/6] Cache OWASP Dependency Check database in CI --- .github/workflows/cve-scanning-gradle.yml | 7 +++++++ build.gradle | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cve-scanning-gradle.yml b/.github/workflows/cve-scanning-gradle.yml index 4687e704f..4dc47ad4f 100644 --- a/.github/workflows/cve-scanning-gradle.yml +++ b/.github/workflows/cve-scanning-gradle.yml @@ -24,6 +24,13 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} + - name: Cache OWASP Dependency Check Database + uses: actions/cache@v4 + with: + path: ~/.gradle/dependency-check-data + key: ${{ runner.os }}-owasp-db-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-owasp-db- - name: Build with Gradle # The build action is not strictly necessary as dependencyCheckAggregate will build the project # but it's good practice to have it as a separate step to catch build errors earlier. diff --git a/build.gradle b/build.gradle index c8edbbc9e..48618be98 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ dependencyCheck { failBuildOnCVSS=5 suppressionFile="./allow-list.xml" data { - directory = "${buildDir}/dependency-check-data" + directory = "${System.getProperty('user.home')}/.gradle/dependency-check-data" } nvd { apiKey = System.getenv("NVD_API_KEY") ?: (project.findProperty("dependencyCheck.nvd.apiKey") ?: "")