Repository navigation
시공간 복잡도 분석 + 알고리즘 패턴 태킹 코드 합치기 #31
Description
Activity
- linked a pull request that will close this issuefeat: 패턴 태깅 댓글에 복잡도 분석 합본 (closes #31) #43
on May 1, 2026 - added a commit that references this issue
on May 1, 2026 요구사항
알고리즘 패턴 태깅이 일어나는 시점에 시간/공간 복잡도 분석 결과도 같은 댓글에 함께 표시한다. 두 기능이 별도 webhook 디스패치로 동작하더라도 타이밍 이슈 없이 합쳐지도록 한다.
진행 과정
-
research.md — 현재 구조 분석. 패턴 태깅은 파일별 review comment(
/pulls/{n}/comments), 복잡도 분석은 PR 단위 issue comment(/issues/{n}/comments) 로 위치가 다르고, 각자 별도 Worker invocation 으로 디스패치되고 있음을 확인. 4개 옵션 비교 후 합본 핸들러(옵션 A) 채택. -
plan.md — "패턴 태깅에 묻어가는" 형태로 최소 변경 계획 수립. MAX_FILES 가드는 불필요(기존 패턴 태깅 자체에 13파일+에서 한도 cliff 가 있어 합본은 1파일분만 당기는 정도) 임을 계산으로 검증.
-
구현 — 두 분석을 같은 invocation 안에서
Promise기반 병렬 실행. 모든 raw 를 사전에 한 번만 다운로드해 패턴 N콜과 복잡도 1콜이 공유. 댓글 본문에 복잡도 섹션 한 블록 append. 구버전 단독 복잡도 issue comment 는 첫 동작 시 자동 정리. -
테스트 —
bun test handlers/75/75,bun test tests/17/17 통과.tests/tag-patterns.test.js신규 추가(skip 가드, 합본, 마이그레이션, 패턴 정리, 부분 실패),tests/subrequest-budget.test.js5파일 기준 22 → 25 갱신.
결과
- 디스패치 invocation 3개 → 2개 (
tag-patterns,learning-status) complexity-analysis모듈은 분석 함수와 한 파일분 섹션 렌더러만 export 하는 형태로 슬림화- subrequest 예산: 5파일 25회 / 10파일 44~45회 (한도 50 안)
- 타이밍 이슈는 두 분석이 같은 함수 안에서 병렬 처리되므로 원천 차단
PR
-
Research: 패턴 태깅 + 복잡도 분석 합치기
이슈 #31의 목표 — 알고리즘 패턴 태깅 댓글에 시간/공간 복잡도 분석 결과를 함께 표시한다. 현재는 두 핸들러가 별도 Worker invocation으로 디스패치되어 결과가 서로 다른 댓글 위치에 게시되는데, 이를 한 댓글로 합치되 타이밍 이슈 없이 안전하게 수행하기 위한 방안을 정리한다.
1. 현재 구조
1.1 디스패치 (
handlers/webhooks.js:289)pull_request이벤트(opened/reopened/synchronize) 시ctx.waitUntil로 세 핸들러를 병렬·독립 invocation으로 디스패치:Invocation #1 (webhook) ├─ /internal/tag-patterns ← Invocation #2 (50 subrequest 예산) ├─ /internal/learning-status ← Invocation #3 └─ /internal/complexity-analysis ← Invocation #4이유: Cloudflare Workers는 invocation당 subrequest 50회 제한이 있어, 파일이 많은 PR에서 한 invocation에 다 몰면 예산을 초과한다.
tests/subrequest-budget.test.js가 5파일 시나리오에서tagPatterns=22,analyzeComplexity=9회 호출을 회귀로 박아둔다.1.2 댓글 게시 위치 차이
핸들러 댓글 형태 위치 마커 tagPatterns파일 단위 review comment ( /pulls/{n}/comments,subject_type: "file")각 파일 우측에 인라인 <!-- dalestudy-pattern-tag -->analyzeComplexityPR 단위 issue comment ( /issues/{n}/comments)PR 본문 아래 일반 댓글 1개 <!-- dalestudy-complexity-analysis -->→ 합치려면 둘 중 하나의 위치로 통일해야 한다.
1.3 데이터 흐름 차이
항목 tagPatterns analyzeComplexity OpenAI 호출 파일당 1회 (N파일 = N콜) 전체 1회 (N파일 일괄 분석) 파일별 raw 다운로드 O O 변경 파일 좁히기 changedFilenames지원미지원 (전체 솔루션 파일 분석) 출력 단위 파일별 {patterns, description}파일별 {solutions[]}(멀티 풀이 가능)2. 합치는 방향 — 옵션 비교
옵션 A: 같은 invocation에서 순차 실행, 파일별 review comment에 합본 게시 (권장)
tag-patterns.js의 파일 단위 review comment 위치를 표준으로 삼고, 그 댓글 본문에 복잡도 분석 결과까지 포함시킨다. 두 핸들러를 하나의 핸들러로 통합한다.새 핸들러: tagPatternsAndComplexity 1. PR files 조회 (1회) 2. raw 다운로드 N회 3. 패턴 분석: 파일당 OpenAI 1회 (N회) 4. 복잡도 분석: 전체 OpenAI 1회 (1회) ← 일괄 분석 유지 5. 파일별로 패턴+복잡도 합쳐서 review comment 작성 (DELETE N + POST N) 총 subrequest: files(1) + raw(N) + 패턴OpenAI(N) + 복잡도OpenAI(1) + 코멘트목록(1) + DELETE(N) + POST(N) = 5N + 35파일 기준 28회, 50회 한도 안에서 안전하다. 10파일 기준 53회로 한도 초과 가능 → MAX_FILES 가드 필요.
장점:
- 타이밍 이슈가 원천적으로 사라진다 — 두 분석이 같은 함수 안에서 직렬 실행되므로 순서 보장.
- 댓글이 1곳(파일별 review)에만 생기므로 사용자 입장에서 깔끔.
- 기존 복잡도 댓글(이슈 코멘트)을 마이그레이션 시점에 한 번만 정리하면 됨.
단점:
- subrequest 예산 압박: 파일이 많으면 예산 초과 위험. 가드 필요(예: 솔루션 파일 > 8개면 복잡도 분석은 일괄 PR 댓글로 폴백).
- 복잡도 결과를 파일별로 쪼개는 매핑 로직 필요(현재는
entries[]배열로 한 번에 렌더링). - 멀티 풀이가 있는 파일에서 댓글 본문이 길어진다(
Details
접기 활용으로 완화).
옵션 B: 한 invocation에서 분석 → 다른 invocation이 결과 가져와 댓글 작성
복잡도 분석 결과를 KV/Cache에 저장한 뒤 패턴 태깅 핸들러가 읽어 합치는 구조.
문제점:
- 두 invocation 간 타이밍 의존성 발생 — 패턴이 먼저 끝났는데 복잡도가 아직이면 빈 결과로 댓글이 나가거나, 폴링 대기로 invocation 시간 낭비.
- KV 추가 인프라 필요. 캐시 무효화 정책 설계 필요.
- 디버깅 복잡도 증가.
→ 사용자가 명시적으로 우려한 "타이밍 이슈"가 정확히 이 구조에서 발생. 권장하지 않음.
옵션 C: 현재 구조 유지하되 두 댓글이 서로 참조 링크
각자 자기 댓글을 게시한 뒤 본문에 상대 댓글로의 anchor 링크를 추가. 합치는 게 아니라 연결하는 방식.
문제점:
- 사용자 요구사항("패턴 태깅의 댓글에 같이 제공")을 충족하지 못함.
- 두 invocation 간 댓글 ID를 알아야 하므로 다시 타이밍 의존성 발생.
→ 요구사항 미충족, 제외.
옵션 D: 디스패처 단계에서 분석 결과만 produce, 댓글 게시는 webhook 본 invocation이 수행
webhook이
await Promise.all([...])로 두 분석 결과를 모두 받은 뒤 합쳐서 댓글 작성.ctx.waitUntil대신 동기 대기.문제점:
- webhook 본 invocation의 subrequest 예산(50)에 두 분석이 모두 들어감 — 현재 분리 아키텍처가 만들어진 이유와 정면 충돌.
- 파일 많은 PR에서 예산 초과 재발.
→ 권장하지 않음. 분리 아키텍처를 우회.
3. 권장안: 옵션 A — 통합 핸들러
3.1 새 핸들러 구조 (
handlers/tag-patterns.js를 확장)export async function tagPatternsAndComplexity( repoOwner, repoName, prNumber, headSha, prData, appToken, openaiApiKey, changedFilenames = null ) { // 1. skip 가드 (draft / maintenance) — 기존과 동일 // 2. PR files 조회 + 솔루션 파일 필터 + changedFilenames 좁히기 // 3. 모든 파일 raw 다운로드 (1회 루프) // → fileEntries = [{ file, problemName, content }, ...] // 4. 두 분석을 병렬 실행 const [patternResults, complexityResults] = await Promise.all([ analyzePatterns(fileEntries, openaiApiKey), // 파일당 1콜 callComplexityAnalysis(fileEntries, openaiApiKey), // 전체 1콜 ]); // → Promise.all 로 OpenAI 두 분석을 동시 진행: 패턴 N콜 + 복잡도 1콜 // 대기 시간 = max(패턴 N콜 직렬, 복잡도 1콜) — 패턴 분석을 // 현재처럼 직렬로 두면 패턴 쪽이 critical path. // 필요 시 패턴도 Promise.all 내부 병렬화 가능 (단, OpenAI rate limit 주의). // 5. 기존 패턴 코멘트 삭제 (대상 파일만) // 6. 파일별로 combined body 생성 후 review comment POST // body = COMMENT_MARKER + 패턴 섹션 + 복잡도 섹션 }
3.2 댓글 본문 포맷 (예시)
<!-- dalestudy-pattern-tag --> <!-- dalestudy-complexity-analysis --> ← 두 마커 모두 유지(검색 호환) ### 🏷️ 알고리즘 패턴 분석 - **패턴**: Two Pointers, Hash Map - **설명**: 정렬 후 양 끝 포인터로 합을 맞추는 ... ### 📊 시간/공간 복잡도 분석 | | 유저 분석 | 실제 분석 | 결과 | |---|---|---|---| | **Time** | O(n) | O(n) | ✅ | | **Space** | O(1) | O(n) | ❌ | **피드백**: 해시맵을 사용하므로 공간은 O(n)입니다. **개선 제안**: 정렬 후 투포인터로 O(1) 공간 가능.
멀티 풀이 파일은 기존
<details>접기 패턴 그대로 유지.3.3 호출 측 변경 (
handlers/webhooks.js)// AS-IS: 3개 디스패치 ctx.waitUntil(fetch("/internal/tag-patterns", ...)); ctx.waitUntil(fetch("/internal/learning-status", ...)); ctx.waitUntil(fetch("/internal/complexity-analysis", ...)); // TO-BE: 2개 디스패치 ctx.waitUntil(fetch("/internal/tag-patterns", ...)); // 패턴+복잡도 합본 ctx.waitUntil(fetch("/internal/learning-status", ...));
/internal/complexity-analysis엔드포인트는 즉시 제거하지 않고 한 사이클 deprecated 상태로 두는 게 안전(롤백 여지). 또는 한 PR로 깔끔히 제거.3.4 마이그레이션 (한 번만)
기존 PR들에는 패턴 review comment + 복잡도 issue comment 가 따로 존재. 새 코드 배포 후 첫 동작 시:
- 기존 issue comment(
<!-- dalestudy-complexity-analysis -->) 삭제 — review comment에 통합되었으니 중복. - 새 review comment에 두 마커 모두 포함 → 기존 패턴 코멘트 정리 로직(
COMMENT_MARKER검색)이 그대로 매칭.
이 정리는 새 핸들러 안에서 자동 수행하도록 한다. 별도 일회성 스크립트 불필요.
4. 타이밍 이슈 분석
시나리오 AS-IS TO-BE (옵션 A) 패턴이 먼저 게시되고 복잡도가 늦게 따라옴 발생 가능 (별 invocation) 불가능 (한 함수 내) synchronize로 두 분석이 동시에 다시 돌면서 race 각 핸들러가 자기 댓글만 upsert하므로 영향 적음 review comment는 삭제 후 재작성이라 race 시 중복 코멘트 발생 가능 복잡도 분석만 실패 패턴 댓글은 정상, 복잡도 댓글만 누락 통합 댓글에서 복잡도 섹션만 누락(Promise.all 의 reject 처리 필요) race 방지
Webhook이 빠르게 두 번 와서 같은 PR에 동시에 두 invocation이 도는 경우:
- 현재도 review comment 삭제 후 재생성 패턴이라 두 invocation이 동시에 들어오면 중복이 생길 수 있음. 통합 후에도 동일 위험이 남는다.
- 완화책: review comment 작성을 PATCH 기반 upsert로 전환(이슈 코멘트와 동일하게 마커로 식별 후 업데이트). 단, GitHub review comment는 PATCH로 body 수정이 가능하므로 구현 가능.
- 또는 KV에 PR 단위 락을 두는 방안도 있으나 과한 듯.
→ review comment를 marker 기반 upsert로 전환하는 게 race 안전성을 한 단계 올린다(별도 작업으로 분리 가능).
Promise.all 부분 실패 처리
const [patternResults, complexityResults] = await Promise.allSettled([...]); // 패턴 실패 → 복잡도만 표시 + 패턴 섹션은 "분석 실패" 메시지 // 복잡도 실패 → 패턴만 표시 // 둘 다 실패 → 댓글 작성 스킵
부분 실패 시에도 댓글이 나갈 수 있도록
Promise.allSettled권장.5. subrequest 예산 검증
옵션 A에서 5파일 기준 호출 수:
5N + 3 = 28. 한도 50 안.10파일 기준:
5*10 + 3 = 53→ 초과. 가드 필요:- MAX_FILES_FOR_PATTERNS = 8 (패턴은 파일당 OpenAI 호출이라 예산 압박)
- 초과 시: 패턴은 처음 8개 파일만 분석 + 복잡도는 전체 일괄 분석(어차피 OpenAI 1콜) + 나머지 파일에는 "패턴 분석 생략(파일 수 한도)" 표시.
또는 패턴 분석을 N콜에서 1콜 일괄로 바꾸는 리팩토링도 검토 가능 — 복잡도 분석과 같은 패턴(전체 파일 한 번에 OpenAI에 보내고 파일별 결과 받기)으로 전환하면 예산 부담이 사라지고 두 분석이 OpenAI 2콜로 통일됨. 단, 컨텍스트 토큰 한도와 정확도 회귀 검증 필요.
→ 장기적으로 패턴 분석도 일괄화 권장. 단기에는 MAX_FILES 가드로 안전 확보.
6. 회귀 테스트
tests/subrequest-budget.test.js가 현재 호출 수를 박아두고 있다. 통합 후 이 테스트의 기대값을 갱신:- AS-IS: tagPatterns=22, analyzeComplexity=9 (별도 invocation)
- TO-BE: tagPatternsAndComplexity=28 (5파일, 합본)
handlers/tag-patterns.test.js와handlers/complexity-analysis.test.js의 기존 단위 테스트는 분석 함수만 따로 export해서 살리고, 통합 함수에 새 테스트 추가:- 패턴+복잡도 둘 다 성공 → 합본 댓글 1개
- 패턴 실패, 복잡도 성공 → 복잡도만 표시된 댓글
- 복잡도 실패, 패턴 성공 → 패턴만 표시된 댓글
- 둘 다 실패 → 댓글 작성 안 함
- 기존 issue comment 정리 (마이그레이션 동작)
7. 단계별 작업 순서 (제안)
- 분석 함수 분리 —
tag-patterns.js의 OpenAI 호출 부분을analyzePatternsForFiles(fileEntries)같은 순수 함수로 추출.complexity-analysis.js의callComplexityAnalysis도 이미 분리되어 있음. - 새 통합 핸들러 작성 — 두 분석을
Promise.allSettled로 병렬 호출, 파일별 합본 본문 생성, review comment upsert. - 마이그레이션 로직 — 통합 핸들러 첫 동작 시 기존
dalestudy-complexity-analysisissue comment 삭제. - 디스패처 변경 —
webhooks.js에서 complexity-analysis 디스패치 제거, internal-dispatch 라우팅도 정리. - 테스트 갱신 — subrequest-budget 회귀 + 새 통합 테스트.
- 문서 업데이트 — AGENTS.md의 "AI 핸들러 Worker 분리 아키텍처" 섹션을 2개 핸들러로 갱신.
각 단계가 독립 PR로 쪼개지진 않고, 1번 분리 + 2~5번 한 PR + 6번 같이가 깔끔할 듯.
8. 결론
- 옵션 A (통합 핸들러, 파일별 review comment) 채택 권장.
- 타이밍 이슈는 두 분석을 같은 함수 안
Promise.allSettled로 병렬 처리하여 원천 차단. - subrequest 예산은 MAX_FILES 가드로 단기 대응, 장기적으로 패턴 분석도 일괄화 검토.
- 마이그레이션은 통합 핸들러 내부에서 기존 issue comment를 자동 정리.
- review comment upsert(PATCH) 전환은 race 안전성 강화로 별도 작업 가능.
Plan: 패턴 태깅에 복잡도 분석 묻어가기
이슈 #31. 패턴 태그 핸들러는 거의 그대로 두고, 복잡도 분석을 그 뒤에 슬쩍 묻어가는 형태로 합친다. 복잡도 핸들러는 별도 디스패치를 그만두고, 분석 함수만 패턴 핸들러가 빌려 쓴다.
1. 핵심 아이디어
tagPatterns (기존 흐름 유지) │ ├─ (NEW) 파일 raws를 pre-loop에서 한 번에 모음 → fileEntries ├─ (NEW) complexityPromise = callComplexityAnalysis(fileEntries, ...) │ ← 패턴 loop 와 병렬 진행되는 1 OpenAI 콜 │ ├─ 기존: 패턴 코멘트 정리 (DELETE) ├─ 기존: per-file 패턴 분석 + review comment POST │ ↑ POST 시점에 complexityPromise 결과를 await 해서 body 에 한 섹션 append │ └─ (NEW) 한 번 마이그레이션: 기존 dalestudy-complexity-analysis issue comment 삭제즉, 패턴 태깅의 per-file 루프와 review comment 게시 위치는 그대로. tagSingleFile 본문에 복잡도 섹션 한 블록만 더 붙이는 것이 변경의 본질.
2. MAX_FILES 가드 결론
필요 없음. 계산:
N파일 기존 tagPatterns호출 수합본 후 호출 수 50 한도 5 22 24~25 OK 10 42 44~45 OK 11 46 48~49 빠듯 12 50 (cliff) 52~53 초과 13 54 (이미 초과) 56~57 초과 기존 코드는 이미 13파일+에서 한도를 넘는 cliff를 갖고 있고, 합본 버전은 이 cliff를 12파일로 1파일분 당길 뿐이다. 리트코드 PR은 보통 1~5 파일이라 실질 영향은 없다. 가드를 새로 도입하지 않고, 코드 주석으로 "12+ 파일 시 subrequest 한도 근접" 한 줄만 남긴다.
기존 cliff 자체에 대응이 필요하다면 그건 별도 이슈로 다룰 영역(예: 패턴 분석 1콜 일괄화).
3. 변경 파일
3.1
handlers/complexity-analysis.js— export 1개 추가-async function callComplexityAnalysis(fileEntries, apiKey) { ... } +export async function callComplexityAnalysis(fileEntries, apiKey) { ... }
composeSolution,extractUserAnnotations등 순수 헬퍼는 이미 export 되어 있어 변경 없음.analyzeComplexity(오케스트레이션)는 더 이상 호출되지 않지만, 한 PR에서 같이 제거.3.2
handlers/tag-patterns.js— 변경의 거의 전부(a) 상단에 import + 레거시 마커 상수 추가
import { callComplexityAnalysis } from "./complexity-analysis.js"; import { getGitHubHeaders } from "../utils/github.js"; // 이미 있음 // 레거시 단독 복잡도 issue comment 식별용. 새 합본 댓글에는 박지 않는다. const LEGACY_COMPLEXITY_MARKER = "<!-- dalestudy-complexity-analysis -->";
새 합본 review comment 본문에는 기존
COMMENT_MARKER(<!-- dalestudy-pattern-tag -->) 하나만 들어간다. tag-patterns 의 기존 정리 로직이 그 마커로 자기 댓글을 찾아 DELETE 하므로 추가 마커는 의미 없다.LEGACY_COMPLEXITY_MARKER는 마이그레이션 함수에서만 사용한다 (구버전이 남긴 단독 issue comment 식별 용도).(b)
tagPatterns본체 — pre-loop 추가, post-loop 추가// 2-3. 기존 Bot 패턴 태그 코멘트 삭제 (변경 파일만) const targetFilenames = solutionFiles.map((f) => f.filename); await deletePreviousPatternComments(...); + // (NEW) 모든 파일 raw 다운로드 (한 번만) — 복잡도와 공유 + const fileEntries = await downloadFileEntries(solutionFiles); + + // (NEW) 복잡도 분석은 1콜이므로 패턴 루프와 병렬 진행 + const complexityPromise = callComplexityAnalysis(fileEntries, openaiApiKey) + .catch((err) => { + console.error(`[tagPatterns] complexity failed: ${err.message}`); + return []; + }); // 2-4. 파일별 OpenAI 분석 + 코멘트 작성 const results = []; - for (const file of solutionFiles) { + for (const fe of fileEntries) { try { - const result = await tagSingleFile(file, repoOwner, repoName, prNumber, headSha, appToken, openaiApiKey); + const result = await tagSingleFile(fe, complexityPromise, repoOwner, repoName, prNumber, headSha, appToken, openaiApiKey); - results.push({ path: file.filename, ...result }); + results.push({ path: fe.file.filename, ...result }); } catch (error) { ... } } + // (NEW) 한 번 마이그레이션: 기존 단독 복잡도 issue comment 삭제 + await deleteLegacyComplexityIssueComment(repoOwner, repoName, prNumber, appToken); return { tagged: results.filter((r) => !r.error).length, results }; }
추가되는 헬퍼 두 개는 짧다:
async function downloadFileEntries(solutionFiles) { return Promise.all( solutionFiles.map(async (file) => { const res = await fetch(file.raw_url); if (!res.ok) throw new Error(`Failed to fetch raw: ${res.status}`); let content = await res.text(); if (content.length > MAX_FILE_SIZE) content = content.slice(0, MAX_FILE_SIZE); return { file, problemName: file.filename.split("/")[0], content }; }) ); } async function deleteLegacyComplexityIssueComment(repoOwner, repoName, prNumber, appToken) { const res = await fetch( `https://api.github.com/repos/${repoOwner}/${repoName}/issues/${prNumber}/comments?per_page=100`, { headers: getGitHubHeaders(appToken) } ); if (!res.ok) return; const comments = await res.json(); const legacy = comments.find( (c) => c.user?.type === "Bot" && c.body?.includes(LEGACY_COMPLEXITY_MARKER) ); if (!legacy) return; await fetch( `https://api.github.com/repos/${repoOwner}/${repoName}/issues/comments/${legacy.id}`, { method: "DELETE", headers: getGitHubHeaders(appToken) } ); }
(c)
tagSingleFile— 시그니처 살짝 + body 끝에 복잡도 섹션 appendasync function tagSingleFile( - file, + fileEntry, + complexityPromise, repoOwner, repoName, prNumber, headSha, appToken, openaiApiKey ) { - // 파일 내용 가져오기 - const contentResponse = await fetch(file.raw_url); - if (!contentResponse.ok) throw new Error(...); - let fileContent = await contentResponse.text(); - if (fileContent.length > MAX_FILE_SIZE) fileContent = fileContent.slice(0, MAX_FILE_SIZE); - const problemName = file.filename.split("/")[0]; + const { file, problemName, content: fileContent } = fileEntry; const analysis = await generatePatternAnalysis(fileContent, problemName, openaiApiKey); const patternsText = analysis.patterns.length > 0 ? analysis.patterns.join(", ") : "감지된 패턴 없음"; - const body = `${COMMENT_MARKER} + let body = `${COMMENT_MARKER} ### 🏷️ 알고리즘 패턴 분석 - **패턴**: ${patternsText} - **설명**: ${analysis.description || "(설명 없음)"}`; + // 복잡도 섹션을 한 블록 더 붙인다 (실패 시 그냥 스킵 — 패턴은 이미 성공) + const complexityResults = await complexityPromise; + const complexityForFile = complexityResults.find((r) => r.problemName === problemName); + if (complexityForFile) { + body += "\n\n" + renderComplexitySection(complexityForFile); + } // 파일 단위 review comment 작성 (기존 그대로) const commentResponse = await fetch(...); ... }
renderComplexitySection은complexity-analysis.js의 기존formatComplexityCommentBody에서 한 파일분만 렌더링하는 형태로 추출 (헤더/푸터 제외,### 📊 시간/공간 복잡도 분석부터 시작). 추출 후 export.3.3
handlers/webhooks.js— 디스패치 정리- // 복잡도 분석 디스패치 - ctx.waitUntil( - (async () => { ... fetch(`${baseUrl}/internal/complexity-analysis`, ...) })() - ); - - console.log(`[handlePullRequestEvent] Dispatched 3 AI handlers for PR #${prNumber}`); + console.log(`[handlePullRequestEvent] Dispatched 2 AI handlers for PR #${prNumber}`);
폴백 경로(INTERNAL_SECRET 미설정 시)에서도
analyzeComplexity호출 블록 제거. import 도 정리.3.4
handlers/internal-dispatch.js-import { analyzeComplexity } from "./complexity-analysis.js"; ... - case "/internal/complexity-analysis": - return await handleComplexityAnalysis(payload, appToken, env); ... -async function handleComplexityAnalysis(payload, appToken, env) { ... }
3.5
handlers/complexity-analysis.js— 잔여 정리같은 PR에서 함께 정리:
analyzeComplexity(오케스트레이션) 제거upsertComplexityComment제거formatComplexityCommentBody는renderComplexitySection(한 파일분) 으로 축소 후 exportcallComplexityAnalysis,composeSolution,extractUserAnnotations,bigOEquals,cleanBigO,isComplexityCommentLine,stripComplexityComments,extractBigO— 모두 유지 (export 도 유지)
4. 댓글 본문 예시
<!-- dalestudy-pattern-tag --> ### 🏷️ 알고리즘 패턴 분석 - **패턴**: Two Pointers, Hash Map - **설명**: 정렬된 배열을 양 끝 포인터로 좁혀가며 합을 맞춥니다. ### 📊 시간/공간 복잡도 분석 | | 유저 분석 | 실제 분석 | 결과 | |---|---|---|---| | **Time** | O(n) | O(n) | ✅ | | **Space** | O(1) | O(n) | ❌ | **피드백**: 해시맵을 사용하므로 공간은 O(n)입니다. **개선 제안**: 정렬 후 투포인터로 O(1) 공간 가능합니다.
복잡도 분석이 실패했거나 해당 problemName 결과가 없으면 복잡도 섹션은 통째로 생략 — 패턴 댓글은 그대로 작성.
5. 테스트 갱신
5.1
tests/subrequest-budget.test.jstagPatterns5파일 기댓값: 22 → 25 으로 갱신 (raw pre-loop 5 + 패턴 OpenAI 5 + POST 5 + DELETE 5 + PR files 1 + 코멘트 목록 1 + 복잡도 OpenAI 1 + 마이그레이션 GET 1 = 24, 마이그레이션 DELETE 1회 시 25). 실제 mock 동작에 맞춰 정확히 박는다.analyzeComplexity테스트는 삭제 (함수 제거).
5.2
handlers/tag-patterns.test.js기존 케이스는 시그니처/호출 그대로 유지되도록 fetch mock에 다음을 추가:
- 복잡도 OpenAI 응답 (problemName 별 결과)
- 마이그레이션용 issue comments GET/DELETE
새 케이스:
- 복잡도 OpenAI 가 reject → 패턴 댓글만 정상 작성, 복잡도 섹션 없음
- 복잡도 결과가 비어 있음 (
[]) → 패턴 댓글만 작성 - 기존 단독 복잡도 issue comment 가 있을 때 → DELETE 1회 호출
- 기존 단독 복잡도 issue comment 가 없을 때 → DELETE 호출 안 함
- 정상 케이스 → 본문에
PATTERN_MARKER하나 + 두 섹션 모두 포함 (COMPLEXITY_MARKER는 박지 않음)
5.3
handlers/complexity-analysis.test.jsanalyzeComplexity케이스 삭제callComplexityAnalysis,composeSolution,extractUserAnnotations,bigOEquals등 단위 테스트는 그대로 유지
5.4
handlers/internal-dispatch.test.js/internal/complexity-analysis케이스 삭제
6. 구현 순서
커밋 단위 권장. 각 단계 후
bun test handlers/ && bun test tests/통과 확인.callComplexityAnalysisexport +formatComplexityCommentBody를renderComplexitySection(한 파일분) 으로 추출/export.tag-patterns.js에downloadFileEntries,deleteLegacyComplexityIssueComment,tagSingleFile시그니처/본문 변경,tagPatterns본체에 pre-loop/post-loop 추가.tag-patterns.test.js갱신 + 새 케이스 추가,subrequest-budget.test.js의tagPatterns기댓값 갱신.webhooks.js와internal-dispatch.js에서 complexity 디스패치/라우팅 제거.complexity-analysis.js잔여 정리 (analyzeComplexity,upsertComplexityComment,formatComplexityCommentBody제거), 관련 테스트 삭제.AGENTS.md의 "AI 핸들러 Worker 분리 아키텍처" 섹션을 2개 핸들러(tag-patterns, learning-status)로 갱신.
7. 수동 검증
- 솔루션 1개 PR → 합본 댓글 1개에 두 섹션 모두 표시
- 솔루션 5개 PR → 5개 합본 댓글 + 기존 단독 복잡도 댓글 자동 삭제 확인
- synchronize → 변경된 파일에만 합본 댓글 갱신, 다른 파일 댓글은 그대로
- OpenAI 키 누락 환경 → 디스패치 자체가 안 일어나서 댓글 없음 (기존 동작 유지)
- 복잡도 OpenAI만 일시 실패하도록 mock → 패턴 섹션만 있는 댓글 게시 확인 (단위 테스트로 대체 가능)
8. 롤백
문제 발생 시 PR 리버트로 한 번에 복구. complexity-analysis.js 파일 자체는 분석 함수와 헬퍼만 남고 오케스트레이션이 빠진 상태이므로, 리버트 시 자동으로 원상 복구.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone