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

wusheng pushed a commit to branch split-claude-md-plugin-dev
in repository https://gitbox.apache.org/repos/asf/skywalking-java.git

commit fa5805c451ec2ab60559893f068c4d9e9a2d87db
Author: Wu Sheng <[email protected]>
AuthorDate: Thu Feb 12 20:13:34 2026 +0800

    Split plugin development guide from root CLAUDE.md into module-level files
    
    Move plugin development content (V2/V1 APIs, class matching, tracing 
concepts,
    meter APIs, plugin test framework, etc.) into dedicated CLAUDE.md files 
under
    apm-sniffer/apm-sdk-plugin/ and apm-sniffer/bootstrap-plugins/, reducing the
    root CLAUDE.md from ~700 lines to ~300 lines for better maintainability.
---
 CLAUDE.md                               | 423 +-------------------------------
 apm-sniffer/apm-sdk-plugin/CLAUDE.md    | 401 ++++++++++++++++++++++++++++++
 apm-sniffer/bootstrap-plugins/CLAUDE.md |  53 ++++
 3 files changed, 465 insertions(+), 412 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 90e3bf0f6c..02c69c17e3 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -110,11 +110,12 @@ The agent uses ByteBuddy for bytecode manipulation at 
runtime:
 **1. SDK Plugins** (`apm-sniffer/apm-sdk-plugin/`)
 - Framework-specific instrumentations (70+ plugins)
 - Examples: grpc-1.x, spring, dubbo, mybatis, mongodb, redis, etc.
-- Pattern: One directory per library/framework version
+- See `apm-sniffer/apm-sdk-plugin/CLAUDE.md` for plugin development guide
 
 **2. Bootstrap Plugins** (`apm-sniffer/bootstrap-plugins/`)
 - Load at JVM bootstrap phase for JDK-level instrumentation
 - Examples: jdk-threading, jdk-http, jdk-httpclient, 
jdk-virtual-thread-executor
+- See `apm-sniffer/bootstrap-plugins/CLAUDE.md` for bootstrap plugin guide
 
 **3. Optional Plugins** (`apm-sniffer/optional-plugins/`)
 - Not included by default, user must copy to plugins directory
@@ -122,237 +123,6 @@ The agent uses ByteBuddy for bytecode manipulation at 
runtime:
 **4. Optional Reporter Plugins** (`apm-sniffer/optional-reporter-plugins/`)
 - Alternative data collection backends (e.g., Kafka)
 
-### Plugin Instrumentation APIs (v1 vs v2)
-
-The agent provides two instrumentation APIs. **V2 is recommended** for all new 
plugins; v1 is legacy and should only be used for maintaining existing plugins.
-
-#### V2 API (Recommended)
-
-V2 provides a `MethodInvocationContext` that is shared across all interception 
phases (`beforeMethod`, `afterMethod`, `handleMethodException`), allowing you 
to pass data (e.g., spans) between phases.
-
-**Instrumentation class (extends `ClassEnhancePluginDefineV2`):**
-```java
-public class XxxInstrumentation extends 
ClassInstanceMethodsEnhancePluginDefineV2 {
-    @Override
-    protected ClassMatch enhanceClass() {
-        return NameMatch.byName("target.class.Name");
-    }
-
-    @Override
-    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
-        return new ConstructorInterceptPoint[] { ... };
-    }
-
-    @Override
-    public InstanceMethodsInterceptV2Point[] 
getInstanceMethodsInterceptV2Points() {
-        return new InstanceMethodsInterceptV2Point[] {
-            new InstanceMethodsInterceptV2Point() {
-                @Override
-                public ElementMatcher<MethodDescription> getMethodsMatcher() {
-                    return named("targetMethod");
-                }
-
-                @Override
-                public String getMethodsInterceptorV2() {
-                    return 
"org.apache.skywalking.apm.plugin.xxx.XxxInterceptor";
-                }
-
-                @Override
-                public boolean isOverrideArgs() {
-                    return false;
-                }
-            }
-        };
-    }
-}
-```
-
-**Interceptor class (implements `InstanceMethodsAroundInterceptorV2`):**
-```java
-public class XxxInterceptor implements InstanceMethodsAroundInterceptorV2 {
-    @Override
-    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] 
allArguments,
-                             Class<?>[] argumentsTypes, 
MethodInvocationContext context) {
-        // Create span and store in context for later use
-        AbstractSpan span = ContextManager.createLocalSpan("operationName");
-        context.setContext(span);  // Pass to afterMethod/handleMethodException
-    }
-
-    @Override
-    public Object afterMethod(EnhancedInstance objInst, Method method, 
Object[] allArguments,
-                              Class<?>[] argumentsTypes, Object ret, 
MethodInvocationContext context) {
-        // Retrieve span from context
-        AbstractSpan span = (AbstractSpan) context.getContext();
-        span.asyncFinish();
-        return ret;
-    }
-
-    @Override
-    public void handleMethodException(EnhancedInstance objInst, Method method, 
Object[] allArguments,
-                                      Class<?>[] argumentsTypes, Throwable t, 
MethodInvocationContext context) {
-        AbstractSpan span = (AbstractSpan) context.getContext();
-        span.log(t);
-    }
-}
-```
-
-**Key V2 classes:**
-- `ClassEnhancePluginDefineV2` - Base class for plugins with both instance and 
static methods
-- `ClassInstanceMethodsEnhancePluginDefineV2` - For instance methods only
-- `ClassStaticMethodsEnhancePluginDefineV2` - For static methods only
-- `InstanceMethodsAroundInterceptorV2` - Interceptor interface with 
`MethodInvocationContext`
-- `StaticMethodsAroundInterceptorV2` - Static method interceptor with context
-
-#### V1 API (Legacy)
-
-V1 uses `MethodInterceptResult` only in `beforeMethod` and has no shared 
context between phases. **Only use for maintaining existing legacy plugins.**
-
-**Key V1 classes (legacy):**
-- `ClassEnhancePluginDefine`
-- `ClassInstanceMethodsEnhancePluginDefine`
-- `ClassStaticMethodsEnhancePluginDefine`
-- `InstanceMethodsAroundInterceptor`
-- `StaticMethodsAroundInterceptor`
-
-### Plugin Development Rules
-
-#### Class Matching Restrictions
-
-**CRITICAL: Never use `.class` references in instrumentation definitions:**
-```java
-// WRONG - will break the agent if ThirdPartyClass doesn't exist
-takesArguments(ThirdPartyClass.class)
-byName(ThirdPartyClass.class.getName())
-
-// CORRECT - use string literals
-takesArguments("com.example.ThirdPartyClass")
-byName("com.example.ThirdPartyClass")
-```
-
-**ClassMatch options:**
-- `byName(String)`: Match by full class name (package + class name) - 
**preferred**
-- `byClassAnnotationMatch`: Match classes with specific annotations (does NOT 
support inherited annotations)
-- `byMethodAnnotationMatch`: Match classes with methods having specific 
annotations
-- `byHierarchyMatch`: Match by parent class/interface - **avoid unless 
necessary** (performance impact)
-
-#### Witness Classes/Methods
-
-Use witness classes/methods to activate plugins only for specific library 
versions:
-```java
-@Override
-protected String[] witnessClasses() {
-    return new String[] { "com.example.VersionSpecificClass" };
-}
-
-@Override
-protected List<WitnessMethod> witnessMethods() {
-    return Collections.singletonList(
-        new WitnessMethod("com.example.SomeClass", 
ElementMatchers.named("specificMethod"))
-    );
-}
-```
-
-#### Bootstrap Instrumentation
-
-For JDK core classes (rt.jar), override `isBootstrapInstrumentation()`:
-```java
-@Override
-public boolean isBootstrapInstrumentation() {
-    return true;
-}
-```
-**WARNING**: Use bootstrap instrumentation only where absolutely necessary.
-
-#### Plugin Configuration
-
-Use `@PluginConfig` annotation for custom plugin settings:
-```java
-public class MyPluginConfig {
-    public static class Plugin {
-        @PluginConfig(root = MyPluginConfig.class)
-        public static class MyPlugin {
-            public static boolean SOME_SETTING = false;
-        }
-    }
-}
-```
-Config key becomes: `plugin.myplugin.some_setting`
-
-#### Dependency Management
-
-**Plugin dependencies must use `provided` scope:**
-```xml
-<dependency>
-    <groupId>com.example</groupId>
-    <artifactId>target-library</artifactId>
-    <version>${version}</version>
-    <scope>provided</scope>
-</dependency>
-```
-
-**Agent core dependency policy:**
-- New dependencies in agent core are treated with extreme caution
-- Prefer using existing imported libraries already in the project
-- Prefer JDK standard libraries over third-party libraries
-- Plugins should rely on the target application's libraries (provided scope), 
not bundle them
-
-### Tracing Concepts
-
-#### Span Types
-- **EntrySpan**: Service provider/endpoint (HTTP server, MQ consumer)
-- **LocalSpan**: Internal method (no remote calls)
-- **ExitSpan**: Client call (HTTP client, DB access, MQ producer)
-
-#### SpanLayer (required for EntrySpan/ExitSpan)
-- `DB`: Database access
-- `RPC_FRAMEWORK`: RPC calls (not ordinary HTTP)
-- `HTTP`: HTTP calls
-- `MQ`: Message queue
-- `UNKNOWN`: Default
-
-#### Context Propagation
-- **ContextCarrier**: Cross-process propagation (serialize to 
headers/attachments)
-- **ContextSnapshot**: Cross-thread propagation (in-memory, no serialization)
-
-#### Required Span Attributes
-For EntrySpan and ExitSpan, always set:
-```java
-span.setComponent(ComponentsDefine.YOUR_COMPONENT);
-span.setLayer(SpanLayer.HTTP);  // or DB, MQ, RPC_FRAMEWORK
-```
-
-#### Special Tags for OAP Analysis
-| Tag | Purpose |
-|-----|---------|
-| `http.status_code` | HTTP response code (integer) |
-| `db.type` | Database type (e.g., "sql", "redis") |
-| `db.statement` | SQL/query statement (enables slow query analysis) |
-| `cache.type`, `cache.op`, `cache.cmd`, `cache.key` | Cache metrics |
-| `mq.queue`, `mq.topic` | MQ metrics |
-
-### Meter Plugin APIs
-
-For collecting numeric metrics (alternative to tracing):
-```java
-// Counter
-Counter counter = MeterFactory.counter("metric_name")
-    .tag("key", "value")
-    .mode(Counter.Mode.INCREMENT)
-    .build();
-counter.increment(1d);
-
-// Gauge
-Gauge gauge = MeterFactory.gauge("metric_name", () -> getValue())
-    .tag("key", "value")
-    .build();
-
-// Histogram
-Histogram histogram = MeterFactory.histogram("metric_name")
-    .steps(Arrays.asList(1, 5, 10))
-    .build();
-histogram.addValue(3);
-```
-
 ### Data Flow
 1. Agent attaches to JVM via `-javaagent` flag
 2. ByteBuddy transforms target classes at load time
@@ -411,6 +181,7 @@ Use Lombok annotations for boilerplate code:
 - 100+ test scenarios for plugin validation
 - Docker-based testing with actual frameworks
 - Pattern: `{framework}-{version}-scenario`
+- See `apm-sniffer/apm-sdk-plugin/CLAUDE.md` for full test framework 
documentation
 
 **End-to-End Tests** (`test/e2e/`)
 - Full system integration testing
@@ -427,165 +198,6 @@ Use Lombok annotations for boilerplate code:
 ./mvnw package -Dmaven.test.skip=true
 ```
 
-### Plugin Test Framework
-
-The plugin test framework verifies plugin functionality using Docker 
containers with real services and a mock OAP backend.
-
-#### Environment Requirements
-- MacOS/Linux
-- JDK 8+
-- Docker & Docker Compose
-
-#### Test Case Structure
-
-**JVM-container (preferred):**
-```
-{scenario}-scenario/
-├── bin/
-│   └── startup.sh              # JVM startup script (required)
-├── config/
-│   └── expectedData.yaml       # Expected trace/meter/log data
-├── src/main/java/...           # Test application code
-├── pom.xml
-├── configuration.yml           # Test case configuration
-└── support-version.list        # Supported versions (one per line)
-```
-
-**Tomcat-container:**
-```
-{scenario}-scenario/
-├── config/
-│   └── expectedData.yaml
-├── src/main/
-│   ├── java/...
-│   └── webapp/WEB-INF/web.xml
-├── pom.xml
-├── configuration.yml
-└── support-version.list
-```
-
-#### Key Configuration Files
-
-**configuration.yml:**
-```yaml
-type: jvm                                    # or tomcat
-entryService: http://localhost:8080/case     # Entry endpoint (GET)
-healthCheck: http://localhost:8080/health    # Health check endpoint (HEAD)
-startScript: ./bin/startup.sh                # JVM-container only
-runningMode: default                         # 
default|with_optional|with_bootstrap
-withPlugins: apm-spring-annotation-plugin-*.jar  # For optional/bootstrap modes
-environment:
-  - KEY=value
-dependencies:                                # External services 
(docker-compose style)
-  mysql:
-    image: mysql:8.0
-    hostname: mysql
-    environment:
-      - MYSQL_ROOT_PASSWORD=root
-```
-
-**support-version.list:**
-```
-# One version per line, use # for comments
-# Only include ONE version per minor version (not all patch versions)
-4.3.6
-4.4.1
-4.5.0
-```
-
-**expectedData.yaml:**
-
-Trace and meter expectations are typically in separate scenarios.
-
-*For tracing plugins:*
-```yaml
-segmentItems:
-  - serviceName: your-scenario
-    segmentSize: ge 1                        # Operators: eq, ge, gt, nq
-    segments:
-      - segmentId: not null
-        spans:
-          - operationName: /your/endpoint
-            parentSpanId: -1                 # -1 for root span
-            spanId: 0
-            spanLayer: Http                  # Http, DB, RPC_FRAMEWORK, MQ, 
CACHE, Unknown
-            spanType: Entry                  # Entry, Exit, Local
-            startTime: nq 0
-            endTime: nq 0
-            componentId: 1
-            isError: false
-            peer: ''                         # Empty string for Entry/Local, 
required for Exit
-            skipAnalysis: false
-            tags:
-              - {key: url, value: not null}
-              - {key: http.method, value: GET}
-              - {key: http.status_code, value: '200'}
-            logs: []
-            refs: []                         # SegmentRefs for 
cross-process/cross-thread
-```
-
-*For meter plugins:*
-```yaml
-meterItems:
-  - serviceName: your-scenario
-    meterSize: ge 1
-    meters:
-      - meterId:
-          name: test_counter
-          tags:
-            - {name: key1, value: value1}    # Note: uses 'name' not 'key'
-        singleValue: gt 0                    # For counter/gauge
-      - meterId:
-          name: test_histogram
-          tags:
-            - {name: key1, value: value1}
-        histogramBuckets:                    # For histogram
-          - 0.0
-          - 1.0
-          - 5.0
-          - 10.0
-```
-
-**startup.sh (JVM-container):**
-```bash
-#!/bin/bash
-home="$(cd "$(dirname $0)"; pwd)"
-# ${agent_opts} is REQUIRED - contains agent parameters
-java -jar ${agent_opts} ${home}/../libs/your-scenario.jar &
-```
-
-#### Running Plugin Tests Locally
-
-```bash
-# Run a specific scenario
-bash ./test/plugin/run.sh -f {scenario_name}
-
-# IMPORTANT: Rebuild agent if apm-sniffer code changed
-./mvnw clean package -DskipTests -pl apm-sniffer
-
-# Use generator to create new test case
-bash ./test/plugin/generator.sh
-```
-
-#### Adding Tests to CI
-
-Add scenario to the appropriate `.github/workflows/` file:
-- Use `python3 tools/select-group.py` to find the file with fewest cases
-- **JDK 8 tests**: `plugins-test.<n>.yaml`
-- **JDK 17 tests**: `plugins-jdk17-test.<n>.yaml`
-- **JDK 21 tests**: `plugins-jdk21-test.<n>.yaml`
-- **JDK 25 tests**: `plugins-jdk25-test.<n>.yaml`
-
-```yaml
-matrix:
-  case:
-    - your-scenario-scenario
-```
-
-#### Test Code Package Naming
-- Test code: `org.apache.skywalking.apm.testcase.*`
-- Code to be instrumented: `test.org.apache.skywalking.apm.testcase.*`
-
 ## Git Submodules
 
 The project uses submodules for protocol definitions:
@@ -616,11 +228,7 @@ git submodule init && git submodule update
 ## Common Development Tasks
 
 ### Adding a New Plugin
-1. Create directory in 
`apm-sniffer/apm-sdk-plugin/{framework}-{version}-plugin/`
-2. Implement instrumentation class using **V2 API** (e.g., extend 
`ClassInstanceMethodsEnhancePluginDefineV2`)
-3. Implement interceptor class using **V2 API** (e.g., implement 
`InstanceMethodsAroundInterceptorV2`)
-4. Register plugin in `skywalking-plugin.def` file
-5. Add test scenario in `test/plugin/scenarios/`
+See `apm-sniffer/apm-sdk-plugin/CLAUDE.md` for detailed guide.
 
 ### Adding an Optional Plugin
 1. Create in `apm-sniffer/optional-plugins/`
@@ -679,19 +287,10 @@ GitHub Actions workflows:
 
 ## Tips for AI Assistants
 
-1. **Use V2 instrumentation API**: Always use V2 classes 
(`ClassEnhancePluginDefineV2`, `InstanceMethodsAroundInterceptorV2`) for new 
plugins; V1 is legacy
-2. **NEVER use `.class` references**: In instrumentation definitions, always 
use string literals for class names (e.g., `byName("com.example.MyClass")` not 
`byName(MyClass.class.getName())`)
-3. **Always set component and layer**: For EntrySpan and ExitSpan, always call 
`setComponent()` and `setLayer()`
-4. **Prefer `byName` for class matching**: Avoid `byHierarchyMatch` unless 
necessary (causes performance issues)
-5. **Use witness classes for version-specific plugins**: Implement 
`witnessClasses()` or `witnessMethods()` to activate plugins only for specific 
library versions
-6. **Always check submodules**: Protocol changes may require submodule updates
-7. **Generate sources first**: Run `mvnw compile` before analyzing generated 
code
-8. **Respect checkstyle**: No System.out, no @author, no Chinese characters
-9. **Follow plugin patterns**: Use existing V2 plugins as templates
-10. **Use Lombok**: Prefer annotations over boilerplate code
-11. **Test both unit and E2E**: Different test patterns for different scopes
-12. **Plugin naming**: Follow `{framework}-{version}-plugin` convention
-13. **Shaded dependencies**: Core dependencies are shaded to avoid classpath 
conflicts
-14. **Java version compatibility**: Agent core must maintain Java 8 
compatibility, but individual plugins may target higher JDK versions (e.g., 
jdk-httpclient-plugin for JDK 11+, virtual-thread plugins for JDK 21+)
-15. **Bootstrap instrumentation**: Only use for JDK core classes, and only 
when absolutely necessary
-16. **Register plugins**: Always add plugin definition to 
`skywalking-plugin.def` file
+1. **Always check submodules**: Protocol changes may require submodule updates
+2. **Generate sources first**: Run `mvnw compile` before analyzing generated 
code
+3. **Respect checkstyle**: No System.out, no @author, no Chinese characters
+4. **Use Lombok**: Prefer annotations over boilerplate code
+5. **Test both unit and E2E**: Different test patterns for different scopes
+6. **Java version compatibility**: Agent core must maintain Java 8 
compatibility, but individual plugins may target higher JDK versions (e.g., 
jdk-httpclient-plugin for JDK 11+, virtual-thread plugins for JDK 21+)
+7. **For plugin development**: See `apm-sniffer/apm-sdk-plugin/CLAUDE.md` and 
`apm-sniffer/bootstrap-plugins/CLAUDE.md`
diff --git a/apm-sniffer/apm-sdk-plugin/CLAUDE.md 
b/apm-sniffer/apm-sdk-plugin/CLAUDE.md
new file mode 100644
index 0000000000..a0c73a2a04
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/CLAUDE.md
@@ -0,0 +1,401 @@
+# CLAUDE.md - SDK Plugin Development Guide
+
+This guide covers developing standard SDK plugins in 
`apm-sniffer/apm-sdk-plugin/`.
+
+## Plugin Instrumentation APIs (v1 vs v2)
+
+The agent provides two instrumentation APIs. **V2 is recommended** for all new 
plugins; v1 is legacy and should only be used for maintaining existing plugins.
+
+### V2 API (Recommended)
+
+V2 provides a `MethodInvocationContext` that is shared across all interception 
phases (`beforeMethod`, `afterMethod`, `handleMethodException`), allowing you 
to pass data (e.g., spans) between phases.
+
+**Instrumentation class (extends `ClassEnhancePluginDefineV2`):**
+```java
+public class XxxInstrumentation extends 
ClassInstanceMethodsEnhancePluginDefineV2 {
+    @Override
+    protected ClassMatch enhanceClass() {
+        return NameMatch.byName("target.class.Name");
+    }
+
+    @Override
+    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+        return new ConstructorInterceptPoint[] { ... };
+    }
+
+    @Override
+    public InstanceMethodsInterceptV2Point[] 
getInstanceMethodsInterceptV2Points() {
+        return new InstanceMethodsInterceptV2Point[] {
+            new InstanceMethodsInterceptV2Point() {
+                @Override
+                public ElementMatcher<MethodDescription> getMethodsMatcher() {
+                    return named("targetMethod");
+                }
+
+                @Override
+                public String getMethodsInterceptorV2() {
+                    return 
"org.apache.skywalking.apm.plugin.xxx.XxxInterceptor";
+                }
+
+                @Override
+                public boolean isOverrideArgs() {
+                    return false;
+                }
+            }
+        };
+    }
+}
+```
+
+**Interceptor class (implements `InstanceMethodsAroundInterceptorV2`):**
+```java
+public class XxxInterceptor implements InstanceMethodsAroundInterceptorV2 {
+    @Override
+    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] 
allArguments,
+                             Class<?>[] argumentsTypes, 
MethodInvocationContext context) {
+        AbstractSpan span = ContextManager.createLocalSpan("operationName");
+        context.setContext(span);  // Pass to afterMethod/handleMethodException
+    }
+
+    @Override
+    public Object afterMethod(EnhancedInstance objInst, Method method, 
Object[] allArguments,
+                              Class<?>[] argumentsTypes, Object ret, 
MethodInvocationContext context) {
+        AbstractSpan span = (AbstractSpan) context.getContext();
+        span.asyncFinish();
+        return ret;
+    }
+
+    @Override
+    public void handleMethodException(EnhancedInstance objInst, Method method, 
Object[] allArguments,
+                                      Class<?>[] argumentsTypes, Throwable t, 
MethodInvocationContext context) {
+        AbstractSpan span = (AbstractSpan) context.getContext();
+        span.log(t);
+    }
+}
+```
+
+**Key V2 classes:**
+- `ClassEnhancePluginDefineV2` - Base class for plugins with both instance and 
static methods
+- `ClassInstanceMethodsEnhancePluginDefineV2` - For instance methods only
+- `ClassStaticMethodsEnhancePluginDefineV2` - For static methods only
+- `InstanceMethodsAroundInterceptorV2` - Interceptor interface with 
`MethodInvocationContext`
+- `StaticMethodsAroundInterceptorV2` - Static method interceptor with context
+
+### V1 API (Legacy)
+
+V1 uses `MethodInterceptResult` only in `beforeMethod` and has no shared 
context between phases. **Only use for maintaining existing legacy plugins.**
+
+**Key V1 classes (legacy):**
+- `ClassEnhancePluginDefine`
+- `ClassInstanceMethodsEnhancePluginDefine`
+- `ClassStaticMethodsEnhancePluginDefine`
+- `InstanceMethodsAroundInterceptor`
+- `StaticMethodsAroundInterceptor`
+
+## Plugin Development Rules
+
+### Class Matching Restrictions
+
+**CRITICAL: Never use `.class` references in instrumentation definitions:**
+```java
+// WRONG - will break the agent if ThirdPartyClass doesn't exist
+takesArguments(ThirdPartyClass.class)
+byName(ThirdPartyClass.class.getName())
+
+// CORRECT - use string literals
+takesArguments("com.example.ThirdPartyClass")
+byName("com.example.ThirdPartyClass")
+```
+
+**ClassMatch options:**
+- `byName(String)`: Match by full class name (package + class name) - 
**preferred**
+- `byClassAnnotationMatch`: Match classes with specific annotations (does NOT 
support inherited annotations)
+- `byMethodAnnotationMatch`: Match classes with methods having specific 
annotations
+- `byHierarchyMatch`: Match by parent class/interface - **avoid unless 
necessary** (performance impact)
+
+### Witness Classes/Methods
+
+Use witness classes/methods to activate plugins only for specific library 
versions:
+```java
+@Override
+protected String[] witnessClasses() {
+    return new String[] { "com.example.VersionSpecificClass" };
+}
+
+@Override
+protected List<WitnessMethod> witnessMethods() {
+    return Collections.singletonList(
+        new WitnessMethod("com.example.SomeClass", 
ElementMatchers.named("specificMethod"))
+    );
+}
+```
+
+### Plugin Configuration
+
+Use `@PluginConfig` annotation for custom plugin settings:
+```java
+public class MyPluginConfig {
+    public static class Plugin {
+        @PluginConfig(root = MyPluginConfig.class)
+        public static class MyPlugin {
+            public static boolean SOME_SETTING = false;
+        }
+    }
+}
+```
+Config key becomes: `plugin.myplugin.some_setting`
+
+### Dependency Management
+
+**Plugin dependencies must use `provided` scope:**
+```xml
+<dependency>
+    <groupId>com.example</groupId>
+    <artifactId>target-library</artifactId>
+    <version>${version}</version>
+    <scope>provided</scope>
+</dependency>
+```
+
+**Agent core dependency policy:**
+- New dependencies in agent core are treated with extreme caution
+- Prefer using existing imported libraries already in the project
+- Prefer JDK standard libraries over third-party libraries
+- Plugins should rely on the target application's libraries (provided scope), 
not bundle them
+
+## Tracing Concepts
+
+### Span Types
+- **EntrySpan**: Service provider/endpoint (HTTP server, MQ consumer)
+- **LocalSpan**: Internal method (no remote calls)
+- **ExitSpan**: Client call (HTTP client, DB access, MQ producer)
+
+### SpanLayer (required for EntrySpan/ExitSpan)
+- `DB`: Database access
+- `RPC_FRAMEWORK`: RPC calls (not ordinary HTTP)
+- `HTTP`: HTTP calls
+- `MQ`: Message queue
+- `UNKNOWN`: Default
+
+### Context Propagation
+- **ContextCarrier**: Cross-process propagation (serialize to 
headers/attachments)
+- **ContextSnapshot**: Cross-thread propagation (in-memory, no serialization)
+
+### Required Span Attributes
+For EntrySpan and ExitSpan, always set:
+```java
+span.setComponent(ComponentsDefine.YOUR_COMPONENT);
+span.setLayer(SpanLayer.HTTP);  // or DB, MQ, RPC_FRAMEWORK
+```
+
+### Special Tags for OAP Analysis
+| Tag | Purpose |
+|-----|---------|
+| `http.status_code` | HTTP response code (integer) |
+| `db.type` | Database type (e.g., "sql", "redis") |
+| `db.statement` | SQL/query statement (enables slow query analysis) |
+| `cache.type`, `cache.op`, `cache.cmd`, `cache.key` | Cache metrics |
+| `mq.queue`, `mq.topic` | MQ metrics |
+
+## Meter Plugin APIs
+
+For collecting numeric metrics (alternative to tracing):
+```java
+// Counter
+Counter counter = MeterFactory.counter("metric_name")
+    .tag("key", "value")
+    .mode(Counter.Mode.INCREMENT)
+    .build();
+counter.increment(1d);
+
+// Gauge
+Gauge gauge = MeterFactory.gauge("metric_name", () -> getValue())
+    .tag("key", "value")
+    .build();
+
+// Histogram
+Histogram histogram = MeterFactory.histogram("metric_name")
+    .steps(Arrays.asList(1, 5, 10))
+    .build();
+histogram.addValue(3);
+```
+
+## Adding a New SDK Plugin
+
+1. Create directory: `apm-sniffer/apm-sdk-plugin/{framework}-{version}-plugin/`
+2. Implement instrumentation class using **V2 API** (extend 
`ClassInstanceMethodsEnhancePluginDefineV2`)
+3. Implement interceptor class using **V2 API** (implement 
`InstanceMethodsAroundInterceptorV2`)
+4. Register plugin in `skywalking-plugin.def` file
+5. Add test scenario in `test/plugin/scenarios/`
+
+## Plugin Test Framework
+
+The plugin test framework verifies plugin functionality using Docker 
containers with real services and a mock OAP backend.
+
+### Environment Requirements
+- MacOS/Linux
+- JDK 8+
+- Docker & Docker Compose
+
+### Test Case Structure
+
+**JVM-container (preferred):**
+```
+{scenario}-scenario/
+├── bin/
+│   └── startup.sh              # JVM startup script (required)
+├── config/
+│   └── expectedData.yaml       # Expected trace/meter/log data
+├── src/main/java/...           # Test application code
+├── pom.xml
+├── configuration.yml           # Test case configuration
+└── support-version.list        # Supported versions (one per line)
+```
+
+**Tomcat-container:**
+```
+{scenario}-scenario/
+├── config/
+│   └── expectedData.yaml
+├── src/main/
+│   ├── java/...
+│   └── webapp/WEB-INF/web.xml
+├── pom.xml
+├── configuration.yml
+└── support-version.list
+```
+
+### Key Configuration Files
+
+**configuration.yml:**
+```yaml
+type: jvm                                    # or tomcat
+entryService: http://localhost:8080/case     # Entry endpoint (GET)
+healthCheck: http://localhost:8080/health    # Health check endpoint (HEAD)
+startScript: ./bin/startup.sh                # JVM-container only
+runningMode: default                         # 
default|with_optional|with_bootstrap
+withPlugins: apm-spring-annotation-plugin-*.jar  # For optional/bootstrap modes
+environment:
+  - KEY=value
+dependencies:                                # External services 
(docker-compose style)
+  mysql:
+    image: mysql:8.0
+    hostname: mysql
+    environment:
+      - MYSQL_ROOT_PASSWORD=root
+```
+
+**support-version.list:**
+```
+# One version per line, use # for comments
+# Only include ONE version per minor version (not all patch versions)
+4.3.6
+4.4.1
+4.5.0
+```
+
+**expectedData.yaml:**
+
+Trace and meter expectations are typically in separate scenarios.
+
+*For tracing plugins:*
+```yaml
+segmentItems:
+  - serviceName: your-scenario
+    segmentSize: ge 1                        # Operators: eq, ge, gt, nq
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /your/endpoint
+            parentSpanId: -1                 # -1 for root span
+            spanId: 0
+            spanLayer: Http                  # Http, DB, RPC_FRAMEWORK, MQ, 
CACHE, Unknown
+            spanType: Entry                  # Entry, Exit, Local
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 1
+            isError: false
+            peer: ''                         # Empty string for Entry/Local, 
required for Exit
+            skipAnalysis: false
+            tags:
+              - {key: url, value: not null}
+              - {key: http.method, value: GET}
+              - {key: http.status_code, value: '200'}
+            logs: []
+            refs: []                         # SegmentRefs for 
cross-process/cross-thread
+```
+
+*For meter plugins:*
+```yaml
+meterItems:
+  - serviceName: your-scenario
+    meterSize: ge 1
+    meters:
+      - meterId:
+          name: test_counter
+          tags:
+            - {name: key1, value: value1}    # Note: uses 'name' not 'key'
+        singleValue: gt 0                    # For counter/gauge
+      - meterId:
+          name: test_histogram
+          tags:
+            - {name: key1, value: value1}
+        histogramBuckets:                    # For histogram
+          - 0.0
+          - 1.0
+          - 5.0
+          - 10.0
+```
+
+**startup.sh (JVM-container):**
+```bash
+#!/bin/bash
+home="$(cd "$(dirname $0)"; pwd)"
+# ${agent_opts} is REQUIRED - contains agent parameters
+java -jar ${agent_opts} ${home}/../libs/your-scenario.jar &
+```
+
+### Running Plugin Tests Locally
+
+```bash
+# Run a specific scenario
+bash ./test/plugin/run.sh -f {scenario_name}
+
+# IMPORTANT: Rebuild agent if apm-sniffer code changed
+./mvnw clean package -DskipTests -pl apm-sniffer
+
+# Use generator to create new test case
+bash ./test/plugin/generator.sh
+```
+
+### Adding Tests to CI
+
+Add scenario to the appropriate `.github/workflows/` file:
+- Use `python3 tools/select-group.py` to find the file with fewest cases
+- **JDK 8 tests**: `plugins-test.<n>.yaml`
+- **JDK 17 tests**: `plugins-jdk17-test.<n>.yaml`
+- **JDK 21 tests**: `plugins-jdk21-test.<n>.yaml`
+- **JDK 25 tests**: `plugins-jdk25-test.<n>.yaml`
+
+```yaml
+matrix:
+  case:
+    - your-scenario-scenario
+```
+
+### Test Code Package Naming
+- Test code: `org.apache.skywalking.apm.testcase.*`
+- Code to be instrumented: `test.org.apache.skywalking.apm.testcase.*`
+
+## Tips for AI Assistants
+
+1. **Use V2 instrumentation API**: Always use V2 classes for new plugins; V1 
is legacy
+2. **NEVER use `.class` references**: Always use string literals for class 
names
+3. **Always set component and layer**: For EntrySpan and ExitSpan, always call 
`setComponent()` and `setLayer()`
+4. **Prefer `byName` for class matching**: Avoid `byHierarchyMatch` unless 
necessary (performance impact)
+5. **Use witness classes for version-specific plugins**: Implement 
`witnessClasses()` or `witnessMethods()`
+6. **Follow plugin patterns**: Use existing V2 plugins as templates
+7. **Plugin naming**: Follow `{framework}-{version}-plugin` convention
+8. **Register plugins**: Always add plugin definition to 
`skywalking-plugin.def` file
+9. **Java version compatibility**: Agent core must maintain Java 8 
compatibility, but individual plugins may target higher JDK versions
+10. **Shaded dependencies**: Core dependencies are shaded to avoid classpath 
conflicts
diff --git a/apm-sniffer/bootstrap-plugins/CLAUDE.md 
b/apm-sniffer/bootstrap-plugins/CLAUDE.md
new file mode 100644
index 0000000000..51b4db99e7
--- /dev/null
+++ b/apm-sniffer/bootstrap-plugins/CLAUDE.md
@@ -0,0 +1,53 @@
+# CLAUDE.md - Bootstrap Plugin Development Guide
+
+This guide covers developing bootstrap-level plugins in 
`apm-sniffer/bootstrap-plugins/`.
+
+For general plugin development concepts (V2 API, tracing, class matching, 
testing), see `apm-sniffer/apm-sdk-plugin/CLAUDE.md`.
+
+## What Are Bootstrap Plugins?
+
+Bootstrap plugins instrument JDK core classes (rt.jar / java.base module) at 
the JVM bootstrap phase. They are loaded before application classes and can 
intercept fundamental JDK APIs.
+
+**Examples:**
+- `jdk-threading-plugin` - Thread pool context propagation
+- `jdk-http-plugin` - `HttpURLConnection` instrumentation
+- `jdk-httpclient-plugin` - JDK 11+ `HttpClient` instrumentation
+- `jdk-virtual-thread-executor-plugin` - JDK 21+ virtual thread support
+- `jdk-forkjoinpool-plugin` - `ForkJoinPool` instrumentation
+
+## Key Difference from SDK Plugins
+
+Bootstrap plugins **must** override `isBootstrapInstrumentation()`:
+```java
+@Override
+public boolean isBootstrapInstrumentation() {
+    return true;
+}
+```
+
+**WARNING**: Use bootstrap instrumentation only where absolutely necessary. It 
affects JDK core classes and has broader impact than SDK plugins.
+
+## Development Rules
+
+All general plugin development rules apply (see `apm-sdk-plugin/CLAUDE.md`), 
plus:
+
+1. **Use V2 API** for new bootstrap plugins, same as SDK plugins
+2. **Minimal scope**: Only intercept what is strictly necessary in JDK classes
+3. **Performance critical**: Bootstrap plugins run on core JDK paths - keep 
interceptor logic lightweight
+4. **Class loading awareness**: JDK core classes are loaded by the bootstrap 
classloader; be careful with class references that might not be visible at 
bootstrap level
+
+## Testing Bootstrap Plugins
+
+Bootstrap plugin test scenarios use `runningMode: with_bootstrap` in 
`configuration.yml`:
+```yaml
+type: jvm
+entryService: http://localhost:8080/case
+healthCheck: http://localhost:8080/health
+startScript: ./bin/startup.sh
+runningMode: with_bootstrap
+withPlugins: jdk-threading-plugin-*.jar
+```
+
+This tells the test framework to load the plugin at the bootstrap level 
instead of the normal plugin directory.
+
+See `apm-sdk-plugin/CLAUDE.md` for full test framework documentation.


Reply via email to