For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Extend Bytesight from a class browser / method tracer into a full JVM reverse-engineering workbench with 7 new capabilities, phased from easiest to hardest.
Architecture: Each feature adds a new screen (or panel) following the existing MVVM +
Koin pattern. New analysis modules live in core (pure Kotlin, no UI dependency). Heavy
bytecode work uses ASM directly; ByteBuddy stays in agent for runtime instrumentation
only. Graph rendering uses Compose Canvas.
Tech Stack: Kotlin (Compose Multiplatform), Java (agent), ASM 9.x (bytecode analysis), Koin (DI), gRPC/Protobuf (agent communication), Compose Canvas (graph drawing).
| Phase | Feature | Effort | New Module? | Depends On |
|---|---|---|---|---|
| 1 | String / Constant Extraction | Easy | No | — |
| 1 | Class Hierarchy Explorer | Easy | No | — |
| 2 | Bytecode-Level Inspector | Medium | No | — |
| 2 | JAR Deobfuscation | Medium | Yes (deobfuscator) |
— |
| 3 | Bytecode Diff View | Medium | No | Phase 2 (deobfuscation provides before/after) |
| 3 | Method Call Graph | Med-Hard | No | Phase 2 (bytecode parsing patterns) |
| 4 | Control Flow Graph Viewer | Hard | No | Phase 3 (graph rendering patterns) |
These use data already available via the existing GetClassBytecode and GetLoadedClasses
RPCs. No agent changes required.
What: Scan bytecode of loaded classes for hardcoded strings, numeric constants, class references, URLs, file paths, and other embedded literals. Display them in a searchable, filterable UI panel.
Why: One of the most common RE tasks. Immediately useful for finding API endpoints, encryption keys, debug messages, hidden features.
Architecture:
- New Kotlin class
ConstantExtractorincoremodule — parses bytecode using ASM'sClassReader+ClassVisitorto collectLDC,LDC_W,LDC2_Winstructions and constant pool entries. - New
StringsScreen+StringsViewModelincomposeApp. - Reuses existing
agentClient.getClassBytecode()to fetch raw bytes per class.
// core/src/main/kotlin/com/bugdigger/core/analysis/ConstantExtractor.kt
data class ExtractedConstant(
val value: Any, // String, Int, Long, Float, Double, Type
val type: ConstantType, // STRING, INTEGER, LONG, FLOAT, DOUBLE, CLASS_REF, METHOD_HANDLE
val className: String, // Class where found
val methodName: String?, // Method where found (null if class-level)
val location: String, // Human-readable location "MyClass.doStuff()"
)
enum class ConstantType { STRING, INTEGER, LONG, FLOAT, DOUBLE, CLASS_REF, METHOD_HANDLE }
// Patterns for highlighting interesting strings
enum class StringPattern(val regex: Regex) {
URL(Regex("https?://.*")),
FILE_PATH(Regex("[/\\\\].*\\.[a-zA-Z]{2,4}")),
IP_ADDRESS(Regex("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")),
CRYPTO_KEY(Regex("[A-Fa-f0-9]{32,}")),
// ... more patterns
}Task 1.1: Create ConstantExtractor in core module
Files:
- Create:
core/src/main/kotlin/com/bugdigger/core/analysis/ConstantExtractor.kt - Create:
core/src/test/kotlin/com/bugdigger/core/analysis/ConstantExtractorTest.kt
Steps:
- Add ASM dependency to
core/build.gradle.kts:implementation("org.ow2.asm:asm:9.7.1") - Write test: feed known bytecode (compile a small class with strings/numbers), assert extracted constants match expected values.
- Implement
ConstantExtractorusingClassReader+ customClassVisitor/MethodVisitorthat overridesvisitLdcInsn()and collects constants. - Run:
.\gradlew.bat :core:test --tests "*.ConstantExtractorTest"
Task 1.2: Create StringsScreen + StringsViewModel
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/strings/StringsViewModel.kt - Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/strings/StringsScreen.kt - Modify:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/navigation/Navigation.kt— addSTRINGStoScreenenum - Modify:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/di/AppModule.kt— registerStringsViewModel
UI layout:
- Top bar: "Extract" button (scans all loaded classes), progress indicator, search bar.
- Filter chips: by type (Strings / Numbers / URLs / Class refs), by pattern match.
- Main table: columns for Value, Type, Pattern (URL/IP/etc.), Class, Method.
- Click row → navigate to ClassBrowser with that class selected.
- "Scan Selection" button to scan only filtered/selected classes.
State:
data class StringsUiState(
val constants: List<ExtractedConstant> = emptyList(),
val filteredConstants: List<ExtractedConstant> = emptyList(),
val searchQuery: String = "",
val typeFilter: Set<ConstantType> = ConstantType.entries.toSet(),
val patternFilter: Set<StringPattern>? = null, // null = all
val isExtracting: Boolean = false,
val progress: Float = 0f, // 0..1
val error: String? = null,
)Task 1.3: Write ViewModel tests
File: composeApp/src/jvmTest/kotlin/com/bugdigger/bytesight/ui/strings/StringsViewModelTest.kt
Test: initial state, search filtering, type filtering, error handling, progress updates.
Run: .\gradlew.bat :composeApp:jvmTest --tests "*.StringsViewModelTest"
What: Interactive tree/graph view showing inheritance relationships (extends/implements) across all loaded classes. Click a class to see its full hierarchy chain.
Why: Essential for understanding OOP structure of an application, finding implementations of interfaces, spotting proxy/wrapper patterns.
Architecture:
ClassInfoalready containssuperclass: stringandinterfaces: repeated stringin the proto — no agent changes needed.- New
HierarchyScreen+HierarchyViewModelincomposeApp. - Build a tree data structure from the flat class list on the client side.
- Render as a collapsible tree (like a file explorer) + optional graph view.
data class HierarchyNode(
val classInfo: ClassInfo?, // null if class not loaded (e.g. java.lang.Object)
val className: String,
val children: List<HierarchyNode>, // subclasses / implementors
val isInterface: Boolean,
)
data class HierarchyUiState(
val roots: List<HierarchyNode> = emptyList(), // Top-level nodes (no parent loaded)
val selectedClass: String? = null,
val selectedAncestors: List<String> = emptyList(), // Upward chain to Object
val searchQuery: String = "",
val showInterfaces: Boolean = true,
val showClasses: Boolean = true,
val isLoading: Boolean = false,
val error: String? = null,
)Task 2.1: Build hierarchy tree from ClassInfo list
This is a pure function — given List<ClassInfo>, return List<HierarchyNode>.
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/hierarchy/HierarchyBuilder.kt - Create:
composeApp/src/jvmTest/kotlin/com/bugdigger/bytesight/ui/hierarchy/HierarchyBuilderTest.kt
Algorithm:
- Index all classes by full name into a map.
- For each class, register it as a child of its
superclassand each of itsinterfaces. - Roots = classes whose superclass is not in the loaded class set (typically
java.lang.Objectsubclasses). - Build tree recursively.
Test: create mock ClassInfo objects with known hierarchy, assert tree structure is correct.
Run: .\gradlew.bat :composeApp:jvmTest --tests "*.HierarchyBuilderTest"
Task 2.2: Create HierarchyScreen + HierarchyViewModel
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/hierarchy/HierarchyViewModel.kt - Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/hierarchy/HierarchyScreen.kt - Modify:
Navigation.kt— addHIERARCHYtoScreenenum - Modify:
Sidebar.kt— add sidebar entry (gated byisConnected) - Modify:
AppModule.kt— registerHierarchyViewModel
UI layout:
- Left panel: collapsible tree view using
LazyColumnwith indentation. Icons: 📦 class, 🔷 interface, 📋 enum. Expand/collapse arrows. - Right panel: selected class detail — full ancestor chain (upward to Object), list of direct subclasses, fields/methods summary.
- Search bar: filter the tree to show only branches containing the search term.
- Toggle: "Show interfaces" / "Show abstract classes" checkboxes.
Task 2.3: Write ViewModel tests
File: composeApp/src/jvmTest/kotlin/com/bugdigger/bytesight/ui/hierarchy/HierarchyViewModelTest.kt
Run: .\gradlew.bat :composeApp:jvmTest --tests "*.HierarchyViewModelTest"
These features add deeper bytecode analysis capabilities and a new module.
What: A raw bytecode disassembly view showing JVM instructions alongside the decompiled
Java source. Think of javap -c output but interactive — click an instruction to highlight
the corresponding decompiled line, and vice versa.
Why: Decompiled source can be misleading (decompiler guesses, lost information). The raw bytecode is the ground truth. Needed for understanding obfuscated code, compiler quirks, and verifying decompiler output.
Architecture:
- New
BytecodeDisassemblerincoremodule — uses ASM to produce a structured list of instructions per method (not just a text dump). - New
InspectorScreenwith split-pane layout: bytecode on left, decompiled source on right. - Reuses existing
CodeViewerfor the decompiled side.
data class DisassembledClass(
val className: String,
val majorVersion: Int,
val minorVersion: Int,
val accessFlags: Int,
val constantPool: List<ConstantPoolEntry>,
val methods: List<DisassembledMethod>,
val fields: List<FieldSummary>,
)
data class DisassembledMethod(
val name: String,
val descriptor: String,
val accessFlags: Int,
val instructions: List<Instruction>,
val maxStack: Int,
val maxLocals: Int,
val tryCatchBlocks: List<TryCatchBlock>,
val localVariables: List<LocalVariable>,
)
data class Instruction(
val offset: Int, // bytecode offset
val opcode: Int, // JVM opcode
val mnemonic: String, // "INVOKEVIRTUAL", "ALOAD", etc.
val operands: String, // Human-readable operand string
val lineNumber: Int?, // Source line if available (from LineNumberTable)
)Task 3.1: Create BytecodeDisassembler in core
Files:
- Create:
core/src/main/kotlin/com/bugdigger/core/analysis/BytecodeDisassembler.kt - Create:
core/src/test/kotlin/com/bugdigger/core/analysis/BytecodeDisassemblerTest.kt
Uses ASM ClassReader with a custom MethodVisitor that records each instruction.
ASM dependency should already be added in Phase 1. If not, add it here.
Test: compile a known Java class, disassemble it, assert instruction sequences.
Run: .\gradlew.bat :core:test --tests "*.BytecodeDisassemblerTest"
Task 3.2: Create InspectorScreen + InspectorViewModel
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/inspector/InspectorViewModel.kt - Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/inspector/InspectorScreen.kt - Modify:
Navigation.kt,Sidebar.kt,AppModule.kt
UI layout:
- Top bar: class selector dropdown (from loaded classes) + method selector dropdown.
- Split pane: left = bytecode instructions (
LazyColumnofInstructionrows with offset, opcode, operands), right = decompiled source inCodeViewer. - Instruction detail panel at bottom: when an instruction is selected, show operand details, stack effect (+N / -N), relevant constant pool entry.
- Color coding: control flow (red), method calls (blue), field access (green), load/store (gray).
What: Load a ProGuard mapping file and apply it to rename obfuscated classes, methods, and fields — either on loaded classes (live) or on a JAR file (offline). Produces deobfuscated bytecode that can then be decompiled.
Why: Core RE capability. We already generate obfuscated JARs in the sample module
with mapping.txt — built-in test case.
Architecture:
- New Gradle module:
deobfuscator(pure Kotlin + ASM, no UI dependency). MappingParser— reads ProGuard.txtformat into a structured mapping model.ClassDeobfuscator— uses ASMClassRemapper+ customRemapperto rewrite all references.- Integration in
composeApp: new "Deobfuscate" action in ClassBrowser that applies mappings before decompilation.
Note on ASM vs ByteBuddy: We use ASM directly here, not ByteBuddy. ByteBuddy is for
runtime instrumentation (method interception, advice). ASM's ClassRemapper is purpose-built
for bulk renaming across type descriptors, signatures, annotations, etc. Both javgent and
Reconstruct use this approach.
// deobfuscator module
data class ClassMapping(
val obfuscatedName: String, // e.g. "a.b.c"
val originalName: String, // e.g. "com.example.UserService"
val methods: List<MethodMapping>,
val fields: List<FieldMapping>,
)
data class MethodMapping(
val obfuscatedName: String,
val originalName: String,
val descriptor: String, // JVM descriptor for overload resolution
)
data class FieldMapping(
val obfuscatedName: String,
val originalName: String,
val type: String,
)
data class MappingFile(
val classMappings: Map<String, ClassMapping>, // keyed by obfuscated name
)Task 4.1: Create deobfuscator module
Files:
- Create:
deobfuscator/build.gradle.kts - Modify:
settings.gradle.kts— add:deobfuscatormodule
Dependencies: ASM (org.ow2.asm:asm:9.7.1, org.ow2.asm:asm-commons:9.7.1), SLF4J,
JUnit 5. No Kotlin Multiplatform — plain kotlin-jvm plugin. Target JVM 17.
Task 4.2: Implement ProGuardMappingParser
Files:
- Create:
deobfuscator/src/main/kotlin/com/bugdigger/deobfuscator/mapping/ProGuardMappingParser.kt - Create:
deobfuscator/src/test/kotlin/com/bugdigger/deobfuscator/mapping/ProGuardMappingParserTest.kt
ProGuard format:
com.example.Original -> a.b.c:
java.lang.String realField -> d
void realMethod(java.lang.String) -> e
Test with the actual sample/build/obfuscated/mapping.txt if available, plus synthetic
test mappings.
Run: .\gradlew.bat :deobfuscator:test --tests "*.ProGuardMappingParserTest"
Task 4.3: Implement ClassDeobfuscator
Files:
- Create:
deobfuscator/src/main/kotlin/com/bugdigger/deobfuscator/transform/ClassDeobfuscator.kt - Create:
deobfuscator/src/test/kotlin/com/bugdigger/deobfuscator/transform/ClassDeobfuscatorTest.kt
Implementation: Create a subclass of ASM's Remapper that looks up obfuscated → original
names from the MappingFile. Use ClassReader + ClassWriter + ClassRemapper to produce
renamed bytecode.
Two-pass approach (from javgent):
- First pass: scan all classes to resolve hierarchy (superclass/interface chains).
- Second pass: apply
ClassRemapperwith hierarchy-aware method resolution.
Test: obfuscate a class with known mapping, deobfuscate, verify names restored.
Run: .\gradlew.bat :deobfuscator:test --tests "*.ClassDeobfuscatorTest"
Task 4.4: Integrate into composeApp
Files:
- Modify:
composeApp/build.gradle.kts— addimplementation(project(":deobfuscator")) - Modify:
ClassBrowserViewModel.kt— add "Apply Mappings" action, store mapping state - Modify:
ClassBrowserScreen.kt— add "Load Mapping File" button + file picker dialog
Flow: User loads .txt mapping file → mappings stored in ViewModel → when selecting a class,
bytecode is deobfuscated before decompilation → decompiled source shows original names.
These features build on Phase 2's bytecode parsing and introduce graph rendering.
What: Side-by-side comparison of two versions of a class's decompiled source — for example, original vs deobfuscated, or two snapshots taken at different times.
Why: Deobfuscation, instrumentation, and class redefinition all modify bytecode. Seeing exactly what changed is essential.
Architecture:
- Simple text diff algorithm (Myers diff or patience diff) — can use an existing JVM
library like
java-diff-utils. - New
DiffScreenwith dualCodeViewerpanels and diff gutter markers (green = added, red = removed, yellow = changed). - Source data comes from decompiling the same class with/without mappings applied.
Task 5.1: Add diff library dependency
Add io.github.java-diff-utils:java-diff-utils:4.12 to composeApp dependencies.
Task 5.2: Create DiffViewModel + DiffScreen
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/diff/DiffViewModel.kt - Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/diff/DiffScreen.kt - Modify:
Navigation.kt,Sidebar.kt,AppModule.kt
State:
data class DiffUiState(
val leftSource: String = "",
val rightSource: String = "",
val leftLabel: String = "Original",
val rightLabel: String = "Modified",
val diffLines: List<DiffLine> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null,
)
data class DiffLine(
val leftLineNum: Int?,
val rightLineNum: Int?,
val leftText: String,
val rightText: String,
val type: DiffType, // EQUAL, INSERT, DELETE, CHANGE
)UI: Two CodeViewer panels with synchronized scrolling. Diff gutter between them
showing +/−/~ markers. "Compare" button to select two sources (e.g., "Obfuscated" vs
"Deobfuscated" of the same class).
What: Static analysis of bytecode to build a caller → callee graph. Visualize which methods call which other methods, rendered as an interactive directed graph.
Why: Essential for understanding program flow, finding entry points, tracing data paths, and identifying dead code. Complements the existing dynamic method tracing.
Architecture:
- New
CallGraphAnalyzerincore— scans bytecode forINVOKE*instructions (INVOKEVIRTUAL, INVOKESTATIC, INVOKEINTERFACE, INVOKESPECIAL, INVOKEDYNAMIC) and records edges. - Graph data model:
CallGraphwith nodes (methods) and edges (calls). - Compose Canvas renderer — this is the first feature that needs graph visualization.
Build a reusable
GraphViewcomposable with force-directed or layered layout.
data class CallGraph(
val nodes: Set<MethodNode>,
val edges: Set<CallEdge>,
)
data class MethodNode(
val className: String,
val methodName: String,
val descriptor: String,
val id: String = "$className.$methodName$descriptor",
)
data class CallEdge(
val caller: MethodNode,
val callee: MethodNode,
val invokeType: InvokeType, // VIRTUAL, STATIC, INTERFACE, SPECIAL, DYNAMIC
val count: Int = 1, // How many call sites
)Task 6.1: Create CallGraphAnalyzer in core
Files:
- Create:
core/src/main/kotlin/com/bugdigger/core/analysis/CallGraphAnalyzer.kt - Create:
core/src/test/kotlin/com/bugdigger/core/analysis/CallGraphAnalyzerTest.kt
Uses ASM MethodVisitor.visitMethodInsn() and visitInvokeDynamicInsn() to record edges.
Task 6.2: Build reusable GraphView composable
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/components/GraphView.kt
This is a reusable Compose Canvas component that will also be used by the CFG viewer in Phase 4. Features:
- Takes a generic graph (nodes with positions + edges) as input.
- Canvas-based rendering: rectangles for nodes, lines/arrows for edges.
- Pan and zoom (mouse drag + scroll wheel).
- Click-to-select nodes.
- Layout algorithm: start with a simple force-directed layout (Fruchterman-Reingold) or a top-down layered layout (Sugiyama).
Task 6.3: Create CallGraphScreen + CallGraphViewModel
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/callgraph/CallGraphViewModel.kt - Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/callgraph/CallGraphScreen.kt - Modify:
Navigation.kt,Sidebar.kt,AppModule.kt
UI:
- Class/method selector: choose a starting method or "scan all."
- Depth slider: how many levels of callers/callees to show (1–5).
- The
GraphViewcomposable renders the call graph. - Click a node → show method details panel (signature, callers, callees).
- Filter: hide library calls, show only application classes.
What: Visualize the control flow graph (CFG) of a method's bytecode as basic blocks connected by edges. Each block shows a sequence of instructions; edges represent branches, jumps, and fall-throughs.
Why: The most powerful view for understanding method logic — especially obfuscated code where the decompiler struggles. CFGs are the standard representation in RE tools (Ghidra, IDA Pro, Binary Ninja).
Architecture:
- New
CfgBuilderincore— takes bytecode, identifies basic block leaders (targets of jumps, instructions after branches, exception handler starts), splits instruction stream into blocks, builds edge set. - Reuses
GraphViewfrom Phase 3 (Feature 6), with a specializedCfgLayoutthat uses a Sugiyama layered layout (top-to-bottom, since CFGs have a natural direction). - Optional: dominator tree overlay, loop highlighting.
data class BasicBlock(
val id: Int,
val startOffset: Int,
val endOffset: Int,
val instructions: List<Instruction>, // reuses from Feature 3
val isEntryBlock: Boolean,
val isExitBlock: Boolean,
val isCatchBlock: Boolean,
)
data class CfgEdge(
val from: BasicBlock,
val to: BasicBlock,
val type: CfgEdgeType, // FALL_THROUGH, JUMP, BRANCH_TRUE, BRANCH_FALSE, EXCEPTION
)
data class ControlFlowGraph(
val methodName: String,
val blocks: List<BasicBlock>,
val edges: List<CfgEdge>,
val entryBlock: BasicBlock,
)Task 7.1: Create CfgBuilder in core
Files:
- Create:
core/src/main/kotlin/com/bugdigger/core/analysis/CfgBuilder.kt - Create:
core/src/test/kotlin/com/bugdigger/core/analysis/CfgBuilderTest.kt
Algorithm:
- First pass: identify leaders — offset 0, targets of all GOTO/IF_*/SWITCH/JSR, instruction after any branch/goto, exception handler start offsets.
- Second pass: split instruction stream at leaders into
BasicBlockobjects. - Third pass: for each block's last instruction, determine successors (fall-through, jump target, both branches for conditionals, all cases for switch, exception handlers).
Test with methods containing: linear code, if/else, loops, try-catch, switch.
Run: .\gradlew.bat :core:test --tests "*.CfgBuilderTest"
Task 7.2: Implement Sugiyama layout for CFG
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/components/SugiyamaLayout.kt
The Sugiyama algorithm for layered graph drawing:
- Assign layers (reverse topological sort / longest path).
- Minimize edge crossings within layers (barycenter heuristic).
- Assign x-coordinates within layers (spacing).
- Route edges (orthogonal or straight lines).
This extends the GraphView from Feature 6 with a layout strategy specifically suited
to directed acyclic-ish graphs (CFGs can have back-edges for loops).
Task 7.3: Create CfgScreen + CfgViewModel
Files:
- Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/cfg/CfgViewModel.kt - Create:
composeApp/src/jvmMain/kotlin/com/bugdigger/bytesight/ui/cfg/CfgScreen.kt - Modify:
Navigation.kt,Sidebar.kt,AppModule.kt
UI:
- Method selector (class dropdown → method dropdown).
GraphViewwith Sugiyama layout rendering basic blocks as rectangles containing instruction text, connected by arrows.- Color code edges: green = fall-through, blue = jump/goto, orange = branch-true, red = branch-false, purple = exception.
- Color code blocks: gray = normal, yellow = loop header (has back-edge), red = catch block.
- Click block → highlight corresponding source lines in side panel.
- Side panel: decompiled source in
CodeViewerfor context.
Each new screen requires:
- Add enum entry to
ScreeninNavigation.kt - Add
NavigationRailIteminSidebar.kt(connection-gated) - Add
whenbranch in the main navigation composable - Register ViewModel in
AppModule.kt
Final sidebar order (9 screens):
ATTACH → CLASS_BROWSER → HIERARCHY → INSPECTOR → STRINGS → CALL_GRAPH → CFG → TRACE → DIFF → SETTINGS
ASM is needed in core (Features 1, 3, 6, 7) and deobfuscator (Feature 4).
Add once per module:
implementation("org.ow2.asm:asm:9.7.1")
implementation("org.ow2.asm:asm-commons:9.7.1") // for ClassRemapper in deobfuscator
implementation("org.ow2.asm:asm-util:9.7.1") // optional, for Textifier debug outputGraphView(built in Feature 6) is reused by Feature 7.CodeViewer(existing) is reused by Features 3, 5, 7.ConstantExtractor(Feature 1) andBytecodeDisassembler(Feature 3) share ASM visitor patterns — consider a shared baseBytecodeAnalyzerif patterns converge.
- Unit tests for all
coreanddeobfuscatoranalysis classes (pure logic, no UI). - ViewModel tests for all new ViewModels (MockK +
runBlocking). - Integration test: extend
AgentIntegrationTestto test deobfuscation round-trip with thesamplemodule's obfuscated JAR + mapping file.
| Phase | Features | Estimated Effort |
|---|---|---|
| 1 | String Extraction + Class Hierarchy | 3–4 days |
| 2 | Bytecode Inspector + JAR Deobfuscation | 5–7 days |
| 3 | Bytecode Diff + Method Call Graph | 5–7 days |
| 4 | Control Flow Graph Viewer | 5–7 days |
| Total | 7 features | ~18–25 days |
Feature 8: Debugger Tab (breakpoints, pause/resume, call stack, variable inspection) with a forward-compatible architecture for future Time-Travel Debugging.
Full design doc: devdocs/debugger.md.