Bytesight is a JVM reverse-engineering desktop application. It attaches a Java agent to a running JVM process, inspects loaded classes, decompiles bytecode (Vineflower), and traces method calls in real time via gRPC streaming.
| Module | Language | Purpose |
|---|---|---|
composeApp |
Kotlin | Desktop UI — Compose Multiplatform, MVVM + Koin |
agent |
Java | Java agent — ByteBuddy instrumentation, gRPC server |
protocol |
Kotlin | gRPC/Protobuf definitions and generated stubs |
core |
Kotlin | Decompiler wrapper (Vineflower) |
sample |
Java | Sample target app for testing/instrumentation |
All modules target JVM 17. Build system is Gradle 8.14.3 with Kotlin DSL.
# Build everything (includes agent fat JAR)
./gradlew build
# Run the desktop application
./gradlew :composeApp:run
# Build the agent fat JAR only
./gradlew :agent:agentJar
# Build the sample app (normal / obfuscated)
./gradlew :sample:jar
./gradlew :sample:obfuscateOn Windows use .\gradlew.bat instead of ./gradlew.
# Run ALL tests across every module
./gradlew test
# Run tests for a single module
./gradlew :agent:test
./gradlew :composeApp:test
./gradlew :core:test
# Run a single test class
./gradlew :agent:test --tests "com.bugdigger.agent.hook.TraceEventBufferTest"
./gradlew :composeApp:jvmTest --tests "com.bugdigger.bytesight.ui.trace.TraceViewModelTest"
./gradlew :core:test --tests "com.bugdigger.core.decompiler.VineflowerDecompilerTest"
# Run a single test method (Kotlin backtick names need quoting)
./gradlew :composeApp:jvmTest --tests "com.bugdigger.bytesight.ui.trace.TraceViewModelTest.Initial State.should have empty initial state"
# Run a single test method (Java camelCase names)
./gradlew :agent:test --tests "com.bugdigger.agent.hook.TraceEventBufferTest.addListener_receivesEvents"Note: composeApp uses the jvmTest task (Kotlin Multiplatform), while agent, core,
and sample use the standard test task.
Integration tests in composeApp are gated by @EnabledIf("isIntegrationTestEnabled") and
require the agent JAR + sample JAR to be pre-built (both are wired as task dependencies).
There is no dedicated linter or formatter configured (no ESLint, Prettier, Checkstyle,
ktlint, or Detekt). The project follows Kotlin official code style
(kotlin.code.style=official in gradle.properties). Use your IDE's default Kotlin/Java
formatter.
| Element | Convention | Example |
|---|---|---|
| Packages | lowercase dotted | com.bugdigger.agent.hook |
| Classes / Interfaces | PascalCase | TraceViewModel, AgentClient |
| Functions / methods | camelCase | refreshClasses, addHook |
| Constants | UPPER_SNAKE_CASE | DEFAULT_PORT, AGENT_VERSION |
| Variables / props | camelCase | connectionKey, uiState |
| Enum values | UPPER_SNAKE_CASE | LOG_ENTRY_EXIT, ENTRY |
| Files | PascalCase (match class) | TraceViewModel.kt, HookManager.java |
| Test classes | ClassNameTest suffix |
TraceViewModelTest, HookResultTest |
| Kotlin test methods | Backtick descriptive | `should have empty initial state` |
| Java test methods | snake_case descriptive | success_createsSuccessfulResult |
- Prefer named imports; avoid wildcards except for Compose UI packages
(
androidx.compose.runtime.*,androidx.compose.foundation.layout.*). - Use static imports for JUnit assertions in Java:
import static org.junit.jupiter.api.Assertions.*. - Order: stdlib/JDK → third-party libs → project modules. No path aliases.
- Kotlin: Use
data classfor state/DTOs,sealed classfor discriminated unions,interfacefor abstractions,enum classfor fixed sets. - Explicit types on public API (function signatures, public properties); inferred types for local variables.
- Use
StateFlow<T>/MutableStateFlow<T>for reactive UI state. - Nullable types used judiciously (
String?,ClassInfo?) — avoid unnecessary nullability. - Java: Standard classes for models; prefer inner
static classfor implementation details. UseConcurrentHashMap,CopyOnWriteArrayList,AtomicLongfor thread safety.
- Kotlin: Use
Result<T>withrunCatching { }+.onSuccess/.onFailurechaining. Usesealed classresults for domain-specific outcomes (e.g.,DecompilationResult.Success,DecompilationResult.Failure). - Java: Try-catch with SLF4J logging (
logger.error(...)) and wrapping inRuntimeExceptionfor fatal errors. Custom Result types via static factory methods (HookResult.success(),HookResult.failure()). - UI errors: Store in
error: String?field of UI state data classes, display viaErrorBannercomposable, clear viaclearError().
- MVVM for Compose UI:
ViewModelexposesStateFlow<UiState>consumed by@Composablescreens. - Dependency injection: Koin —
singleOf,factoryOf,single<Interface> { Implementation() }declared inAppModule.kt. - Service layer:
AgentClient(gRPC client),AttachService(JVM Attach API). - Agent: Java Instrumentation API →
ClassFileTransformer+ ByteBuddyAdvicefor method interception. Embedded gRPC server for communication with the desktop app.
- Use KDoc (
/** ... */) on public classes, interfaces, and methods. - Use Javadoc with
@param,@return,@throwstags in Java code. - Inline comments explain why, not what.
- Use section dividers for long files:
// ========== Helper Methods ==========.
- JUnit 5 across all modules with
useJUnitPlatform(). - Kotlin tests: MockK (
mockk(relaxed = true)),kotlinx-coroutines-test(runBlocking). - Java tests: JUnit 5 assertions,
CountDownLatchfor async verification. - Group related tests with
@Nestedinner classes and@DisplayName. - Follow Arrange-Act-Assert pattern consistently.
- Test file location mirrors source:
src/test/java/...(agent, core) orsrc/jvmTest/kotlin/...(composeApp).
| Dependency | Purpose |
|---|---|
| Jetpack Compose Multiplatform | Desktop UI framework |
| Koin | Dependency injection |
| gRPC + Protobuf | Agent ↔ UI communication |
| ByteBuddy | Runtime bytecode instrumentation |
| Vineflower | Bytecode decompilation |
| RSyntaxTextArea | Code editor component in UI |
| SLF4J + Logback | Logging |
| JUnit 5 + MockK | Testing |
Protocol definitions live in protocol/src/main/proto/bytesight.proto. Code is generated
by the Protobuf Gradle plugin into protocol/build/generated/source/proto/. Never edit
generated files — modify the .proto file and rebuild.
- Target JVM 17 — do not use APIs from newer JDK versions.
- Keep modules decoupled: UI depends on
protocolandcore, never onagent. - The
agentmodule is pure Java (no Kotlin) to minimize the fat JAR size. - Prefer extension functions for display helpers (e.g.,
MethodTraceEvent.toDisplay()). - Use
privatefor composables that are internal to a screen file. - Visibility: omit
public(Kotlin default); useinternalorprivatedeliberately.