Skip to content

Commit c7901a5

Browse files
mccullsPerfectSlayerdevflow.devflow-routing-intake
authored
Trim AGENTS.md of duplicate content (#12003)
Trim AGENTS.md of duplicate content feat(ai): Restore techdebt review Co-authored-by: PerfectSlayer <bruce.bujon@datadoghq.com> Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent c341840 commit c7901a5

5 files changed

Lines changed: 108 additions & 23 deletions

File tree

.agents/skills/techdebt/SKILL.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
---
22
name: techdebt
3-
description: Analyze branch changes for technical debt, code duplication, and unnecessary complexity
3+
description: >-
4+
Review a code diff / branch / PR for technical debt — code duplication,
5+
unnecessary complexity / over-engineering, and redundant or dead code. Use
6+
whenever the user wants a tech-debt, cleanup, or refactor review, asks to check
7+
a branch or PR for duplication / complexity / dead code before opening a PR, or
8+
mentions "techdebt". Refactor-only: it reports issues and offers
9+
behavior-preserving fixes; it never changes behavior.
410
user-invocable: true
511
context: fork
612
allowed-tools:

.claude/CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
## Claude Code workflow
44

5-
- Before creating a pull request, run `/techdebt` to check for technical debt, code duplication, and unnecessary complexity in the branch changes.
5+
- Before creating a pull request, run the [Review Guidelines](../AGENTS.md#review-guidelines) checks over the branch changes.
66
- After running `/migrate-groovy-to-java`, run `/review-groovy-migration` on the migrated files before opening a PR.
77
- Always write new unit tests using JUnit 5 and Java. If the target test file is `.groovy`, run the `/migrate-groovy-to-java` skill on it first.

AGENTS.md

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ docs/ Developer documentation (see below)
3838
| Testing guide (6 test types) | [docs/how_to_test.md](docs/how_to_test.md) |
3939
| Working with Gradle | [docs/how_to_work_with_gradle.md](docs/how_to_work_with_gradle.md) |
4040
| Bootstrap/premain constraints | [docs/bootstrap_design_guidelines.md](docs/bootstrap_design_guidelines.md) |
41+
| Instrumentation/advice constraints | [docs/instrumentation_design_guidelines.md](docs/instrumentation_design_guidelines.md) |
4142
| CI/CD workflows | [.github/workflows/README.md](.github/workflows/README.md) |
4243

4344
**When working on a topic above, read the linked file first** — they are the source of truth maintained by humans.
@@ -56,14 +57,12 @@ docs/ Developer documentation (see below)
5657
## Code conventions
5758

5859
- **Formatting**: google-java-format enforced via Spotless. Run `./gradlew spotlessApply` before committing.
59-
- **Static imports**: Prefer static imports over class-qualified calls for call-style helpers — JUnit `Assertions`, Mockito (`mock`, `when`, `verify`, `anyString`, `RETURNS_DEFAULTS`, ...), Hamcrest/AssertJ matchers, internal test DSLs, and similar. Same goes for production code: `Collections.emptyList()` is fine, but if you find yourself repeatedly writing `Foo.bar(...)` where `Foo` adds no information at the call site, static-import `bar`. Wildcard `import static x.*` is disallowed (enforced by IDE config in CONTRIBUTING.md).
60+
- **Static imports**: Prefer static imports over class-qualified calls for call-style helpers, in both test (Assertions.assertEquals, Mockito.mock) and production code (Collections.emptyList). Wildcard imports disallowed — see CONTRIBUTING.md.
6061
- **Instrumentation layout**: `dd-java-agent/instrumentation/{framework}/{framework}-{minVersion}/`
6162
- **Instrumentation pattern**: Type matching → Method matching → Advice class (bytecode advice, not AOP)
6263
- **Test frameworks**: Always use JUnit 5 for unit tests. Only use Groovy / Spock tests for instrumentation and smoke tests.
6364
- **Forked tests**: Use `ForkedTest` suffix when tests need a separate JVM
6465
- **Flaky tests**: Annotate with `@Flaky` — they are skipped in CI by default
65-
- **Instrumentation one-shot methods**: Never extract the return values of `triggerClasses()`, `contextStore()`, `classLoaderMatcher()`, or `methodAdvice()` into static constants. These are called once by the framework — extracting to a constant adds constant-pool bloat with no benefit.
66-
- **Scope lifecycle order**: Keep the scope open until all work that needs the current active span is done — decorator calls, async callback registration, etc. Required order: decorator calls and callback registration, then `scope.close()`, then `span.finish()`.
6766

6867
## PR conventions
6968

@@ -74,23 +73,10 @@ docs/ Developer documentation (see below)
7473

7574
## Review Guidelines
7675

77-
Before marking a PR ready, run the applicable reviews below over the branch changes (the diff since the merge-base with `master`). These are **advisory, precision-first** checks — they surface high-severity issues early and are not merge gates. The linked guidelines are the tool-agnostic source of truth.
76+
- **Technical debt**: run `/techdebt` over branch changes before marking a PR ready to catch code duplication, unnecessary complexity, and dead code (refactor-only, never changes behavior) — see [.agents/skills/techdebt/SKILL.md](.agents/skills/techdebt/SKILL.md).
77+
- **Performance**: run `/perf-review` over branch changes before marking a PR ready (advisory, not a merge gate) — see [.agents/skills/perf-review/SKILL.md](.agents/skills/perf-review/SKILL.md).
7878

79-
### Performance Review
79+
## Critical constraints
8080

81-
Follow the performance-review guidelines in [.agents/skills/perf-review/SKILL.md](.agents/skills/perf-review/SKILL.md) — the do-no-harm / assume-hot posture, the hot-path checks (universal + Java J1–J11 + ByteBuddy-Advice idioms), and the confidence/severity model, with the detailed rubric in its `references/`. Scope: hot-path allocation, unbounded memory, repeated work, escaping objects, native-boundary crossings, and JVM-specific pitfalls.
82-
83-
## Bootstrap constraints (critical)
84-
85-
Code running in the agent's `premain` phase must **not** use:
86-
- `java.util.logging.*` — locks in log manager before app configures it
87-
- `java.nio.file.*` — triggers premature provider initialization
88-
- `javax.management.*` — causes class loading issues
89-
90-
See [docs/bootstrap_design_guidelines.md](docs/bootstrap_design_guidelines.md) for details and alternatives.
91-
92-
## Advice constraints (critical)
93-
94-
Advice methods (OnMethodEnter/OnMethodExit) are inlined into the instrumented class's bytecode.
95-
Calling a static interface method there, such as Context.root() or Context.current(), can cause
96-
a VerifyError at runtime — use Java8BytecodeBridge (rootContext, currentContext, etc.) instead.
81+
- **Bootstrap**: never use `java.util.logging.*`, `java.nio.file.*`, or `javax.management.*` in `premain` code — see [docs/bootstrap_design_guidelines.md](docs/bootstrap_design_guidelines.md).
82+
- **Advice**: never call a static interface method from advice — can cause a `VerifyError` — see [docs/instrumentation_design_guidelines.md](docs/instrumentation_design_guidelines.md).

CONTRIBUTING.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ For IntelliJ IDEA, we suggest the following settings and plugin.
6363
* To run test in a specific JDK use the `testJvm` property, e.g. `-PtestJvm=11`
6464
* Install the [Google Java Format](https://plugins.jetbrains.com/plugin/8527-google-java-format) plugin
6565

66+
### Static imports
67+
68+
Prefer static imports over class-qualified calls for call-style helpers — JUnit `Assertions`, Mockito (`mock`,
69+
`when`, `verify`, `anyString`, `RETURNS_DEFAULTS`, ...), Hamcrest/AssertJ matchers, internal test DSLs, and similar.
70+
71+
Same goes for production code: `Collections.emptyList()` is fine, but if you find yourself repeatedly writing
72+
`Foo.bar(...)` where `Foo` adds no information at the call site, static-import `bar`.
73+
74+
Wildcard imports (`import x.*` or `import static x.*`) are disallowed — see the IntelliJ IDEA settings above.
75+
6676
### Troubleshooting
6777

6878
* Gradle fails with a "too many open files" error.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Instrumentation and Advice Design Guidelines
2+
3+
This document outlines design constraints and best practices when writing or refactoring instrumentation and advice code.
4+
Following these guidelines will help avoid constant-pool bloat and subtle span/scope lifecycle bugs.
5+
6+
## Background
7+
8+
Instrumentation classes wire up type/method matchers and advice once, when the framework loads them. Advice methods
9+
are inlined into the instrumented application's bytecode and run on every invocation of the matched method, so their
10+
ordering directly affects when spans are visible to nested work.
11+
12+
## Constraints to Follow
13+
14+
### 1. Instrumentation one-shot methods
15+
16+
**Why to avoid extracting to constants:**
17+
18+
- `triggerClasses()`, `contextStore()`, `classLoaderMatcher()`, and `methodAdvice()` are called exactly once by the
19+
framework when the instrumentation is registered
20+
- Extracting their return values into static constants adds constant-pool bloat with no runtime benefit
21+
22+
**What to do instead:**
23+
24+
Return the values directly from the method; don't cache them in a field.
25+
26+
```java
27+
// BAD - unnecessary static constant
28+
private static final ElementMatcher.Junction<ClassLoader> CL_MATCHER = hasClassesNamed("foo.Bar");
29+
30+
@Override
31+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
32+
return CL_MATCHER;
33+
}
34+
35+
// GOOD - construct directly in the one-shot method
36+
@Override
37+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
38+
return hasClassesNamed("foo.Bar");
39+
}
40+
```
41+
42+
### 2. Scope lifecycle order
43+
44+
**Why to avoid closing the scope early:**
45+
46+
- Keeping the scope open until all work needing the current active span is done ensures decorator calls and async
47+
callback registration see the span as active
48+
- Closing the scope before that work runs can make nested code lose track of the current span
49+
50+
**What to do instead:**
51+
52+
Required order: decorator calls and callback registration, then `scope.close()`, then `span.finish()`.
53+
54+
```java
55+
// GOOD
56+
DECORATE.onOperation(span, request);
57+
registerCallback(span);
58+
scope.close();
59+
span.finish();
60+
```
61+
62+
### 3. Static interface methods in advice
63+
64+
**Why to avoid calling them directly:**
65+
66+
- Advice methods (`@Advice.OnMethodEnter`/`@Advice.OnMethodExit`) are inlined into the instrumented class's bytecode
67+
- Calling a static interface method there, such as `Context.current()` or `Context.root()`, can cause a
68+
`VerifyError` at runtime
69+
70+
**What to use instead:**
71+
72+
Use `Java8BytecodeBridge` (`currentContext()`, `rootContext()`, etc.) in place of the static interface method,
73+
static-imported for readability.
74+
75+
```java
76+
import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.currentContext;
77+
78+
// BAD - static interface method call, can cause VerifyError when inlined
79+
Context ctx = Context.current();
80+
81+
// GOOD - use the bytecode bridge, static-imported
82+
Context ctx = currentContext();
83+
```

0 commit comments

Comments
 (0)