feat(review-loop): AI 코드 리뷰 루프 이식 + 자율 수정 루프(P0/P1) 배선 - #5
Conversation
- 루프 엔진 이식(48 main + 30 test): com.wanted.backend → com.module06.backend - P0 배선: AutoLoopRunner · AutoLoopOrchestrator · reviewAutoFix 태스크 · scripts/review-autoloop.sh - P1 안전막: VerificationPort · CompileVerification(in-JVM javac) · VerifiedFixer(컴파일 실패 시 롤백) - 게이트: pre-push 훅(Gate1 ArchUnit skip-if-absent) · CI gate1-semgrep · gate2-judge - build.gradle: 루프 deps(anthropic-java·snakeyaml·starter-test) + review-loop 태스크 9종 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthrough로컬 훅과 CI가 변경 Java 파일을 대상으로 결정론적 검사와 LLM Judge를 실행한다. Java 리뷰 루프는 findings를 검증하고 점수화한다. 자동 수정 루프는 예산과 컴파일 검증을 적용한다. 감사 로그와 교훈 저장소가 실행 결과를 기록한다. Changes리뷰 루프 인프라
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Developer
participant PrePushHook
participant ReviewLoopRunner
participant GeminiJudgeAdapter
participant JudgeScorer
participant GitPush
Developer->>PrePushHook: git push
PrePushHook->>ReviewLoopRunner: 변경 Java 파일 목록
ReviewLoopRunner->>GeminiJudgeAdapter: review(filePath, code, policy)
GeminiJudgeAdapter-->>ReviewLoopRunner: findings
ReviewLoopRunner->>JudgeScorer: score(findings)
JudgeScorer-->>PrePushHook: 판정 결과
PrePushHook->>GitPush: push 허용 또는 차단
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
Gate 2 · 결정론 판정95 tests 89 ✅ 0s ⏱️ Results for commit 8382a60. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (12)
review-loop/AUTOLOOP_DESIGN.md-28-60 (1)
28-60: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win이전 패키지명을 현재 패키지명으로 바꾸세요.
Line 30과 Line 57의
com.wanted.backend.reviewloop.judge.AutoLoopRunner는 이 PR의 이식 대상과 다릅니다. 문서와 Gradle 예시를 복사하면AutoLoopRunner를 찾지 못합니다. 두 위치를com.module06.backend.reviewloop.judge.AutoLoopRunner로 변경하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@review-loop/AUTOLOOP_DESIGN.md` around lines 28 - 60, 문서의 두 AutoLoopRunner 참조가 현재 패키지명을 사용하도록 수정하세요. `com.wanted.backend.reviewloop.judge.AutoLoopRunner#main`과 Gradle `mainClass` 값을 모두 `com.module06.backend.reviewloop.judge.AutoLoopRunner`로 변경하고, 나머지 배선과 옵션은 유지하세요.review-loop/UNIFIED_DESIGN.md-158-169 (1)
158-169: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win검증 명령을 실제 스크립트 호출로 바꾸세요.
Line 82-84는 Gradle 래퍼를 만들지 않는다고 설명합니다. 그러나 Line 164와 Line 167은
./gradlew reviewVerify를 실행합니다. 현재scripts/review-verify.sh는--files-from도 받지 않습니다. 이 절차를 따르면 드라이버 검증 단계가 실패합니다.수정 예시
-5. 검증 ./gradlew reviewVerify --args="--files-from <changed>" +5. 검증 bash scripts/review-verify.sh @@ -7. 최종 검증 ./gradlew reviewVerify --args="--files-from <changed> --with-test" +7. 최종 검증 bash scripts/review-verify.sh --with-test🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@review-loop/UNIFIED_DESIGN.md` around lines 158 - 169, Update the review workflow steps 5 and 7 to invoke the existing scripts/review-verify.sh directly instead of the unavailable ./gradlew reviewVerify wrapper, preserving the --files-from <changed> argument and adding --with-test for final verification. Ensure the documented command matches the script’s supported options.src/main/java/com/module06/backend/reviewloop/judge/ReviewRunner.java-34-58 (1)
34-58: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win초기 소진 예산의
AuditSummary계약을 명시하세요.ReviewBudget(0)또는 이미 소진된 budget이면 루프를 실행하지 않고finalVerdict() == null,roundsUsed() == 0인AuditSummary를 반환합니다. 예산이 최소 한 라운드를 요구하면 입력을 거부하고, 0라운드를 허용하면 nullable 결과를 문서화하고 소비자와 회귀 테스트에서 처리하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewRunner.java` around lines 34 - 58, Update ReviewRunner.run and its AuditSummary contract to explicitly handle an initially exhausted ReviewBudget: return an AuditSummary with finalVerdict() null and roundsUsed() 0 without invoking reviewer.review or consuming budget. Ensure the minimum-round budget configuration is rejected when zero rounds are disallowed; otherwise document and handle the nullable verdict in consumers and add regression coverage.scripts/review-fix.sh-10-10 (1)
10-10: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win사용 예시의 패키지 경로를 갱신하세요.
이 PR은
com.wanted.backend에서com.module06.backend로 이식합니다. Line 10의 예시 경로는 여전히com/wanted/backend를 가리킵니다. 존재하지 않는 경로를 안내하면 사용자가 잘못된 인자를 전달합니다.🛠️ 제안 수정
-# bash scripts/review-fix.sh --path src/main/java/com/wanted/backend/domain/cart --domain cart +# bash scripts/review-fix.sh --path src/main/java/com/module06/backend/domain/cart --domain cart🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/review-fix.sh` at line 10, Update the example command comment in review-fix.sh to replace the outdated com/wanted/backend package path with the migrated com/module06/backend path, while preserving the existing cart domain example.src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopCli.java-78-81 (1)
78-81: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
build디렉터리를 먼저 생성하세요.Line 80은
build/review-cli-screen.txt에 바로 씁니다.build/가 없으면NoSuchFileException이 발생합니다../gradlew clean직후나 JVM으로 직접 실행할 때 이 경로가 없을 수 있습니다. Line 16의logsDir처리와 동일하게 부모 디렉터리를 보장하세요.🛠️ 제안 수정
String screen = s.toString(); System.out.println(screen); // 터미널 출력 - Files.writeString(Path.of("build/review-cli-screen.txt"), screen); // 깨끗한 확인용 + Path screenFile = Path.of("build/review-cli-screen.txt"); + Files.createDirectories(screenFile.getParent()); + Files.writeString(screenFile, screen); // 깨끗한 확인용🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopCli.java` around lines 78 - 81, Update the file-writing flow around Files.writeString in ReviewLoopCli so the parent build directory is created or otherwise guaranteed to exist before writing build/review-cli-screen.txt. Match the existing logsDir directory-initialization approach, while preserving the current screen output and file contents.review-loop/knowledge/README.md-3-13 (1)
3-13: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
ReviewLoopCli의 교훈 저장 경로를 정본으로 통일하세요.ReviewLoopCli는review-loop/logs/lessons.jsonl에 저장하지만,ReviewLoopRunner(Gate 2)와reviewLesson은ReviewLoopPaths.LESSONS인review-loop/knowledge/lessons.jsonl을 사용합니다.ReviewLoopCli에서ReviewLoopPaths.LESSONS를 사용하도록 수정하세요. 현재.gitignore에는review-loop/logs/규칙도 없으므로 README의 설명과 ignore 규칙을 일치시키세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@review-loop/knowledge/README.md` around lines 3 - 13, ReviewLoopCliの教訓保存先をReviewLoopPaths.LESSONSに統一し、review-loop/knowledge/lessons.jsonlへ保存するよう更新してください。あわせてREADMEのlogs/に関する説明と.gitignoreの設定を確認し、review-loop/logs/を揮発性ログとして無視する規則を追加して記述と実際の挙動を一致させてください。src/main/java/com/module06/backend/reviewloop/judge/CompileVerification.java-36-52 (1)
36-52: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win임시 출력 디렉터리를 삭제하지 않습니다.
verify호출마다 라인 38이 새 임시 디렉터리를 만듭니다. 그 디렉터리와 생성된.class파일은 삭제되지 않습니다. 루프는 파일마다 여러 라운드를 돌므로 실행마다 임시 디렉터리가 누적됩니다. 검증 후 디렉터리를 정리하십시오.🧹 제안: 검증 후 임시 디렉터리 정리
String classpath = System.getProperty("java.class.path", ""); + Path outDir = null; try { - Path outDir = Files.createTempDirectory("autoloop-javac"); + outDir = Files.createTempDirectory("autoloop-javac"); StringWriter diag = new StringWriter(); try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)) { Iterable<? extends JavaFileObject> units = fm.getJavaFileObjects(filePath.toFile()); List<String> options = List.of( "-classpath", classpath, "-d", outDir.toString(), "-encoding", "UTF-8"); boolean ok = compiler.getTask(diag, fm, null, options, null, units).call(); return new VerifyResult(ok, diag.toString()); } } catch (IOException e) { return new VerifyResult(false, "컴파일 게이트 IO 오류: " + e.getMessage()); + } finally { + deleteQuietly(outDir); } } + + private static void deleteQuietly(Path dir) { + if (dir == null) { + return; + } + try (var paths = Files.walk(dir)) { + paths.sorted(java.util.Comparator.reverseOrder()).forEach(p -> { + try { + Files.deleteIfExists(p); + } catch (IOException ignored) { + // 임시 파일 정리 실패는 검증 결과에 영향을 주지 않는다. + } + }); + } catch (IOException ignored) { + // 정리 실패 무시 + } + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/CompileVerification.java` around lines 36 - 52, Update CompileVerification.verify to always delete the temporary outDir and its generated class files after compilation completes, including when compilation or verification fails. Use guaranteed cleanup around the existing compiler task while preserving the current VerifyResult and diagnostic handling.src/main/java/com/module06/backend/reviewloop/judge/AutoFixRunner.java-41-63 (1)
41-63: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
AutoFixRunner의 소진된ReviewBudget결과를 null-safe하게 처리하십시오.
ReviewBudget가 이미 소진되면run은 판정 없이finalVerdict == null인 결과를 반환합니다.AutoLoopOrchestrator는 이 호출을 건너뛰고finalVerdict()도 검사하지만, 다른 호출자는 null을 역참조할 수 있습니다. 소진된 budget으로 호출하지 않거나 명시적 verdict를 반환하십시오. null을 반환하는 조기 반환만 추가하면 문제는 남습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/AutoFixRunner.java` around lines 41 - 63, Update AutoFixRunner.run so an already-exhausted ReviewBudget cannot produce an AutoFixResult with a null verdict: either reject/avoid the call before entering the loop or return an explicit terminal verdict. Ensure every return path, including the final path after the loop, provides a non-null finalVerdict rather than adding only a null-return guard.src/test/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapterLiveTest.java-38-40 (1)
38-40: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
build/디렉터리가 없으면 결과 파일 쓰기가 실패합니다.
build/gemini-findings.txt에 쓰기 전에 부모 디렉터리 존재를 보장하지 않습니다. Gradle 태스크로 실행하면build/가 이미 존재하지만, 다른 실행 경로(IDE 등)에서는 존재하지 않을 수 있습니다. 이 경우 실제 API 호출과 finding 검증은 성공했는데도 파일 쓰기 단계에서NoSuchFileException이 발생해 테스트가 실패합니다.
ReviewLoopRunner.writeFindings처럼 부모 디렉터리를 먼저 생성해 주십시오. 함께, 이미 import된Files,Path를 두고 전체 한정 이름을 쓴 부분도 정리해 주십시오.🔧 제안하는 수정
- // 데모: 실제로 뭐라고 잡았는지 파일로 남긴다(확인용). - java.nio.file.Files.writeString(java.nio.file.Path.of("build/gemini-findings.txt"), - findings.stream().map(Object::toString).collect(java.util.stream.Collectors.joining("\n"))); + // 데모: 실제로 뭐라고 잡았는지 파일로 남긴다(확인용). + Path out = Path.of("build/gemini-findings.txt"); + Files.createDirectories(out.getParent()); + Files.writeString(out, + findings.stream().map(Object::toString).collect(java.util.stream.Collectors.joining("\n")));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapterLiveTest.java` around lines 38 - 40, Update the findings-file write in GeminiJudgeAdapterLiveTest to create the parent directory before calling writeString, matching ReviewLoopRunner.writeFindings behavior so direct or IDE execution succeeds when build is absent. Reuse the existing Files and Path imports instead of fully qualified names.src/main/java/com/module06/backend/reviewloop/judge/DriverBudget.java-31-32 (1)
31-32: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
.git파일을 해석하여 Git 경로를 구성하십시오. 이 프로젝트는 별도 worktree 사용을 권장합니다. worktree와 submodule에서는.git이 파일이므로 현재 코드는"(unknown)"을 반환하거나 상태 저장에 실패합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/DriverBudget.java` around lines 31 - 32, DriverBudget의 STATE_FILE 및 HEAD_FILE을 고정된 .git 하위 경로로 만들지 말고, .git이 디렉터리인지 파일인지 확인해 실제 Git 디렉터리를 해석하도록 수정하십시오. .git 파일의 gitdir 경로를 읽어 worktree와 submodule에서도 HEAD 조회 및 상태 저장이 동일하게 동작하게 하며, 일반 저장소의 디렉터리 형태도 유지하십시오.src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.java-116-118 (1)
116-118: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win리포트 출력 전에 상위 디렉터리를 만드세요.
Line 118은
build/reviewloop-run.txt에 직접 씁니다.build디렉터리가 없으면NoSuchFileException이 발생합니다. 그 경우 LLM 호출을 모두 마친 뒤에 러너가 실패하고, 게이트가 자기 IO 오류로 push를 막습니다. Line 173-176의 방어 방식과 동일하게 디렉터리를 먼저 생성하세요.🐛 제안 수정
String report = out.toString(); System.out.println(report); - Files.writeString(Path.of("build/reviewloop-run.txt"), report); + Path reportFile = Path.of("build/reviewloop-run.txt"); + Files.createDirectories(reportFile.getParent()); + Files.writeString(reportFile, report);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.java` around lines 116 - 118, Before the Files.writeString call in ReviewLoopRunner, create the parent directory for build/reviewloop-run.txt using the same defensive directory-creation approach already used around lines 173-176. Ensure report generation and output remain unchanged while preventing failure when the build directory does not exist.src/main/java/com/module06/backend/reviewloop/judge/OptionPanel.java-6-9 (1)
6-9: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
OptionPanel의 불변성을 보장하고 빈 응답 경로를 분리하세요.
options를List.copyOf(options)로 방어 복사하세요. 정상 패널에서는recommendation.pick()이 옵션의letter와 일치하는지 검증하세요. 단,GeminiOptionPanelAdapter.parsePanel은 빈 옵션과 빈pick을 응답 없음 상태로 생성하므로, 이 상태를 별도로 표현하거나 해당 경로에서OptionPanel을 생성하지 않도록 수정해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/OptionPanel.java` around lines 6 - 9, OptionPanel의 options를 List.copyOf(options)로 방어 복사해 불변성을 보장하세요. 정상 패널 생성 시 recommendation.pick()이 options의 PanelOption letter와 일치하는지 검증하고, GeminiOptionPanelAdapter.parsePanel의 빈 options·빈 pick 응답 없음 경로는 별도 상태로 표현하거나 OptionPanel 생성을 건너뛰도록 수정하세요.
🧹 Nitpick comments (15)
src/main/java/com/module06/backend/reviewloop/judge/ReviewLessonRecorder.java (1)
52-69: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win시스템 기본 시계를 직접 사용해 팀 컨벤션과 다르다.
Line 68에서
LocalDateTime.now()를 직접 호출한다.ReviewRunner의 주석(Line 15)은 "시각은 팀 컨벤션(ClockConfig)에 맞춰 주입된 Clock을 쓴다"고 명시한다. 이 파일은 그 컨벤션을 따르지 않는다.
lessons.jsonl은 팀 전체가 공유하고 git으로 추적하는 파일이다(ReviewLoopPaths.java Line 10 참고). 기여자마다 로컬 시스템 시간대가 다르면 기록된 시각이 서로 어긋나 리포트의 시간 순서 해석이 혼란스러워진다.
Instant.now()(UTC 기준) 사용 또는Clock주입으로 통일하는 것을 제안한다. 이 저장소의 CONV_001 규칙(재발명 금지)이 정확히 이런 패턴을 겨냥한다는 점도 참고한다.♻️ 제안하는 수정
- return new Lesson(LocalDateTime.now().toString(), rule.strip(), kind, note.strip()); + return new Lesson(java.time.Instant.now().toString(), rule.strip(), kind, note.strip());🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewLessonRecorder.java` around lines 52 - 69, Update the timestamp creation in ReviewLessonRecorder.parse to follow the repository’s ClockConfig convention instead of calling LocalDateTime.now() directly. Reuse the existing injected or configured Clock, or use Instant.now() in UTC if that is the established project pattern, while preserving the Lesson construction and argument parsing behavior.src/main/java/com/module06/backend/reviewloop/judge/ReviewReport.java (1)
47-55: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
humanNote에 개행 문자가 있으면 목록 서식이 깨질 수 있다.
l.humanNote()는ReviewLessonRecorder의--note-file옵션을 통해 여러 줄 텍스트를 담을 수 있다(strip()은 선행·후행 공백만 제거하고 내부 개행은 유지한다). 여러 줄 노트가 하나의 마크다운 불릿 항목(Line 52-54)에 그대로 삽입되면, 후속 줄이 불릿 접두사 없이 렌더링되어 리포트 가독성이 떨어진다.내부 개행을 공백으로 치환하는 것을 제안한다.
♻️ 제안하는 수정
for (Lesson l : lessons) { md.append("- **[").append(l.kind()).append("]** `").append(l.ruleId()) - .append("` — ").append(l.humanNote()).append('\n'); + .append("` — ").append(l.humanNote().replace("\n", " ").replace("\r", "")).append('\n'); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewReport.java` around lines 47 - 55, Update the lesson rendering loop in ReviewReport so each l.humanNote() is normalized by replacing internal newline characters with spaces before appending it to the Markdown list item. Preserve the existing note content and formatting for single-line notes.src/main/java/com/module06/backend/reviewloop/judge/RepeatedPatternDetector.java (1)
23-31: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win동일 count에서 정렬 순서를 고정하세요.
counts는HashMap입니다. count가 같은 규칙이 둘 이상이면 결과 순서가 실행마다 달라질 수 있습니다. 클래스 주석은 결정론을 명시하고,ReviewLoopCli는 이 순서를 그대로 화면에 출력합니다.ruleId를 2차 정렬 키로 추가하면 순서가 고정됩니다.♻️ 제안 리팩터
return counts.entrySet().stream() .filter(e -> e.getValue() >= threshold) .map(e -> new Alert(e.getKey(), e.getValue())) - .sorted((a, b) -> Long.compare(b.count(), a.count())) + .sorted(Comparator.comparingLong(Alert::count).reversed() + .thenComparing(Alert::ruleId)) .toList();
java.util.Comparator임포트를 추가하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/RepeatedPatternDetector.java` around lines 23 - 31, Update RepeatedPatternDetector.detect by adding ruleId as a secondary ascending sort key after descending count, using Comparator as needed, so alerts with equal counts always have deterministic ordering.scripts/review-fix.sh (1)
17-20: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win리포지토리 루트로 이동한 뒤
./gradlew를 호출하세요.Line 31과 Line 41은 상대 경로
./gradlew와$DIR/review-fix-apply.sh를 사용합니다. 사용자가 하위 디렉터리에서 이 스크립트를 실행하면./gradlew를 찾지 못합니다.scripts/review-score-domains.sh(Line 22-23)는 루트로 이동합니다. 동일한 방식을 적용하면 동작이 일관됩니다.♻️ 제안 리팩터
DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$DIR/.." && pwd)" +cd "$ROOT" || exit 1 TARGET_ARGS="$*"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/review-fix.sh` around lines 17 - 20, Update scripts/review-fix.sh to change into the repository root before invoking the relative ./gradlew and $DIR/review-fix-apply.sh commands, following the existing root-resolution approach used by review-score-domains.sh while preserving the script directory references.scripts/review-score-domains.sh (1)
80-98: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
eval대신 파일이나read로 집계 값을 받으세요.Line 81은
awk출력을eval로 재파싱합니다. 현재awk의printf포맷은 모두 숫자로 강제 변환하므로 즉각적인 주입 경로는 없습니다. 그러나$out은 LLM 판정 출력이며 신뢰 경계 밖입니다. 포맷이 나중에%s로 바뀌면 임의 명령 실행이 가능해집니다.read로 값을 받으면 이 위험이 사라집니다. Shellcheck의 SC2154 경고(Line 96, 104)도 함께 해소됩니다.♻️ 제안 리팩터
- eval "$(awk ' + read -r files avg low nr crit fnd < <(awk ' / score / { files++ for (i = 1; i <= NF; i++) { if ($i == "score") { s = $(i+1) + 0; sum += s; if (s < 80) low++ } if ($i == "findings") { fnd += $(i+1) + 0 } } if ($0 ~ /NEEDS_REVISION/) nr++ if ($0 ~ /AWAITING_HUMAN/) crit++ } END { - printf "files=%d avg=%s low=%d nr=%d crit=%d fnd=%d\n", + printf "%d %s %d %d %d %d\n", files, (files ? sprintf("%.1f", sum / files) : 0), low, nr, crit, fnd - }' "$out")" + }' "$out")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/review-score-domains.sh` around lines 80 - 98, Replace the eval-based assignment around the awk aggregation with safe read-based parsing of its fixed output, assigning files, avg, low, nr, crit, and fnd directly without executing command text. Preserve the existing numeric aggregation and zero-files handling, and ensure the change resolves the SC2154 warnings for these variables.Source: Linters/SAST tools
src/test/java/com/module06/backend/reviewloop/judge/GeminiAutoFixLoopLiveTest.java (1)
28-65: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win세 개의 Gemini 라이브 테스트에 타임아웃을 추가하십시오. 세 파일 모두
@EnabledIfEnvironmentVariable(named = "GEMINI_API_KEY", ...)로 게이트되지만, 실제 외부 API 호출에 시간 제한을 두지 않는다. API가 응답하지 않으면 라이브 게이트 잡이 무한정 대기할 수 있다.
src/test/java/com/module06/backend/reviewloop/judge/GeminiAutoFixLoopLiveTest.java#L28-L65:autoFixLoopEndToEnd에@Timeout애노테이션을 추가하십시오.AutoFixRunner.run은 예산 소진까지 반복하므로 API 무응답 시 대기 시간이 특히 길어질 수 있습니다.src/test/java/com/module06/backend/reviewloop/judge/GeminiOptionPanelLiveTest.java#L19-L45:generatesPanel에@Timeout애노테이션을 추가하십시오.src/test/java/com/module06/backend/reviewloop/judge/GeminiReviewLoopLiveTest.java#L25-L57:fullLoopEndToEnd에@Timeout애노테이션을 추가하십시오.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/module06/backend/reviewloop/judge/GeminiAutoFixLoopLiveTest.java` around lines 28 - 65, 세 개의 Gemini 라이브 테스트에 외부 API 무응답을 제한할 수 있는 유한한 JUnit `@Timeout을` 추가하십시오. src/test/java/com/module06/backend/reviewloop/judge/GeminiAutoFixLoopLiveTest.java:28-65의 autoFixLoopEndToEnd, src/test/java/com/module06/backend/reviewloop/judge/GeminiOptionPanelLiveTest.java:19-45의 generatesPanel, src/test/java/com/module06/backend/reviewloop/judge/GeminiReviewLoopLiveTest.java:25-57의 fullLoopEndToEnd에 적용하고, 필요한 Timeout import와 모든 테스트에 일관된 적절한 제한 시간을 사용하십시오.src/test/java/com/module06/backend/reviewloop/judge/OptionPanelTest.java (1)
38-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win테스트가 라우팅을 검증하지 않습니다.
selectedActionRoutes는PanelOption의action()필드 값만 확인합니다. 라우팅 동작은 실행하지 않습니다.DisplayName은 "다음 처리가 라우팅된다"라고 설명하므로 테스트 내용과 설명이 일치하지 않습니다. 선택한PanelAction을 처리 경로에 넣고 결과를 확인하도록 보강하거나, 설명을 필드 검증으로 정정하십시오.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/module06/backend/reviewloop/judge/OptionPanelTest.java` around lines 38 - 44, Update selectedActionRoutes so it either passes the chosen PanelAction through the actual routing/handling path and asserts the resulting behavior, or rename its DisplayName to accurately describe validating the PanelOption action field; prefer exercising the routing path if the relevant handler is available.src/main/java/com/module06/backend/reviewloop/judge/AutoLoopRunner.java (1)
84-107: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value대상 파일 경로에 상한 필터가 없습니다.
loadTargets는 목록의 각 줄을 그대로Path로 변환합니다..java확장자와 존재 여부만 검사합니다. 목록 파일이 예상 밖 경로(예:../상위 경로, 저장소 밖 절대 경로)를 담으면 루프가 저장소 밖 파일을 수정할 수 있습니다. 현재는 스크립트가git diff결과로 목록을 만들므로 실제 위험은 낮습니다. 방어 차원에서 저장소 루트 하위 경로만 허용하는 검사를 추가하십시오.♻️ 제안: 저장소 루트 하위 경로만 허용
+ Path root = Path.of("").toAbsolutePath().normalize(); List<Path> files = new ArrayList<>(); for (String raw : Files.readAllLines(list)) { String s = raw.replace("", "").trim(); // BOM/공백 방어 if (s.isBlank() || !s.endsWith(".java")) { continue; } Path p = Path.of(s); - if (Files.exists(p)) { + if (!p.toAbsolutePath().normalize().startsWith(root)) { + System.out.println("[autoloop] 저장소 밖 경로 스킵: " + s); + continue; + } + if (Files.exists(p)) { files.add(p); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/AutoLoopRunner.java` around lines 84 - 107, Update loadTargets to resolve each listed path against the repository root and accept it only when its normalized absolute path remains within that root; skip absolute paths and traversal entries that escape the root while preserving the existing .java, existence, and maxFiles filtering.src/test/java/com/module06/backend/reviewloop/judge/ReviewLessonRecorderTest.java (1)
60-75: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
--kind누락과MISSED경로가 없습니다.현재 테스트는
CONFIRMED,FALSE_POSITIVE, 잘못된 kind만 다룹니다.ReviewLessonRecorder.parse는--kind가 없으면LessonKind.valueOf("")로 예외를 던집니다. 그 경로와MISSEDkind를 추가로 검증하면 CLI 계약이 완결됩니다.💚 제안: 누락 케이스 추가
+ `@Test` + `@DisplayName`("--kind 누락은 거부한다") + void rejectsMissingKind() { + assertThatThrownBy(() -> ReviewLessonRecorder.parse(new String[]{ + "--rule", "CONV_001", "--note", "x"})) + .isInstanceOf(IllegalArgumentException.class); + } + + `@Test` + `@DisplayName`("MISSED도 유효한 kind다") + void acceptsMissedKind() throws IOException { + Lesson lesson = ReviewLessonRecorder.parse(new String[]{ + "--rule", "CONV_001", "--kind", "MISSED", "--note", "x"}); + assertThat(lesson.kind()).isEqualTo(LessonKind.MISSED); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/module06/backend/reviewloop/judge/ReviewLessonRecorderTest.java` around lines 60 - 75, Review the tests around ReviewLessonRecorder.parse and add coverage for the MISSED kind, asserting it produces LessonKind.MISSED, plus a case where --kind is omitted, asserting the parser rejects the input with the expected IllegalArgumentException and message. Preserve the existing CONFIRMED and unknown-kind coverage.src/main/java/com/module06/backend/reviewloop/judge/GeminiLessonDraftAdapter.java (1)
51-56: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win일시적 오류와 빈 응답을 구분하지 않습니다.
라인 52는 모든 비-2xx 응답을 즉시 예외로 만듭니다. 429(rate limit)와 5xx는 재시도로 회복 가능합니다. 또한
parseNote는 응답에parts가 없으면 빈 문자열을 반환합니다. 그 결과 빈humanNote를 가진Lesson이 승인 경로로 들어갑니다. 두 경우를 명시적으로 다루십시오.
- 429/503에는 짧은 지수 백오프 재시도를 적용하십시오.
- 빈 노트에는 예외를 던지거나 호출자가 판별할 수 있는 값을 반환하십시오.
♻️ 제안: 빈 노트 차단
String note = parseNote(response.body()); + if (note.isBlank()) { + throw new IllegalStateException("Gemini 교훈 초안이 비어 있습니다."); + } return new Lesson("(draft)", finding.ruleId(), kind, note);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/GeminiLessonDraftAdapter.java` around lines 51 - 56, 응답 처리에서 429 및 503을 일시적 오류로 분류하고 짧은 지수 백오프로 제한된 재시도를 적용하십시오. 그 외 비-2xx 응답은 기존처럼 명확한 예외로 처리하고, parseNote 결과가 빈 문자열이면 Lesson을 생성하지 말고 예외를 던지거나 호출자가 실패를 판별할 수 있는 값으로 반환하십시오.src/main/java/com/module06/backend/reviewloop/judge/GeminiCodeFixerAdapter.java (1)
89-97: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
parseFixedCode의 폴백 동작은 안전하지만, 잘린 응답을 감지하지 못합니다.
finishReason이MAX_TOKENS인 경우에도 이 메서드는 잘린text를 그대로 반환합니다(빈 문자열이 아니므로 Line 96의isBlank()검사를 통과함). 잘린 코드가 컴파일 실패로 이어지면VerifiedFixer의 롤백이 이를 잡아낼 가능성이 높지만, 근본 원인은 위maxOutputTokens미설정입니다. 위 코멘트의 수정과 함께, 필요하다면 응답의finishReason을 확인해MAX_TOKENS일 때 원본 코드로 폴백하는 방어 로직을 추가하는 것을 고려하십시오.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/GeminiCodeFixerAdapter.java` around lines 89 - 97, Update parseFixedCode to inspect the candidate response’s finishReason before returning generated text, and return fallback when it is MAX_TOKENS. Preserve the existing empty or blank-text fallback behavior and only apply this additional guard to truncated responses.src/main/java/com/module06/backend/reviewloop/judge/DriverBudget.java (1)
62-70: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueload-apply-save 사이에 원자성 보장이 없습니다.
main()은load(),applied(),save()를 순차 실행하지만 파일 잠금이 없습니다. 두 프로세스가 동시에 실행되면 한쪽의 증가분이 유실될 수 있습니다(TOCTOU). 이 도구는 드라이버가 라운드마다 순차 호출하는 설계라 실제 발생 가능성은 낮지만, 예산 추적은 무한 루프 방지가 목적이므로 견고성을 높이려면FileLock으로 read-modify-write 구간을 보호하는 것을 고려해 주십시오.Also applies to: 84-97, 99-102
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/DriverBudget.java` around lines 62 - 70, Protect the entire load-apply-save read-modify-write sequence in main() with an exclusive FileLock on the budget state file, keeping the lock held through load(), applied(), and save(). Ensure the lock is released reliably via the existing Java resource-management pattern while preserving rendering and exhausted-status behavior.src/main/java/com/module06/backend/reviewloop/judge/ClaudeJudgeAdapter.java (1)
77-91: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
toUpperCase에Locale.ROOT를 지정하세요.기본 로케일 의존 변환은 터키어 로케일에서
i를İ로 바꿉니다. 그 경우"high"입력이 파싱에 실패하고 조용히Confidence.LOW로 떨어집니다.Locale.ROOT를 쓰면 결과가 로케일과 무관해집니다.♻️ 제안 수정
+import java.util.Locale; + private Severity parseSeverity(String raw) { try { - return Severity.valueOf(raw == null ? "" : raw.trim().toUpperCase()); + return Severity.valueOf(raw == null ? "" : raw.trim().toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException e) { return Severity.MINOR; // 알 수 없으면 보수적으로 MINOR (Critical 오판 방지) } } private Confidence parseConfidence(String raw) { try { - return Confidence.valueOf(raw == null ? "" : raw.trim().toUpperCase()); + return Confidence.valueOf(raw == null ? "" : raw.trim().toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException e) { return Confidence.LOW; } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/ClaudeJudgeAdapter.java` around lines 77 - 91, Update the toUpperCase calls in parseSeverity and parseConfidence to use Locale.ROOT, ensuring normalized enum inputs parse consistently regardless of the JVM’s default locale.src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.java (1)
86-108: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win파일별 리뷰 실패를 격리하세요.
loop.review는 LLM 호출을 포함합니다. 한 파일에서 예외가 발생하면 루프가 중단되고 나머지 파일은 리뷰되지 않습니다. pre-push 훅에서는 일시적 API 오류가 push 차단으로 이어집니다. 파일별로 예외를 잡고, 실패를 리포트에 기록한 뒤 다음 파일을 계속 처리하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.java` around lines 86 - 108, 각 파일의 ReviewLoopRunner 처리에서 loop.review 호출과 후속 결과 처리를 파일별 try-catch로 감싸세요. 예외가 발생한 파일은 실패 내용을 out 리포트와 audit에 기록하고 blocked 상태를 적절히 반영한 뒤, 예외를 전파하지 말고 다음 targets 항목으로 계속 진행하도록 수정하세요.src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java (1)
26-163: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winGemini REST 호출 골격이 두 어댑터에서 중복된다.
두 파일은
HttpClient/ObjectMapper필드, 생성자와 API 키 검증, 엔드포인트 조립, 응답 코드 확인과 예외 래핑 로직이 거의 동일하다. 프롬프트 내용과 응답 스키마, 결과 파싱만 다르다. 이후 계층에 있는GeminiLessonDraftAdapter,GeminiCodeFixerAdapter도 같은 패턴을 따를 가능성이 높다.
src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java#L26-L163: 공통 HTTP 전송·오류 처리 로직을 추상 기반 클래스나 헬퍼로 추출하고, 이 클래스는 프롬프트 생성과 응답 파싱만 구현하도록 리팩터링한다.src/main/java/com/module06/backend/reviewloop/judge/GeminiOptionPanelAdapter.java#L20-L134: 동일한 공통 기반을 사용하도록 리팩터링한다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java` around lines 26 - 163, Extract the duplicated Gemini REST setup and request/response error handling from GeminiJudgeAdapter and GeminiOptionPanelAdapter into a shared abstract base class or helper, including HttpClient/ObjectMapper state, API-key validation, endpoint construction, status checks, and exception wrapping. Refactor GeminiJudgeAdapter at src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java:26-163 and GeminiOptionPanelAdapter at src/main/java/com/module06/backend/reviewloop/judge/GeminiOptionPanelAdapter.java:20-134 to reuse it while retaining each adapter’s prompt construction, response schema, and result parsing behavior; structure it for reuse by GeminiLessonDraftAdapter and GeminiCodeFixerAdapter.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/gate1-semgrep.yml:
- Around line 16-17: Pin the container image in the workflow’s container
configuration to a verified immutable Semgrep image digest instead of the
floating semgrep/semgrep reference, and document the procedure for validating
and updating that digest.
- Around line 19-22: Disable checkout credential persistence by adding
persist-credentials: false to the checkout step in
.github/workflows/gate1-semgrep.yml lines 19-22 and
.github/workflows/gate2-judge.yml lines 27-28. In
.github/workflows/gate2-judge.yml lines 70-73 and 117-118, stop exposing
GEMINI_API_KEY while executing PR-controlled ./gradlew code, and arrange for the
secret to be used only by trusted workflow code.
In @.github/workflows/gate2-judge.yml:
- Around line 17-20: Update the workflow permissions in gate2-judge.yml to
remove the global pull-requests: write permission, keeping only contents: read
at workflow scope. If test-result publishing requires checks: write, move that
permission to the gate2-deterministic job rather than retaining it globally.
- Around line 101-108: Remove direct repository-secret injection from
gate2-review at .github/workflows/gate2-judge.yml lines 101-108 and run its
key-using step only after approval from a protected GitHub Environment secret;
keep the pre-approval path deterministic and key-free. Apply the same
protected-environment boundary to gate2-live-judge at
.github/workflows/gate2-judge.yml lines 135-141, without relying on
continue-on-error to protect the secret.
In `@scripts/review-autoloop.sh`:
- Around line 37-40: Update the CHANGED path in the review-autoloop script to
use the repository-managed path returned by git rev-parse --git-path instead of
assuming .git is a directory, so linked worktrees can write the changed-file
list successfully. Keep the existing git diff filtering and line-count logic
unchanged.
In `@scripts/review-fix-apply.sh`:
- Around line 47-58: Replace the Bash 4-only mapfile usage in the
findings-processing while loop with a Bash 3.2-compatible method that reliably
populates opts from options_for, including when no options are returned.
Preserve the existing iteration and display behavior in the "${opts[@]}" loop so
set -u cannot trigger an unbound-variable failure.
In `@scripts/review-score-domains.sh`:
- Line 29: Update scripts/review-score-domains.sh lines 29-29 by changing
DOMAIN_ROOT to src/main/java/com/module06/backend/domain, and update
scripts/review-fix.sh lines 10-10 by changing the usage example’s --path value
to src/main/java/com/module06/backend/domain/cart.
In
`@src/main/java/com/module06/backend/reviewloop/judge/AutoLoopOrchestrator.java`:
- Around line 78-99: Wrap the AutoFixRunner.run invocation in the
file-processing flow with try/catch so any exception restores the file from
original before being rethrown. Ensure Files.writeString(abs, original) executes
on failure, while preserving the existing changed-result handling for successful
runs.
In `@src/main/java/com/module06/backend/reviewloop/judge/EvidenceValidator.java`:
- Around line 28-30: Update EvidenceValidator’s target-path validation to
normalize the resolved finding path and canonical repository root, rejecting any
target whose actual path falls outside the repository, including absolute paths,
../ traversal, and symlinked files escaping the root. Preserve regular-file
validation for in-repository targets, remove out-of-scope findings through the
existing validation flow, and add tests covering all three path cases.
In
`@src/main/java/com/module06/backend/reviewloop/judge/GeminiCodeFixerAdapter.java`:
- Around line 80-87: Update the response-handling flow around parseFixedCode to
inspect Gemini’s finishReason before returning generated code. Treat token-limit
termination (MAX_TOKENS) as a failed or rejected response rather than returning
the potentially truncated result, while preserving the existing behavior for
normally completed responses.
In `@src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java`:
- Around line 115-120: Remove the explicit temperature=0 setting from generation
configuration in GeminiJudgeAdapter.java (lines 115-120) and
GeminiOptionPanelAdapter.java (lines 117-121), while preserving the existing
systemInstruction and explicit JSON response rules. In GeminiModels.java (lines
14-20), restrict GEMINI_MODEL to supported models or add model-specific
regression tests covering the allowed configuration.
In `@src/main/java/com/module06/backend/reviewloop/judge/JudgePromptBuilder.java`:
- Around line 35-46: Update the lesson prompt construction around the loop in
JudgePromptBuilder to apply explicit per-rule recent-lesson limits and a
deterministic overall character or token budget before appending to the policy
string. Exclude CONFIRMED lessons, then deterministically select, order, and
truncate remaining lessons so the generated prompt always stays within the
configured bounds. Add deterministic tests covering selection order, per-rule
limits, and total-budget truncation.
In `@src/main/java/com/module06/backend/reviewloop/judge/KnowledgeStore.java`:
- Around line 32-44: Update KnowledgeStore.lessons() in
src/main/java/com/module06/backend/reviewloop/judge/KnowledgeStore.java lines
32-44 to wrap each mapper.readValue(line, Lesson.class) call in a line-scoped
try-catch, warn on parsing failure, and skip only the invalid line. Apply the
same handling to mapper.readValue(line, AuditRecord.class) inside
ReviewReport.fromFiles() in
src/main/java/com/module06/backend/reviewloop/judge/ReviewReport.java lines
59-72, preserving successful records and preventing one malformed JSONL line
from aborting the entire read.
In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.java`:
- Around line 42-52: Update the early-return branches in ReviewLoopRunner for
empty targets and missing GEMINI_API_KEY to initialize findingsOut as an empty
findings file when it is configured, before returning. Ensure both paths clear
stale findings while preserving the existing normal writeFindings flow.
In `@src/main/java/com/module06/backend/reviewloop/judge/RuleCatalog.java`:
- Around line 213-219: Update parseSeverity so an unknown severity never
silently falls back to Severity.MINOR; instead, fail rule loading by propagating
an appropriate exception, or use the safer Severity.CRITICAL fallback with a
warning. Preserve normal parsing for valid severity values and ensure
misspellings such as “Critial” cannot bypass the human-approval path.
---
Minor comments:
In `@review-loop/AUTOLOOP_DESIGN.md`:
- Around line 28-60: 문서의 두 AutoLoopRunner 참조가 현재 패키지명을 사용하도록 수정하세요.
`com.wanted.backend.reviewloop.judge.AutoLoopRunner#main`과 Gradle `mainClass` 값을
모두 `com.module06.backend.reviewloop.judge.AutoLoopRunner`로 변경하고, 나머지 배선과 옵션은
유지하세요.
In `@review-loop/knowledge/README.md`:
- Around line 3-13:
ReviewLoopCliの教訓保存先をReviewLoopPaths.LESSONSに統一し、review-loop/knowledge/lessons.jsonlへ保存するよう更新してください。あわせてREADMEのlogs/に関する説明と.gitignoreの設定を確認し、review-loop/logs/を揮発性ログとして無視する規則を追加して記述と実際の挙動を一致させてください。
In `@review-loop/UNIFIED_DESIGN.md`:
- Around line 158-169: Update the review workflow steps 5 and 7 to invoke the
existing scripts/review-verify.sh directly instead of the unavailable ./gradlew
reviewVerify wrapper, preserving the --files-from <changed> argument and adding
--with-test for final verification. Ensure the documented command matches the
script’s supported options.
In `@scripts/review-fix.sh`:
- Line 10: Update the example command comment in review-fix.sh to replace the
outdated com/wanted/backend package path with the migrated com/module06/backend
path, while preserving the existing cart domain example.
In `@src/main/java/com/module06/backend/reviewloop/judge/AutoFixRunner.java`:
- Around line 41-63: Update AutoFixRunner.run so an already-exhausted
ReviewBudget cannot produce an AutoFixResult with a null verdict: either
reject/avoid the call before entering the loop or return an explicit terminal
verdict. Ensure every return path, including the final path after the loop,
provides a non-null finalVerdict rather than adding only a null-return guard.
In
`@src/main/java/com/module06/backend/reviewloop/judge/CompileVerification.java`:
- Around line 36-52: Update CompileVerification.verify to always delete the
temporary outDir and its generated class files after compilation completes,
including when compilation or verification fails. Use guaranteed cleanup around
the existing compiler task while preserving the current VerifyResult and
diagnostic handling.
In `@src/main/java/com/module06/backend/reviewloop/judge/DriverBudget.java`:
- Around line 31-32: DriverBudget의 STATE_FILE 및 HEAD_FILE을 고정된 .git 하위 경로로 만들지
말고, .git이 디렉터리인지 파일인지 확인해 실제 Git 디렉터리를 해석하도록 수정하십시오. .git 파일의 gitdir 경로를 읽어
worktree와 submodule에서도 HEAD 조회 및 상태 저장이 동일하게 동작하게 하며, 일반 저장소의 디렉터리 형태도 유지하십시오.
In `@src/main/java/com/module06/backend/reviewloop/judge/OptionPanel.java`:
- Around line 6-9: OptionPanel의 options를 List.copyOf(options)로 방어 복사해 불변성을
보장하세요. 정상 패널 생성 시 recommendation.pick()이 options의 PanelOption letter와 일치하는지
검증하고, GeminiOptionPanelAdapter.parsePanel의 빈 options·빈 pick 응답 없음 경로는 별도 상태로
표현하거나 OptionPanel 생성을 건너뛰도록 수정하세요.
In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopCli.java`:
- Around line 78-81: Update the file-writing flow around Files.writeString in
ReviewLoopCli so the parent build directory is created or otherwise guaranteed
to exist before writing build/review-cli-screen.txt. Match the existing logsDir
directory-initialization approach, while preserving the current screen output
and file contents.
In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.java`:
- Around line 116-118: Before the Files.writeString call in ReviewLoopRunner,
create the parent directory for build/reviewloop-run.txt using the same
defensive directory-creation approach already used around lines 173-176. Ensure
report generation and output remain unchanged while preventing failure when the
build directory does not exist.
In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewRunner.java`:
- Around line 34-58: Update ReviewRunner.run and its AuditSummary contract to
explicitly handle an initially exhausted ReviewBudget: return an AuditSummary
with finalVerdict() null and roundsUsed() 0 without invoking reviewer.review or
consuming budget. Ensure the minimum-round budget configuration is rejected when
zero rounds are disallowed; otherwise document and handle the nullable verdict
in consumers and add regression coverage.
In
`@src/test/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapterLiveTest.java`:
- Around line 38-40: Update the findings-file write in
GeminiJudgeAdapterLiveTest to create the parent directory before calling
writeString, matching ReviewLoopRunner.writeFindings behavior so direct or IDE
execution succeeds when build is absent. Reuse the existing Files and Path
imports instead of fully qualified names.
---
Nitpick comments:
In `@scripts/review-fix.sh`:
- Around line 17-20: Update scripts/review-fix.sh to change into the repository
root before invoking the relative ./gradlew and $DIR/review-fix-apply.sh
commands, following the existing root-resolution approach used by
review-score-domains.sh while preserving the script directory references.
In `@scripts/review-score-domains.sh`:
- Around line 80-98: Replace the eval-based assignment around the awk
aggregation with safe read-based parsing of its fixed output, assigning files,
avg, low, nr, crit, and fnd directly without executing command text. Preserve
the existing numeric aggregation and zero-files handling, and ensure the change
resolves the SC2154 warnings for these variables.
In `@src/main/java/com/module06/backend/reviewloop/judge/AutoLoopRunner.java`:
- Around line 84-107: Update loadTargets to resolve each listed path against the
repository root and accept it only when its normalized absolute path remains
within that root; skip absolute paths and traversal entries that escape the root
while preserving the existing .java, existence, and maxFiles filtering.
In `@src/main/java/com/module06/backend/reviewloop/judge/ClaudeJudgeAdapter.java`:
- Around line 77-91: Update the toUpperCase calls in parseSeverity and
parseConfidence to use Locale.ROOT, ensuring normalized enum inputs parse
consistently regardless of the JVM’s default locale.
In `@src/main/java/com/module06/backend/reviewloop/judge/DriverBudget.java`:
- Around line 62-70: Protect the entire load-apply-save read-modify-write
sequence in main() with an exclusive FileLock on the budget state file, keeping
the lock held through load(), applied(), and save(). Ensure the lock is released
reliably via the existing Java resource-management pattern while preserving
rendering and exhausted-status behavior.
In
`@src/main/java/com/module06/backend/reviewloop/judge/GeminiCodeFixerAdapter.java`:
- Around line 89-97: Update parseFixedCode to inspect the candidate response’s
finishReason before returning generated text, and return fallback when it is
MAX_TOKENS. Preserve the existing empty or blank-text fallback behavior and only
apply this additional guard to truncated responses.
In `@src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java`:
- Around line 26-163: Extract the duplicated Gemini REST setup and
request/response error handling from GeminiJudgeAdapter and
GeminiOptionPanelAdapter into a shared abstract base class or helper, including
HttpClient/ObjectMapper state, API-key validation, endpoint construction, status
checks, and exception wrapping. Refactor GeminiJudgeAdapter at
src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java:26-163
and GeminiOptionPanelAdapter at
src/main/java/com/module06/backend/reviewloop/judge/GeminiOptionPanelAdapter.java:20-134
to reuse it while retaining each adapter’s prompt construction, response schema,
and result parsing behavior; structure it for reuse by GeminiLessonDraftAdapter
and GeminiCodeFixerAdapter.
In
`@src/main/java/com/module06/backend/reviewloop/judge/GeminiLessonDraftAdapter.java`:
- Around line 51-56: 응답 처리에서 429 및 503을 일시적 오류로 분류하고 짧은 지수 백오프로 제한된 재시도를 적용하십시오.
그 외 비-2xx 응답은 기존처럼 명확한 예외로 처리하고, parseNote 결과가 빈 문자열이면 Lesson을 생성하지 말고 예외를 던지거나
호출자가 실패를 판별할 수 있는 값으로 반환하십시오.
In
`@src/main/java/com/module06/backend/reviewloop/judge/RepeatedPatternDetector.java`:
- Around line 23-31: Update RepeatedPatternDetector.detect by adding ruleId as a
secondary ascending sort key after descending count, using Comparator as needed,
so alerts with equal counts always have deterministic ordering.
In
`@src/main/java/com/module06/backend/reviewloop/judge/ReviewLessonRecorder.java`:
- Around line 52-69: Update the timestamp creation in ReviewLessonRecorder.parse
to follow the repository’s ClockConfig convention instead of calling
LocalDateTime.now() directly. Reuse the existing injected or configured Clock,
or use Instant.now() in UTC if that is the established project pattern, while
preserving the Lesson construction and argument parsing behavior.
In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.java`:
- Around line 86-108: 각 파일의 ReviewLoopRunner 처리에서 loop.review 호출과 후속 결과 처리를 파일별
try-catch로 감싸세요. 예외가 발생한 파일은 실패 내용을 out 리포트와 audit에 기록하고 blocked 상태를 적절히 반영한 뒤,
예외를 전파하지 말고 다음 targets 항목으로 계속 진행하도록 수정하세요.
In `@src/main/java/com/module06/backend/reviewloop/judge/ReviewReport.java`:
- Around line 47-55: Update the lesson rendering loop in ReviewReport so each
l.humanNote() is normalized by replacing internal newline characters with spaces
before appending it to the Markdown list item. Preserve the existing note
content and formatting for single-line notes.
In
`@src/test/java/com/module06/backend/reviewloop/judge/GeminiAutoFixLoopLiveTest.java`:
- Around line 28-65: 세 개의 Gemini 라이브 테스트에 외부 API 무응답을 제한할 수 있는 유한한 JUnit
`@Timeout을` 추가하십시오.
src/test/java/com/module06/backend/reviewloop/judge/GeminiAutoFixLoopLiveTest.java:28-65의
autoFixLoopEndToEnd,
src/test/java/com/module06/backend/reviewloop/judge/GeminiOptionPanelLiveTest.java:19-45의
generatesPanel,
src/test/java/com/module06/backend/reviewloop/judge/GeminiReviewLoopLiveTest.java:25-57의
fullLoopEndToEnd에 적용하고, 필요한 Timeout import와 모든 테스트에 일관된 적절한 제한 시간을 사용하십시오.
In `@src/test/java/com/module06/backend/reviewloop/judge/OptionPanelTest.java`:
- Around line 38-44: Update selectedActionRoutes so it either passes the chosen
PanelAction through the actual routing/handling path and asserts the resulting
behavior, or rename its DisplayName to accurately describe validating the
PanelOption action field; prefer exercising the routing path if the relevant
handler is available.
In
`@src/test/java/com/module06/backend/reviewloop/judge/ReviewLessonRecorderTest.java`:
- Around line 60-75: Review the tests around ReviewLessonRecorder.parse and add
coverage for the MISSED kind, asserting it produces LessonKind.MISSED, plus a
case where --kind is omitted, asserting the parser rejects the input with the
expected IllegalArgumentException and message. Preserve the existing CONFIRMED
and unknown-kind coverage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f041290f-2b36-4a53-a38b-2415f15c0cce
📒 Files selected for processing (104)
.githooks/pre-push.github/workflows/gate1-semgrep.yml.github/workflows/gate2-judge.ymlbuild.gradlereview-loop/AUTOLOOP_DESIGN.mdreview-loop/DRIVER.mdreview-loop/UNIFIED_DESIGN.mdreview-loop/golden/conv001/ReinventedClock.java.txtreview-loop/golden/perf001/QuizListN1.java.txtreview-loop/knowledge/README.mdreview-loop/knowledge/lessons.jsonlreview-loop/logs/error_log.jsonlreview-loop/rules.yamlreview-loop/semgrep/query.ymlscripts/review-autoloop.shscripts/review-fix-apply.shscripts/review-fix.shscripts/review-score-domains.shscripts/review-verify.shsrc/main/java/com/module06/backend/reviewloop/judge/AuditLogWriter.javasrc/main/java/com/module06/backend/reviewloop/judge/AuditRecord.javasrc/main/java/com/module06/backend/reviewloop/judge/AuditSummary.javasrc/main/java/com/module06/backend/reviewloop/judge/AutoFixResult.javasrc/main/java/com/module06/backend/reviewloop/judge/AutoFixRunner.javasrc/main/java/com/module06/backend/reviewloop/judge/AutoLoopOrchestrator.javasrc/main/java/com/module06/backend/reviewloop/judge/AutoLoopRunner.javasrc/main/java/com/module06/backend/reviewloop/judge/ClaudeJudgeAdapter.javasrc/main/java/com/module06/backend/reviewloop/judge/CliArgs.javasrc/main/java/com/module06/backend/reviewloop/judge/CodeFixerPort.javasrc/main/java/com/module06/backend/reviewloop/judge/CompileVerification.javasrc/main/java/com/module06/backend/reviewloop/judge/Confidence.javasrc/main/java/com/module06/backend/reviewloop/judge/DriverBudget.javasrc/main/java/com/module06/backend/reviewloop/judge/EvidenceValidator.javasrc/main/java/com/module06/backend/reviewloop/judge/Finding.javasrc/main/java/com/module06/backend/reviewloop/judge/FindingDto.javasrc/main/java/com/module06/backend/reviewloop/judge/FindingSource.javasrc/main/java/com/module06/backend/reviewloop/judge/GeminiCodeFixerAdapter.javasrc/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.javasrc/main/java/com/module06/backend/reviewloop/judge/GeminiLessonDraftAdapter.javasrc/main/java/com/module06/backend/reviewloop/judge/GeminiModels.javasrc/main/java/com/module06/backend/reviewloop/judge/GeminiOptionPanelAdapter.javasrc/main/java/com/module06/backend/reviewloop/judge/JudgeDecision.javasrc/main/java/com/module06/backend/reviewloop/judge/JudgeFindingsDto.javasrc/main/java/com/module06/backend/reviewloop/judge/JudgePromptBuilder.javasrc/main/java/com/module06/backend/reviewloop/judge/JudgeScorer.javasrc/main/java/com/module06/backend/reviewloop/judge/JudgeVerdict.javasrc/main/java/com/module06/backend/reviewloop/judge/KnowledgeStore.javasrc/main/java/com/module06/backend/reviewloop/judge/Lesson.javasrc/main/java/com/module06/backend/reviewloop/judge/LessonApprovalService.javasrc/main/java/com/module06/backend/reviewloop/judge/LessonDraftPort.javasrc/main/java/com/module06/backend/reviewloop/judge/LessonKind.javasrc/main/java/com/module06/backend/reviewloop/judge/LlmJudgePort.javasrc/main/java/com/module06/backend/reviewloop/judge/OptionPanel.javasrc/main/java/com/module06/backend/reviewloop/judge/OptionPanelPort.javasrc/main/java/com/module06/backend/reviewloop/judge/PanelAction.javasrc/main/java/com/module06/backend/reviewloop/judge/PanelOption.javasrc/main/java/com/module06/backend/reviewloop/judge/Recommendation.javasrc/main/java/com/module06/backend/reviewloop/judge/RepeatedPatternDetector.javasrc/main/java/com/module06/backend/reviewloop/judge/ReviewBudget.javasrc/main/java/com/module06/backend/reviewloop/judge/ReviewLessonRecorder.javasrc/main/java/com/module06/backend/reviewloop/judge/ReviewLoop.javasrc/main/java/com/module06/backend/reviewloop/judge/ReviewLoopCli.javasrc/main/java/com/module06/backend/reviewloop/judge/ReviewLoopPaths.javasrc/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.javasrc/main/java/com/module06/backend/reviewloop/judge/ReviewReport.javasrc/main/java/com/module06/backend/reviewloop/judge/ReviewRunner.javasrc/main/java/com/module06/backend/reviewloop/judge/RoundReviewer.javasrc/main/java/com/module06/backend/reviewloop/judge/RuleAccuracy.javasrc/main/java/com/module06/backend/reviewloop/judge/RuleCatalog.javasrc/main/java/com/module06/backend/reviewloop/judge/Severity.javasrc/main/java/com/module06/backend/reviewloop/judge/VerificationPort.javasrc/main/java/com/module06/backend/reviewloop/judge/VerifiedFixer.javasrc/main/java/com/module06/backend/reviewloop/judge/VerifyResult.javasrc/test/java/com/module06/backend/reviewloop/judge/AcceptanceGateTest.javasrc/test/java/com/module06/backend/reviewloop/judge/AuditLogWriterTest.javasrc/test/java/com/module06/backend/reviewloop/judge/AutoFixRunnerTest.javasrc/test/java/com/module06/backend/reviewloop/judge/ClaudeJudgeAdapterLiveTest.javasrc/test/java/com/module06/backend/reviewloop/judge/DriverBudgetTest.javasrc/test/java/com/module06/backend/reviewloop/judge/EvidenceValidatorTest.javasrc/test/java/com/module06/backend/reviewloop/judge/GeminiAutoFixLoopLiveTest.javasrc/test/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapterLiveTest.javasrc/test/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapterRequestTest.javasrc/test/java/com/module06/backend/reviewloop/judge/GeminiLessonDraftLiveTest.javasrc/test/java/com/module06/backend/reviewloop/judge/GeminiOptionPanelLiveTest.javasrc/test/java/com/module06/backend/reviewloop/judge/GeminiReviewLoopLiveTest.javasrc/test/java/com/module06/backend/reviewloop/judge/JudgePipelineTest.javasrc/test/java/com/module06/backend/reviewloop/judge/JudgePromptBuilderTest.javasrc/test/java/com/module06/backend/reviewloop/judge/JudgeScorerTest.javasrc/test/java/com/module06/backend/reviewloop/judge/KnowledgeArtifactsGenerator.javasrc/test/java/com/module06/backend/reviewloop/judge/KnowledgeLoopTest.javasrc/test/java/com/module06/backend/reviewloop/judge/KnowledgeLoopWiringTest.javasrc/test/java/com/module06/backend/reviewloop/judge/OptionPanelTest.javasrc/test/java/com/module06/backend/reviewloop/judge/PrePushGatePolicyTest.javasrc/test/java/com/module06/backend/reviewloop/judge/ReviewBudgetTest.javasrc/test/java/com/module06/backend/reviewloop/judge/ReviewLessonRecorderTest.javasrc/test/java/com/module06/backend/reviewloop/judge/ReviewLoopPathsTest.javasrc/test/java/com/module06/backend/reviewloop/judge/ReviewLoopRunnerTest.javasrc/test/java/com/module06/backend/reviewloop/judge/ReviewLoopTest.javasrc/test/java/com/module06/backend/reviewloop/judge/ReviewReportTest.javasrc/test/java/com/module06/backend/reviewloop/judge/ReviewRunnerTest.javasrc/test/java/com/module06/backend/reviewloop/judge/RuleAccuracyTest.javasrc/test/java/com/module06/backend/reviewloop/judge/RuleCatalogDomainTest.javasrc/test/java/com/module06/backend/reviewloop/judge/RuleCatalogScoringTest.javasrc/test/java/com/module06/backend/reviewloop/judge/SemiAutoLessonTest.java
| container: | ||
| image: semgrep/semgrep |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- workflow ---'
cat -n .github/workflows/gate1-semgrep.yml
printf '%s\n' '--- related image references ---'
rg -n -i 'semgrep/semgrep|container:|image:' .github README.md 2>/dev/null || true
printf '%s\n' '--- repository metadata ---'
git remote -v 2>/dev/null || true
git status --shortRepository: module06-4/BACKEND
Length of output: 1565
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import re
from pathlib import Path
text = Path(".github/workflows/gate1-semgrep.yml").read_text()
match = re.search(r'^\s*image:\s*(\S+)\s*$', text, re.M)
image = match.group(1)
print("image_ref:", image)
print("has_tag_or_digest:", ":" in image.rsplit("/", 1)[-1] or "@" in image)
PY
token="$(curl -fsSL 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:semgrep/semgrep:pull' | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')"
printf '%s\n' '--- latest manifest digest ---'
curl -fsSI \
-H "Authorization: Bearer $token" \
-H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json' \
'https://registry-1.docker.io/v2/semgrep/semgrep/manifests/latest' |
tr -d '\r' | grep -i '^docker-content-digest:\|^content-type:'
printf '%s\n' '--- relevant repository guidance ---'
rg -n -i 'digest|pin(ned|ning)?|semgrep|container image|dependency update|renovate|dependabot' . --glob '!*.lock' --glob '!node_modules/**' 2>/dev/null | head -100 || trueRepository: module06-4/BACKEND
Length of output: 2751
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import re
from pathlib import Path
text = Path(".github/workflows/gate1-semgrep.yml").read_text()
match = re.search(r'^\s*image:\s*(\S+)\s*$', text, re.M)
image = match.group(1)
print("image_ref:", image)
print("has_tag_or_digest:", ":" in image.rsplit("/", 1)[-1] or "@" in image)
PY
token="$(curl -fsSL 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:semgrep/semgrep:pull' | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')"
curl -fsSI \
-H "Authorization: Bearer $token" \
-H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json' \
'https://registry-1.docker.io/v2/semgrep/semgrep/manifests/latest' |
tr -d '\r' | grep -i '^docker-content-digest:\|^content-type:'Repository: module06-4/BACKEND
Length of output: 288
Semgrep 컨테이너 이미지를 digest로 고정하세요.
semgrep/semgrep에는 tag와 digest가 없으므로 Docker는 latest를 사용합니다. latest가 변경되면 동일한 커밋에서 다른 Semgrep 버전이 실행될 수 있습니다. 검증된 digest를 지정하고 갱신 절차를 문서화하세요.
🧰 Tools
🪛 zizmor (1.28.0)
[error] 17-17: unpinned image references (unpinned-images): container image is unpinned
(unpinned-images)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/gate1-semgrep.yml around lines 16 - 17, Pin the container
image in the workflow’s container configuration to a verified immutable Semgrep
image digest instead of the floating semgrep/semgrep reference, and document the
procedure for validating and updating that digest.
Source: Linters/SAST tools
There was a problem hiding this comment.
persist-credentials: false는 네 개 checkout 단계 모두에 반영했습니다 (8ef3710).
시크릿 주입 건은 gate2-judge.yml:108 스레드에서 함께 답변드립니다.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- gate1 workflow ---'
sed -n '1,100p' .github/workflows/gate1-semgrep.yml
printf '%s\n' '--- Semgrep image and update-procedure references ---'
rg -n -i -C 2 'semgrep/semgrep|semgrep.*(digest|update|갱신)|digest.*semgrep' \
.github review-loop README.md 2>/dev/null || trueLength of output: 2102
@dlxodus02 persist-credentials: false 변경은 확인했습니다. 해당 변경은 별도 actions/checkout 지적을 해결합니다.
이 스레드는 Semgrep container image 고정에 관한 지적입니다. 현재 semgrep/semgrep:1.172.0은 버전 태그이지만 immutable digest는 아닙니다. 태그는 다시 지정될 수 있으므로, 동일한 커밋에서 실행 이미지가 변경될 수 있습니다.
워크플로 주석에는 태그 사용 이유가 문서화되어 있습니다. 그러나 이 스레드의 digest 고정 요구 사항은 아직 충족되지 않았습니다.
You are interacting with an AI system.
| if (lessons != null && !lessons.isEmpty()) { | ||
| StringBuilder mistakes = new StringBuilder(); | ||
| for (Lesson lesson : lessons) { | ||
| if (lesson.kind() == LessonKind.CONFIRMED) { | ||
| continue; // CONFIRMED는 Judge의 실수가 아님 — 정확도 집계용이라 프롬프트에서 제외 | ||
| } | ||
| mistakes.append("- [").append(lesson.kind()).append("] ") | ||
| .append(lesson.ruleId()).append(": ").append(lesson.humanNote()).append('\n'); | ||
| } | ||
| if (mistakes.length() > 0) { | ||
| sb.append("\n과거 사람 검토에서 나온 교훈(같은 실수를 반복하지 마라):\n").append(mistakes); | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
fd -t f 'JudgePromptBuilder\.java|ReviewLoop.*\.java|Lesson.*\.java' . || true
printf '%s\n' '--- relevant symbols and call sites ---'
rg -n -S 'class JudgePromptBuilder|buildPolicy|class ReviewLoop|ReviewLoop|LessonKind|humanNote|ruleId' . \
-g '*.java' -g '*.kt' -g '*.md' || trueRepository: module06-4/BACKEND
Length of output: 30112
🏁 Script executed:
#!/bin/bash
set -eu
for f in \
src/main/java/com/module06/backend/reviewloop/judge/JudgePromptBuilder.java \
src/main/java/com/module06/backend/reviewloop/judge/ReviewLoop.java \
src/main/java/com/module06/backend/reviewloop/judge/KnowledgeStore.java \
src/main/java/com/module06/backend/reviewloop/judge/ReviewLoopRunner.java \
src/main/java/com/module06/backend/reviewloop/judge/AutoLoopRunner.java \
src/main/java/com/module06/backend/reviewloop/judge/AutoLoopOrchestrator.java \
src/test/java/com/module06/backend/reviewloop/judge/JudgePromptBuilderTest.java \
src/test/java/com/module06/backend/reviewloop/judge/KnowledgeLoopTest.java \
src/test/java/com/module06/backend/reviewloop/judge/KnowledgeLoopWiringTest.java
do
if [ -f "$f" ]; then
printf '\n--- %s ---\n' "$f"
wc -l "$f"
sed -n '1,220p' "$f"
fi
doneRepository: module06-4/BACKEND
Length of output: 33826
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- repeated review calls ---'
rg -n -C 5 -S 'review\(|ReviewLoop|roundsUsed|roundsPerFile|globalBudget' \
src/main/java/com/module06/backend/reviewloop/judge \
-g '*.java' | sed -n '1,260p'
printf '%s\n' '--- judge request construction and limits ---'
for f in $(fd -t f -i 'JudgeAdapter|Gemini.*Adapter|Claude.*Adapter|AutoFixRunner' src/main/java/com/module06/backend/reviewloop/judge); do
printf '\n--- %s ---\n' "$f"
rg -n -C 6 -S 'policy|contents|prompt|request|token|maxOutput|review\(' "$f" || true
done
printf '%s\n' '--- lesson persistence and ordering ---'
rg -n -C 8 -S 'new KnowledgeStore|\.record\(|lessons\(\)|LESSONS|lessons\.jsonl|sort|Comparator' \
src/main/java src/test review-loop -g '*.java' -g '*.md' -g '*.jsonl' || trueRepository: module06-4/BACKEND
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- exact request builders ---'
sed -n '96,135p' src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java
sed -n '34,75p' src/main/java/com/module06/backend/reviewloop/judge/ClaudeJudgeAdapter.java
printf '%s\n' '--- repeated-call implementations ---'
sed -n '1,90p' src/main/java/com/module06/backend/reviewloop/judge/ReviewRunner.java
sed -n '1,85p' src/main/java/com/module06/backend/reviewloop/judge/AutoFixRunner.java
printf '%s\n' '--- persisted lessons ---'
fd -t f -i 'lessons\.jsonl' review-loop src || true
for f in $(fd -t f -i 'lessons\.jsonl' review-loop src); do
printf '\n--- %s ---\n' "$f"
wc -c -l "$f"
cat -n "$f"
done
printf '%s\n' '--- request-size configuration ---'
rg -n -i -S 'maxTokens|maxOutputTokens|input.*token|token.*limit|context.*limit|request.*size|payload.*size' \
src/main/java review-loop build.gradle* gradle.properties 2>/dev/null || trueRepository: module06-4/BACKEND
Length of output: 8996
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
builder = Path("src/main/java/com/module06/backend/reviewloop/judge/JudgePromptBuilder.java").read_text()
store = Path("src/main/java/com/module06/backend/reviewloop/judge/KnowledgeStore.java").read_text()
loop = Path("src/main/java/com/module06/backend/reviewloop/judge/ReviewLoop.java").read_text()
runner = Path("src/main/java/com/module06/backend/reviewloop/judge/AutoFixRunner.java").read_text()
assert "lesson.kind() == LessonKind.CONFIRMED" in builder
assert "append(lesson.humanNote())" in builder
assert "Files.readAllLines(file)" in store
assert "promptBuilder.buildPolicy(catalog, lessons)" in loop
assert "while (!budget.isExhausted())" in runner
assert "reviewer.review(filePath, code)" in runner
def lesson_line(i):
return f"- [FALSE_POSITIVE] CONV_001: lesson-{i}\n"
def serialized_lesson_bytes(n):
return len("".join(lesson_line(i) for i in range(n)).encode())
sizes = [serialized_lesson_bytes(n) for n in (0, 1, 10, 100, 1000)]
assert sizes == sorted(sizes)
assert sizes[-1] > sizes[0]
print("CONFIRMED lessons are excluded by the current builder")
print("non-CONFIRMED lesson serialization follows input-file order")
print("serialized lesson section grows monotonically with lesson count:", sizes)
print("ReviewLoop reuses the same unbounded lesson list on each review call")
PYRepository: module06-4/BACKEND
Length of output: 430
교훈 프롬프트에 명시적 크기 상한을 적용하세요.
CONFIRMED를 제외한 Lesson을 제한 없이 정책 문자열에 추가합니다. 축적된 JSONL 전체를 각 파일과 자동 수정 라운드의 요청에 반복해서 포함하므로, 모델의 컨텍스트 한도를 초과해 Judge 요청이 실패할 수 있습니다. 규칙별 최근 교훈 수와 전체 문자 또는 토큰 예산을 제한하고, 선택·정렬·절단 순서를 검증하는 결정론적 테스트를 추가하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/java/com/module06/backend/reviewloop/judge/JudgePromptBuilder.java`
around lines 35 - 46, Update the lesson prompt construction around the loop in
JudgePromptBuilder to apply explicit per-rule recent-lesson limits and a
deterministic overall character or token budget before appending to the policy
string. Exclude CONFIRMED lessons, then deterministically select, order, and
truncate remaining lessons so the generated prompt always stays within the
configured bounds. Add deterministic tests covering selection order, per-rule
limits, and total-budget truncation.
There was a problem hiding this comment.
타당한 지적이라 후속 작업으로 등록하고, 이번 PR에서는 보류하겠습니다.
이식 과정에서 lessons.jsonl을 초기화해 현재 축적된 교훈이 0건이라 당장의 영향은 없습니다.
다만 append-only 구조상 교훈이 쌓이면 매 요청 비용이 선형 증가하고 결국 컨텍스트 한도에 닿는다는 지적에 동의합니다.
규칙별 최근 N건 + 전체 문자 예산 상한을 결정론 테스트와 함께 추가하겠습니다.
There was a problem hiding this comment.
@dlxodus02, 확인했습니다. 현재 lessons.jsonl이 비어 있으므로 이 PR의 즉시 동작에는 영향이 없습니다.
후속 작업에는 규칙별 최근 교훈 수 제한, 전체 문자 예산, 결정론적 선택·정렬·절단 테스트를 포함하면 됩니다. 이 리뷰 코멘트는 열린 상태로 유지하겠습니다.
후속 GitHub Issue를 생성할까요? 생성하면 해당 Issue에 PR #5와 이 코멘트 링크를 연결하겠습니다.
🐇📏
You are interacting with an AI system.
CodeRabbit(.coderabbit.yaml)과 역할 분리 — 광범위 어드바이저리는 CodeRabbit, 루프 Gate는 게이트/자율수정 대상만 남긴다(중복·오탐 방지). - 제거: FLOWN 전용 SEC_001/002·DOM_001/002·QDIFF_001/003/005 + ARCH_003 FLOWN 부채 리셋 - 유지: 결정론 백본(ARCH_001/002/003·QUERY_002·MIG_001) + judge 3개(CONV_001·PERF_001·ARCH_003a) - judge 3개는 회귀 가드(JudgePromptBuilderTest·RuleCatalogScoringTest)가 고정 → 유지 - 앵커의 FLOWN 파일참조 genericize Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
검증된 결함 수정: - scripts: 이식 누락된 com/wanted/backend 경로 → com/module06/backend (review-score-domains.sh는 존재하지 않는 경로를 스캔하고 있었음) - review-autoloop.sh: linked worktree에서 .git은 파일이라 그 아래 쓰기 실패 → git rev-parse --git-path 사용 - review-fix-apply.sh: mapfile은 bash 4+ 전용 → macOS 기본 bash 3.2에서 훅이 깨짐 → 이식성 있는 while-read 방식 - AutoLoopOrchestrator: 판정·수정 중 예외 시 중간 수정본이 작업트리에 남던 문제 → try/catch로 원본 복원 후 다음 파일 진행 - ReviewLoopRunner: 조기 종료(대상 없음·키 없음) 시 findings 파일 미초기화로 낡은 findings가 수정 요청서에 재사용되던 문제 → resetFindings 추가 - KnowledgeStore·ReviewReport: JSONL 한 줄 손상이 전체 읽기를 실패시키던 문제 → 줄 단위 skip (lessons.jsonl은 공유·커밋 파일이라 머지 충돌 위험) - RuleCatalog.parseSeverity: 알 수 없는 severity를 조용히 MINOR로 폴백 → CRITICAL 오타가 "Critical은 항상 사람" 안전장치를 우회 → 로드 실패로 전환 워크플로 보안 강화: - gate2-judge: 전역 쓰기 권한 제거(contents: read), checks: write는 필요한 잡에만 - 모든 checkout에 persist-credentials: false Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GeminiJudgeAdapter: finishReason != STOP인데 parts가 비면 findings 0건 → "지적 없음 = PASS"로 게이트가 조용히 통과하던 문제(가장 위험한 실패 방향). 판정 불가를 통과가 아니라 오류로 드러낸다. - GeminiCodeFixerAdapter: 잘린 응답(MAX_TOKENS)을 '고친 코드'로 채택하던 문제. 파일 전체를 반환하는 어댑터라 잘리면 파일이 깨진다 → 원본 유지 + 원인 로그. (VerifiedFixer가 결국 롤백하지만 라운드·비용 낭비 + 원인 추적 불가였음) - gate1-semgrep: image를 latest → 1.172.0 고정. latest 드리프트로 게이트 동작이 조용히 달라지는 것 방지. digest 대신 버전 태그(Dependabot이 container 이미지를 자동 갱신하지 않아 digest는 수동 유지보수 부담). CodeRabbit #10·#1 반영. #11(temperature:0)은 스코어 진동(85↔100) 해결책이라 유지. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java (1)
126-145: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win누락된 Gemini 판정 메타데이터를 오류로 처리하십시오.
candidates,finishReason,content.parts또는 텍스트가 없으면List.of()를 반환하지 말고 예외를 발생시키십시오.finishReason은 반드시STOP이어야 합니다. 그렇지 않으면 불완전한 응답이 정상적인 finding 없음으로 처리되어 게이트를 통과할 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java` around lines 126 - 145, Update the response validation in GeminiJudgeAdapter around candidate parsing so missing candidates, finishReason, content.parts, or text throw an exception instead of returning List.of(). Require finishReason to be present and exactly STOP, while preserving normal parsing only for complete responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java`:
- Around line 126-145: Update the response validation in GeminiJudgeAdapter
around candidate parsing so missing candidates, finishReason, content.parts, or
text throw an exception instead of returning List.of(). Require finishReason to
be present and exactly STOP, while preserving normal parsing only for complete
responses.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cd7e88ed-1eb0-4096-a4fb-5d17d2c4c1ec
📒 Files selected for processing (3)
.github/workflows/gate1-semgrep.ymlsrc/main/java/com/module06/backend/reviewloop/judge/GeminiCodeFixerAdapter.javasrc/main/java/com/module06/backend/reviewloop/judge/GeminiJudgeAdapter.java
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/gate1-semgrep.yml
📌 연관 이슈
📝 작업 내용
🖥️ 프론트엔드 연동 가이드 (API 명세)
🚨 주요 에러 코드 및 예외
💡 백엔드 리뷰 포인트
✅ 체크리스트
Summary by CodeRabbit