Case Study · AI-Driven Migration

오픈소스 Bytecode Viewer 2.13.2를 Java 8에서 Java 21로 옮기는 과정을 Kiro IDE의 AI-DLC(AI-Driven Lifecycle) 규칙 위에서 처음부터 끝까지 기록했습니다. AI가 코드를 대신 쓴 것이 아니라, 사람이 결정하고 AI가 문서·계획·코드·검증을 차곡차곡 쌓아 올린 이야기입니다.

왜 이 프로젝트를 AI-DLC로 옮겼나

Bytecode Viewer는 자바 바이트코드와 안드로이드 APK를 뜯어볼 때 쓰는 오래된 오픈소스 데스크톱 앱입니다. 여섯 개의 디컴파일러(CFR, Procyon, FernFlower, JADX, JD-GUI, Krakatau)와 세 개의 디스어셈블러, 그리고 JavaScript·Python·Ruby·Groovy·Java 플러그인 시스템을 한 프로세스에 담고 있는 작지만 단단한 앱입니다. 다만 Java 8 위에 고정 돼 있었고, 그 위에 현대적인 도구 체인을 얹기엔 점점 부담스러워지고 있었습니다.

이번 과제는 단순했습니다. “Bytecode Viewer를 Java 8에서 Java 21로 옮겨라.” 단순하지만 실제로 해보면 금세 성가셔집니다. SecurityManager는 Java 18에서 제거됐고, 내장 Nashorn 엔진은 Java 15에서 빠졌고, 구 버전 의존성 40여 개 중 상당수는 최신 버전이 Java 11 이상을 요구합니다. 게다가 이 프로젝트는 테스트가 사실상 한 개뿐 입니다.

이런 전형적인 브라운필드 현대화를 감으로 처리하면 반드시 무언가를 깜박합니다. 어디를 지웠는지, 왜 지웠는지, 어떤 대안을 검토했는지가 휘발되기 쉽습니다. 그래서 이번에는 Kiro IDE의 AI-DLC(AI-Driven Lifecycle) 방법론을 그대로 얹어, 모든 결정을 문서화하고 승인 게이트를 네 번 거치며 진행했습니다.

결과부터 놓고 시작하겠습니다.

지표
소스 파일 수 302 (main 301 + test 1)
변경 파일 pom.xml + Java 소스 4개 + Dockerfile.build 신규 + Dockerfile.vnc 신규
Java 버전 1.8 → 21
빌드 결과 target/Bytecode-Viewer-2.13.2.jar (97MB fat JAR)
빌드 환경 Docker (maven:3.9-eclipse-temurin-21)
빌드 상태 [DONE] SUCCESS — 302개 전부 컴파일

Kiro IDE와 AI-DLC, 그리고 Steering

Kiro IDE

Kiro는 스펙 주도 개발(Spec-Driven Development) 을 전제로 만든 AI IDE입니다. 이번 작업에서 활용한 기능은 네 가지입니다.

  • Spec workflow — Requirements → Design → Tasks 3단계 스펙 작성
  • Hooks — 파일 저장, 툴 호출 전후 같은 이벤트에 AI 액션을 자동 연결
  • Steering — 워크스페이스 단위로 AI에게 항상 적용할 규칙·가이드 주입
  • MCP — Model Context Protocol. 외부 컨텍스트 서버 연동

이번 케이스의 핵심은 Steering 입니다. AI-DLC 규칙을 Steering 파일에 박아 두면, 사용자가 “시작해봐"처럼 한마디만 던져도 에이전트가 AI-DLC 플로우를 따라 단계별로 움직입니다.

AI-DLC란 무엇인가

AI-DLC는 AI 에이전트가 전체 소프트웨어 라이프사이클을 단계별로 진행하되, 각 단계마다 사람의 승인 게이트를 두는 방법론입니다. 세 단계 구조가 전부입니다.

┌───────────────────────────────────────────────────────┐
│ INCEPTION                                             │
│  Workspace Detection → Reverse Engineering →          │
│  Requirements → User Stories → Workflow Planning →    │
│  Application Design → Units Generation                │
├───────────────────────────────────────────────────────┤
│ CONSTRUCTION (per unit)                               │
│  Functional Design → NFR Req/Design →                 │
│  Infra Design → Code Generation → Build & Test        │
├───────────────────────────────────────────────────────┤
│ OPERATIONS                                            │
│  Deploy / Run / Monitor / Maintain                    │
└───────────────────────────────────────────────────────┘

각 단계의 산출물은 aidlc-docs/ 하위 경로에 문서로 고정 됩니다. aidlc-state.md로 현재 단계를 추적하고, 모든 의사결정은 audit.md에 타임스탬프와 함께 쌓입니다. 어떤 단계는 SKIP 할 수 있지만, SKIP했다는 사실과 근거 또한 반드시 기록합니다.

Greenfield와 Brownfield

구분 Greenfield Brownfield
정의 새 프로젝트 기존 코드가 있는 프로젝트
필수 단계 요구 분석부터 시작 Reverse Engineering 필수
본 케이스 [N] [Y]

이번 케이스는 당연히 브라운필드입니다. Kiro는 작업 시작 직후 워크스페이스를 스캔해 pom.xml을 찾아내고, 리버스 엔지니어링 단계를 자동으로 편성했습니다.

Kiro Steering으로 규칙을 AI에 강제하기

Steering 파일은 .kiro/steering/aidlc-rules/*.md에 둡니다. 프론트매터로 언제 이 규칙을 주입할지 선택합니다.

  • inclusion: always — 모든 대화에 포함 (이번 케이스의 AI-DLC 규칙)
  • inclusion: fileMatch + fileMatchPattern — 특정 파일 열람 시에만 포함
  • inclusion: manual# 프롬프트로 사용자가 직접 참조할 때만 포함

이번에 사용한 규칙 가운데 핵심만 추려 봅니다.

브라운필드가 감지되면 먼저 Reverse Engineering을 실행한다.

각 단계 완료 후 사용자에게 승인을 요청한다.

모든 승인/거절은 aidlc-docs/audit.md에 타임스탬프와 함께 기록한다.

문서는 aidlc-docs/ 안에, 코드는 워크스페이스 루트에 둔다(혼합 금지).

대상: Bytecode Viewer가 Java 8에 묶여 있던 이유

이 앱의 루트는 src/main/java/the/bytecode/club/bytecodeviewer/이고, 그 아래에 api · bootloader · cli · compilers · decompilers · gui · malwarescanner · plugin · resources · searching · translation · util 패키지가 있습니다. Swing GUI와 CLI를 동시에 제공하고 외부 플러그인까지 실행할 수 있는 구조라 전방위로 의존성이 많습니다.

현대 자바 쪽에서 보면 걸림돌도 분명합니다.

이슈 영향
SecurityManager 사용 (util/SecurityMan.java) Java 18에서 제거됨
Nashorn JS 엔진 (JS 플러그인 지원) Java 15에서 제거됨
오래된 의존성 (DarkLaf 3.0.2, JADX 1.4.7, RSyntaxTextArea 3.6.0) 최신은 Java 11+ 요구
테스트 1개 자동 검증의 한계
로컬에 Java/Maven 미설치 Docker로 빌드 환경 구성 필요

실제 진행 타임라인

시간대별 요약부터 살펴봅니다. 모든 승인 엔트리는 audit.md에 그대로 박혀 있습니다.

2026-05-12 18:22  "시작해봐"                    → Workspace Detection
2026-05-12 18:23  자동 스캔 완료                 → Reverse Engineering 완료
2026-05-12 18:33  질문서 생성 (requirement-verification-questions.md)
2026-05-12 19:04  사용자 답변 수집 → requirements.md 작성
2026-05-12 19:08  "승인"                         → Workflow Plan 승인
2026-05-12 23:45  "승인해"                       → Code Generation Plan 승인
2026-05-12 23:57  "승인해"                       → 실제 코드 수정 시작
2026-05-13 00:25  "승인"                         → Code Generation 완료 승인
2026-05-13 00:47  Docker 빌드 SUCCESS, 97MB JAR 생성
flowchart TD
    Start(["User: 시작해봐"])

    subgraph INCEPTION["INCEPTION PHASE"]
        WD["Workspace Detection [DONE]"]
        RE["Reverse Engineering [DONE]"]
        RA["Requirements Analysis [DONE]"]
        WP["Workflow Planning [DONE]"]
    end

    subgraph CONSTRUCTION["CONSTRUCTION PHASE"]
        CG["Code Generation (Plan + Execute) [DONE]"]
        BT["Build and Test [DONE]"]
    end

    Start --> WD --> RE --> RA --> WP --> CG --> BT --> End(["Complete"])

User Stories · Application Design · Units Generation · Functional Design · NFR · Infrastructure Design 단계는 사유를 기록하고 SKIP 했습니다. 근거는 aidlc-docs/inception/plans/execution-plan.md에 남아 있습니다.

단계별 기록 — 탐지·리버스 엔지니어링·요구·계획

Workspace Detection — 사실만 모은다

첫 트리거는 정말 짧습니다. 사용자는 시작해봐 한마디만 던졌습니다. AI-DLC의 첫 원칙은 이 단계에서 사실(Fact)만 수집 하라는 것이고, 의견과 결정은 다음 단계로 미룹니다.

  1. 루트 파일 목록을 훑어 pom.xml 존재 확인 → Brownfield 확정
  2. src/main/java 파일 개수 카운트 → 302개
  3. pom.xml 파싱 → <java.version>1.8</java.version> 추출
  4. 시스템에 Java/Maven 설치 여부 확인 → 미설치

이 단계의 산출물은 두 개뿐입니다. aidlc-docs/aidlc-state.md(상태 트래킹 초기화)와 aidlc-docs/audit.md의 첫 엔트리.

Reverse Engineering — 이후 모든 단계의 근거

브라운필드 프로젝트에서는 이 단계가 모든 걸 가릅니다. Kiro는 aidlc-docs/inception/reverse-engineering/ 아래에 다음 문서를 한꺼번에 만들었습니다.

architecture.md            # 계층/모듈 구조
business-overview.md       # 이 앱이 뭘 하는지
technology-stack.md        # Java 8, Maven, ASM, Swing, ...
component-inventory.md     # 6 decompiler + 3 disassembler ...
dependencies.md            # pom.xml 의존성 + 리스크 주석
code-structure.md          # 패키지 트리, 주요 클래스 위치
reverse-engineering-timestamp.md

요점은 이 문서들이 다음 단계의 입력 이 된다는 것입니다. 예를 들어 technology-stack.md에서 “Nashorn 엔진 사용"을 발견하면, 자동으로 요구 질문서의 Q3(“Nashorn 대체 방안?")으로 승계됩니다.

Requirements Analysis — 질문서로 주고받기

자동 생성된 질문서는 aidlc-docs/inception/requirements/requirement-verification-questions.md에 떨어집니다. 포맷은 단순합니다. 가이드 + 객관식 + [Answer]: 빈칸. 가이드에는 배경 지식과 트레이드오프를 넣어, 사용자가 왜 그 선택을 하는지 스스로 돌아보게 합니다.

실제 답변 결과는 이렇게 정리됐습니다.

Q 주제 사용자 선택
Q1 Java 버전 B) Java 21 (LTS)
Q2 SecurityManager 대체 전략 C) ProcessBuilder 격리
Q3 Nashorn 대체 A) GraalJS
Q4 의존성 업그레이드 범위 D) Java 21 최신, 필요 시 17+ 폴백
Q5 보안 규칙 적용 B) Skip (데스크톱 앱)
Q6 PBT 규칙 B) 부분 적용
Q7 워크플로우 계획 B) 마이그레이션 중심, Docker 빌드

답변이 모이면 에이전트가 requirements.md를 씁니다. 여기서 주목할 점은 질문 자체가 문서로 남는다는 것 입니다. 나중에 “왜 21을 골랐는가"를 되짚을 때, 질문서의 [Answer]:가 그대로 감사 추적의 근거가 됩니다.

Workflow Planning — 실행과 SKIP을 함께 기록

산출물은 aidlc-docs/inception/plans/execution-plan.md입니다.

  • [DONE] Workspace Detection · Reverse Engineering · Requirements · Workflow Planning
  • [DONE] Code Generation · Build & Test
  • [SKIP] User Stories — 마이그레이션이라 사용자 시나리오 없음
  • [SKIP] Application Design — 기존 아키텍처 유지
  • [SKIP] Units Generation — 단일 모놀리식 앱
  • [SKIP] Functional Design / NFR / Infra — 기능·NFR 변경 없음, 인프라 없음

사용자가 승인을 입력하면 audit.md에 엔트리가 붙고 Construction 단계로 전이합니다.

코드 생성과 실제 변경

Plan 문서부터 만든다

코드를 바로 손대지 않습니다. 체크리스트 문서부터 만듭니다. 산출물은 aidlc-docs/construction/plans/bytecode-viewer-code-generation-plan.md이고, 한국어 요약본 bytecode-viewer-code-generation-plan-ko.md를 같이 둡니다.

  1. pom.xmljava.version 변경, 의존성 업그레이드, GraalJS 주석 해제
  2. SecurityManager 제거 — SecurityMan.java 삭제 + 참조 3개 파일 정리
  3. JavascriptPluginLaunchStrategy.java — primary 엔진을 graal.js로 변경
  4. Deprecated API 점검 — javap.Main, java.security.*
  5. Dockerfile.build 생성 (빌드 검증용)
  6. migration-summary.md 생성

Plan → Approve → Execute 구조. 승인 전까지는 실제 파일을 건드리지 않습니다. 이 원칙 하나만 지켜도 마이그레이션 중 우발적 부작용을 크게 줄일 수 있습니다.

승인 뒤 실제로 이루어진 변경

pom.xml

  • <java.version>1.8</java.version><java.version>21</java.version>
  • google-java-format: 1.7 → 1.25.2
  • jadx: 1.4.7 → 1.5.1
  • js.version: 21.2.0 → 24.1.1 (이후 빌드 중 24.1.2로 조정)
  • GraalJS 의존성 주석 해제 (org.graalvm.polyglot:js, org.graalvm.js:js-scriptengine)

Before (Java 8, Nashorn 의존, GraalJS 주석 처리)

<java.version>1.8</java.version>
<google-java-format.version>1.7</google-java-format.version>
<jadx.version>1.4.7</jadx.version>
<js.version>21.2.0</js.version>

<!-- TODO Re-add for Graal.JS support -->
<!--<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>${js.version}</version>
</dependency>
<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>${js.version}</version>
</dependency>-->

After (Java 21, GraalJS 활성화)

<java.version>21</java.version>
<google-java-format.version>1.25.2</google-java-format.version>
<jadx.version>1.5.1</jadx.version>
<js.version>24.1.2</js.version>

<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js</artifactId>
    <version>${js.version}</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>${js.version}</version>
</dependency>

BytecodeViewer.java — SecurityManager 제거

Before

public class BytecodeViewer {
    // ...
    public static SecurityMan sm = new SecurityMan();   // line 140

    public static void main(String[] args) {
        // ...
        System.setSecurityManager(sm);                   // line 175
        // ...
    }
}

After

public class BytecodeViewer {
    // ...
    // SecurityManager removed — deprecated in Java 17, removed in Java 18.
    // Plugin sandboxing migrated to ProcessBuilder-based isolation (see FR-02).

    public static void main(String[] args) {
        // ...
        // No setSecurityManager call
        // ...
    }
}

이로써 이 프로세스는 더 이상 JVM 내부에 별도 권한 모델을 두지 않습니다. 그 대신 플러그인을 실행할 때 ProcessBuilder 기반 격리 경로를 택했고, 격리 로직 자체는 후속 FR-02 작업으로 분리했습니다. 1차 PR에서는 “우선 컴파일과 기존 기능을 살리는” 데 집중했습니다.

JavascriptPluginLaunchStrategy.java — Nashorn에서 GraalJS로

// Before
private static final String FIRST_PICK_ENGINE = "nashorn";
private static final String FALLBACK_ENGINE   = "graal.js";

// After
private static final String FIRST_PICK_ENGINE = "graal.js";
private static final String FALLBACK_ENGINE   = "js";

// GraalJS polyglot 바인딩
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("polyglot.js.allowHostAccess", true);
bindings.put("polyglot.js.allowHostClassLookup",
             (Predicate<String>) s -> true);

삭제·유지·신규 파일 요약

  • 삭제src/main/java/.../util/SecurityMan.java
  • 수정BytecodeViewer.java, EZInjection.java, JavapDisassembler.java, JavascriptPluginLaunchStrategy.java
  • 유지NullSecurityManagerScanner.java (이 클래스는 본인 SM 제거와 무관하게, 분석 대상 바이트코드 에서 악성 패턴을 찾는 맬웨어 스캐너입니다)
  • 신규Dockerfile.build (멀티스테이지, maven:3.9-eclipse-temurin-21)

Docker 기반 빌드와 VNC GUI 실측

재현 가능한 빌드 — Dockerfile.build

우리 빌드 머신에는 Java도 Maven도 깔려 있지 않습니다. 대신 Docker 이미지 하나만 있으면 누구나 같은 결과를 낼 수 있습니다.

# Stage 1: build
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
COPY libs ./libs
RUN mvn -q -DskipTests package

# Stage 2: runtime (image only carries the JAR)
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/Bytecode-Viewer-2.13.2.jar ./bcv.jar
ENTRYPOINT ["java", "-jar", "bcv.jar"]

이 한 파일로 기대되는 결과는 단순합니다.

  • 302개 Java 파일 컴파일 성공
  • 97MB Fat JAR 생성 (shade plugin)
  • 유닛 테스트는 사실상 없음 → 수동 기능 검증 체크리스트로 보완

빌드 중에 해결된 이슈. GraalJS 버전을 24.1.1에서 24.1.2로 조정했고, 아티팩트 아이디도 org.graalvm.js:js에서 org.graalvm.polyglot:js(POM type)로 바꿔야 했습니다. Java 마이그레이션에서 “어떤 버전을 상수로 박느냐"는 빌드 전에 단정할 수 없다 는 교훈이 또 한 번 나왔습니다.

GUI를 실제로 띄워 봐야 끝난다 — Dockerfile.vnc

Bytecode Viewer는 Swing GUI 앱입니다. mvn package가 통과했다고 해서 “마이그레이션 OK"가 되진 않습니다. 서버 환경에는 X 서버도 없으므로, 가상 디스플레이를 만들고 VNC로 뚫어 실제 창을 띄워야 합니다. Xvfb(가상 디스플레이) + fluxbox(경량 WM) + x11vnc(원격 접속) 조합을 사용합니다.

FROM eclipse-temurin:21-jdk

RUN apt-get update && apt-get install -y --no-install-recommends \
      xvfb x11vnc fluxbox xterm \
      libxrender1 libxtst6 libxi6 libxext6 libx11-6 \
    && rm -rf /var/lib/apt/lists/*

ENV DISPLAY=:0

COPY target/Bytecode-Viewer-2.13.2.jar /app/bytecode-viewer.jar

EXPOSE 5900

CMD Xvfb :0 -screen 0 1280x800x24 & \
    sleep 1 && \
    fluxbox & \
    x11vnc -display :0 -forever -passwd test1234 -rfbport 5900 & \
    sleep 2 && \
    java -jar /app/bytecode-viewer.jar
패키지 역할
xvfb 헤드리스 서버에서도 동작하는 가상 X 디스플레이
fluxbox 창 관리자(WM). Swing 창에 타이틀바와 리사이즈를 제공
x11vnc 가상 디스플레이를 VNC 프로토콜로 공개(포트 5900)
libxrender1, libxtst6, libxi6, libxext6, libx11-6 Swing/AWT가 의존하는 X 런타임 라이브러리

이미지 빌드와 컨테이너 실행

# 1) GUI용 Docker 이미지 빌드
cd bytecode-viewer
docker build -f Dockerfile.vnc -t bcv-vnc . 2>&1 | tail -10

# 2) 컨테이너 기동 (백그라운드, 5900 포트 매핑)
docker run -d --name bcv-test -p 5900:5900 bcv-vnc

# 3) 기동 로그 확인
sleep 5 && docker logs bcv-test 2>&1 | tail -15

# 4) 테스트 종료 후 정리
docker stop bcv-test && docker rm bcv-test

실제 실행 로그

[engine] WARNING: The polyglot engine uses a fallback runtime that does not
support runtime compilation to native code. Execution without runtime
compilation will negatively impact the guest application performance.
The following cause was found: JVMCI is not enabled for this JVM.
Enable JVMCI using -XX:+EnableJVMCI.
(...중략...)
Extracting Krakatau
Start up took 1 seconds
Failed to read: session.screen0.titlebar.left
Setting default value
Failed to read: session.screen0.titlebar.right
Setting default value
Successfully extracted Krakatau
Extracting Enjarify

로그 읽는 법은 이렇습니다.

  • GraalJS 경고 — JVMCI가 꺼져 있다는 정보성 안내입니다. 기동에 영향은 없고, 더 조용히 하려면 JVM 인자에 -Dpolyglot.engine.WarnInterpreterOnly=false를 붙이면 됩니다.
  • Extracting Krakatau / Enjarify — BCV가 번들된 서브도구를 압축 해제하는 정상 동작입니다. Java 21에서도 그대로 수행됩니다.
  • fluxbox Failed to read ... — 경량 WM의 기본 테마 경고로 무해합니다.

Windows에서 VNC로 접속

  1. VNC 클라이언트 설치 — RealVNC Viewer 또는 TightVNC Viewer
  2. 접속 정보 — <서버 IP>:5900, 비밀번호 test1234
  3. [NOTE] Windows 기본 원격 데스크톱(RDP)은 VNC 프로토콜이 아니므로 사용할 수 없습니다.

GUI 실측 결과 (스크린샷)

메인 화면 — Java 21 런타임에서 정상 기동

디컴파일러 뷰 — 메뉴, 툴바, 상태바, 탭 로드까지 이상 없음

실행 인자로 JAR을 전달해 좌측 트리에 샘플을 자동 로드한 상태

확인 항목을 체크리스트로 남겨 둡니다.

  • [DONE] GUI 메인 윈도우 정상 렌더링 (Swing + DarkLaf)
  • [DONE] 메뉴바·툴바·상태바 정상 표시
  • [DONE] 디컴파일러 탭(6종) 로드 가능
  • [DONE] GraalJS 엔진 초기화 경고만 있고 크래시 없음
  • [DONE] Krakatau·Enjarify 서브도구 추출 정상
  • [TODO] (수동) 실제 JAR 드래그&드롭 후 각 디컴파일러 결과 비교 — 이후 단계에서 수행

감사 로그에 이 테스트가 남는 방식

## Build and Test - GUI Verification (VNC)
Timestamp  : 2026-05-13T00:53:00Z
Artifact   : target/Bytecode-Viewer-2.13.2.jar (97MB)
Image      : bcv-vnc:latest  (eclipse-temurin:21-jdk + Xvfb + fluxbox + x11vnc)
Container  : bcv-test (-p 5900:5900)
Result     : PASS — GUI started on Java 21, Krakatau/Enjarify extraction OK
Warnings   : GraalJS "JVMCI not enabled" (informational)
Screenshots: mydoc/bytecode-viewer-main-java21.png, mydoc/bytecode-viewer-decompiler-view.png, mydoc/bytecode-viewer-sample-loaded.png

요지는 단순합니다. 마이그레이션 검증은 mvn package OK런타임 구동 OK 라는 두 축으로 쪼개야 하고, GUI 앱은 VNC/Xvfb 조합으로 CI나 원격 환경에서도 실측할 수 있습니다. 로그와 스크린샷 경로까지 audit.md에 박아두면 한참 뒤에 다시 보더라도 그대로 재현됩니다.

산출물 구조와 읽는 순서

aidlc-docs/
├── aidlc-state.md           # 현재 AI-DLC 진행 상태(체크박스)
├── audit.md                 # 모든 승인/의사결정 감사 로그
├── inception/
│   ├── plans/
│   │   └── execution-plan.md                            # 실행/스킵할 단계 결정
│   ├── requirements/
│   │   ├── requirement-verification-questions.md        # 질문서
│   │   ├── requirement-verification-questions-ko.md     # 한글 버전
│   │   └── requirements.md                              # 최종 요구사항
│   └── reverse-engineering/
│       ├── architecture.md
│       ├── business-overview.md
│       ├── technology-stack.md
│       ├── component-inventory.md
│       ├── dependencies.md
│       ├── code-structure.md
│       └── reverse-engineering-timestamp.md
└── construction/
    ├── plans/
    │   ├── bytecode-viewer-code-generation-plan.md
    │   └── bytecode-viewer-code-generation-plan-ko.md
    ├── bytecode-viewer/
    │   └── code/
    │       └── migration-summary.md                     # 실제 변경 내역 요약
    └── build-and-test/
        └── build-and-test-summary.md                    # Docker 빌드/검증 결과

처음 이 프로젝트를 받는 사람에게는 아래 순서를 권합니다.

  1. aidlc-state.md — 지금 어디까지 왔는지
  2. reverse-engineering/business-overview.md — 앱이 뭔지
  3. requirements/requirements.md — 무엇을 달성해야 하는지
  4. inception/plans/execution-plan.md — 어떤 길을 갈지
  5. construction/plans/bytecode-viewer-code-generation-plan.md — 구체적 수정 지점
  6. construction/bytecode-viewer/code/migration-summary.md — 실제로 바꾼 것
  7. construction/build-and-test/build-and-test-summary.md — 어떻게 확인했나
  8. audit.md — 어떤 의사결정이 있었나 (감사용)

Steering·Hooks로 내 프로젝트에 이식하기

새 프로젝트에 AI-DLC를 얹는 5단계

  1. Steering 설치.kiro/steering/aidlc-rules/에 AI-DLC 규칙 파일 복사
  2. 상태 파일 준비aidlc-docs/aidlc-state.md 초기화
  3. 감사 로그 준비aidlc-docs/audit.md 빈 파일 생성
  4. 트리거 — 사용자는 “시작해봐” 또는 “Start AI-DLC” 한마디
  5. 승인 루프 — 에이전트가 단계별 산출물을 만들면 검토 → 승인/거절 → 다음 단계

Steering 파일 예시 (간소화)

.kiro/steering/aidlc-rules/00-aidlc.md

---
inclusion: always
---

# AI-DLC Rules

1. Code lives at the workspace root.
   Documentation lives only under `aidlc-docs/`. Never mix them.
2. On every turn, consult `aidlc-docs/aidlc-state.md` to determine the
   current stage. If the file is missing, treat it as a new project.
3. If a `pom.xml`, `package.json`, `Cargo.toml`, etc. is present,
   the project is **Brownfield** — run Reverse Engineering before
   anything else.
4. Each stage produces files in a fixed location
   (see `docs/GENERATED_DOCS_REFERENCE.md`).
5. Every user decision must be appended to `aidlc-docs/audit.md`
   with an ISO-8601 timestamp.
6. Skipping a stage is allowed but must record a `Rationale:` line.
7. Before executing Code Generation, produce a plan document and
   ask for explicit user approval.

.kiro/steering/aidlc-rules/10-brownfield.md

---
inclusion: fileMatch
fileMatchPattern: "**/pom.xml"
---

# Brownfield Expansion

When a `pom.xml` file is detected:

- Record the Maven `groupId`, `artifactId`, `version`, and `java.version`.
- Enumerate top-level dependencies and flag those with known EOL
  (e.g. Nashorn, SecurityManager based APIs) in
  `aidlc-docs/inception/reverse-engineering/dependencies.md`.

Hooks로 반복 작업 자동화

Kiro Hooks를 쓰면 특정 이벤트에 AI 액션을 자동으로 붙일 수 있습니다.

pom.xml이 저장될 때마다 의존성 트리를 다시 문서화

{
  "name": "Refresh dependency doc",
  "version": "1.0.0",
  "when": {
    "type": "fileEdited",
    "patterns": ["pom.xml"]
  },
  "then": {
    "type": "askAgent",
    "prompt": "pom.xml이 변경되었습니다. aidlc-docs/inception/reverse-engineering/dependencies.md를 최신화하세요."
  }
}

Spec 태스크가 완료될 때마다 mvn test 실행

{
  "name": "Verify after task",
  "version": "1.0.0",
  "when": { "type": "postTaskExecution" },
  "then": {
    "type": "runCommand",
    "command": "docker run --rm -v $(pwd):/app -w /app maven:3.9-eclipse-temurin-21 mvn test -q"
  }
}

이번 케이스에서 AI-DLC가 잘 통한 이유

  • 승인 게이트가 네 번 있었습니다. 질문서 → 요구 → 워크플로우 계획 → 코드 생성 계획 → 실제 코드 생성으로 이어지는 흐름에서 사용자는 결정 지점만 확인했습니다.
  • “문서로 먼저” 원칙 덕분에 GraalJS 아티팩트 아이디 같은 세부 사항을 빌드 전에 명확히 적어둘 수 있었고, 빌드 중 수정도 수월했습니다.
  • 감사 로그 가 남기 때문에 감사·리뷰·롤백 근거가 자연스럽게 확보됐습니다.

우리가 얻은 교훈

  1. Plan → Approve → Execute를 생략하지 말 것. 아무리 “간단한 변경"이라도 pom.xml 하나에서 GraalJS artifactId 불일치 같은 이슈가 튀어나옵니다.
  2. Deprecated API 제거는 체인으로 번진다. SecurityMan 하나 지우면 BytecodeViewer·EZInjection·JavapDisassembler까지 줄줄이 따라 수정됩니다. 플랜 문서에 “Related files to touch"를 미리 적어 두면 덜 다칩니다.
  3. Docker 빌드를 1차 검증 수단으로. 로컬에 Java/Maven이 없어도 재현 가능한 빌드가 나옵니다. 에이전트 루프에 mvn package -q 성공 여부를 걸어 두면 회귀 방지에 효과적입니다.
  4. 테스트가 부족하면 수동 검증을 산출물로 만든다. build-and-test-summary.md에 GUI 수동 테스트 체크리스트를 묶어 두는 것만으로도 다음 사람이 덜 헤맵니다.

실무 체크리스트

  • [CHECK] .kiro/steering/ 안에 AI-DLC 규칙 파일이 있다
  • [CHECK] aidlc-docs/aidlc-state.md에 현재 단계가 표시되어 있다
  • [CHECK] 승인·거절은 aidlc-docs/audit.md에만 남긴다
  • [CHECK] Brownfield라면 reverse-engineering/ 문서가 완비돼 있다
  • [CHECK] requirements.md는 사용자의 객관식 선택 기반으로 구성돼 있다
  • [CHECK] execution-plan.md에 SKIP 이유가 명시돼 있다
  • [CHECK] Code Generation은 Plan 문서 부터 만들었다
  • [CHECK] 빌드 검증은 Docker 또는 동일한 이미지·버전 기반으로 재현 가능하다

부록 A. 용어집

용어 설명
AI-DLC AI-Driven Lifecycle. AI 에이전트가 전체 SW 라이프사이클을 단계별로 진행하는 방법론
Brownfield 기존 코드 위에 작업하는 프로젝트
Greenfield 새로 시작하는 프로젝트
Inception Phase 요구·설계·계획을 확정하는 상위 단계
Construction Phase 실제 구현과 검증을 수행하는 단계
Steering Kiro의 항상-적용 규칙 파일(.kiro/steering/**/*.md)
Hook 파일 저장, 태스크 완료 같은 이벤트에 AI 액션을 자동 트리거
MCP Model Context Protocol. 외부 컨텍스트 서버 연동 규격
Fat JAR 모든 의존성을 하나로 묶은 실행 가능 JAR (shade plugin)
Nashorn JDK에 있던 자바스크립트 엔진. Java 15에서 제거됨
GraalJS Nashorn 후속 JS 엔진. Java 21과 호환
SecurityManager 퍼미션 샌드박스. Java 17 deprecated, Java 18 제거
ProcessBuilder 별도 JVM 프로세스로 플러그인을 격리 실행
PBT Property-Based Testing. 무작위 입력으로 엣지 케이스를 탐지

부록 B. 명령어 치트시트

# 1) 로컬 Java 없이 컴파일만 확인
docker run --rm -v "$(pwd)":/app -w /app \
  maven:3.9-eclipse-temurin-21 mvn compile -q

# 2) Fat JAR 패키징
docker run --rm -v "$(pwd)":/app -w /app \
  maven:3.9-eclipse-temurin-21 mvn package -DskipTests -q

# 3) 빌드된 JAR 실행 (Java 21)
java -jar target/Bytecode-Viewer-2.13.2.jar

# 4) CLI 모드(회귀 검증용)
java -jar target/Bytecode-Viewer-2.13.2.jar \
     -i test.jar -o output.java \
     -decompiler cfr -t all

# 5) Dockerfile.build 기반 이미지 빌드
cd bytecode-viewer
docker build -f Dockerfile.build -t bcv:java21 .

# 6) GUI 실측 테스트용 VNC 이미지 빌드 & 실행
cd bytecode-viewer
docker build -f Dockerfile.vnc -t bcv-vnc .
docker run -d --name bcv-test -p 5900:5900 bcv-vnc
docker logs bcv-test | tail -20            # 기동 로그 확인
#   VNC 클라이언트로 <server>:5900 접속, 비밀번호 test1234
docker stop bcv-test && docker rm bcv-test # 테스트 종료 후 정리

마무리

이번 케이스에서 얻은 한 줄 요약은 이렇습니다. AI-DLC는 “AI에게 시켜보기"가 아니라 “사실 수집 → 결정 → 계획 → 실행 → 검증 → 감사 로그"라는 고전적인 엔지니어링 사이클을 문서 기반으로 강제하는 도구입니다. AI는 단정적으로 산출물을 내고, 사람은 짧게 결정만 합니다. 그 사이에 승인 게이트와 감사 로그가 끼어들면서, 작고 지루해 보이던 결정들이 모두 다시 찾을 수 있는 자리에 남게 됐습니다.

다음에는 이 흐름 위에 Property-Based Testing 을 얹어, ProcessBuilder 기반 플러그인 격리 구현의 맹점을 자동으로 찾아내는 실험을 공유할 예정입니다. 혹시 지금 Java 8 레거시를 보며 한숨 쉬고 계신 분이 있다면, 작은 모듈 하나라도 이 흐름으로 옮겨 보시길 권합니다. 첫 단계는 정말 단순합니다. 시작해봐.