Skip to content

시공간 복잡도 분석 + 알고리즘 패턴 태킹 코드 합치기 #31

Description

@sounmind
No description provided.

Activity

  1. moved this from Todo to No status in AI 프로젝트 1기on Apr 18, 2026
  2. added a commit that references this issue on May 1, 2026
    f3c1f69
  3. lkhoony commented on May 1, 2026

    @lkhoony
    Contributor

    요구사항

    알고리즘 패턴 태깅이 일어나는 시점에 시간/공간 복잡도 분석 결과도 같은 댓글에 함께 표시한다. 두 기능이 별도 webhook 디스패치로 동작하더라도 타이밍 이슈 없이 합쳐지도록 한다.

    진행 과정

    1. research.md — 현재 구조 분석. 패턴 태깅은 파일별 review comment(/pulls/{n}/comments), 복잡도 분석은 PR 단위 issue comment(/issues/{n}/comments) 로 위치가 다르고, 각자 별도 Worker invocation 으로 디스패치되고 있음을 확인. 4개 옵션 비교 후 합본 핸들러(옵션 A) 채택.

    2. plan.md — "패턴 태깅에 묻어가는" 형태로 최소 변경 계획 수립. MAX_FILES 가드는 불필요(기존 패턴 태깅 자체에 13파일+에서 한도 cliff 가 있어 합본은 1파일분만 당기는 정도) 임을 계산으로 검증.

    3. 구현 — 두 분석을 같은 invocation 안에서 Promise 기반 병렬 실행. 모든 raw 를 사전에 한 번만 다운로드해 패턴 N콜과 복잡도 1콜이 공유. 댓글 본문에 복잡도 섹션 한 블록 append. 구버전 단독 복잡도 issue comment 는 첫 동작 시 자동 정리.

    4. 테스트 — bun test handlers/ 75/75, bun test tests/ 17/17 통과. tests/tag-patterns.test.js 신규 추가(skip 가드, 합본, 마이그레이션, 패턴 정리, 부분 실패), tests/subrequest-budget.test.js 5파일 기준 22 → 25 갱신.

    결과

    • 디스패치 invocation 3개 → 2개 (tag-patterns, learning-status)
    • complexity-analysis 모듈은 분석 함수와 한 파일분 섹션 렌더러만 export 하는 형태로 슬림화
    • subrequest 예산: 5파일 25회 / 10파일 44~45회 (한도 50 안)
    • 타이밍 이슈는 두 분석이 같은 함수 안에서 병렬 처리되므로 원천 차단

    PR

    #43

  4. lkhoony commented on May 1, 2026

    @lkhoony
    Contributor

    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 -->
    analyzeComplexity PR 단위 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 + 3
    

    5파일 기준 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 가 따로 존재. 새 코드 배포 후 첫 동작 시:

    1. 기존 issue comment(<!-- dalestudy-complexity-analysis -->) 삭제 — review comment에 통합되었으니 중복.
    2. 새 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. 단계별 작업 순서 (제안)

    1. 분석 함수 분리 — tag-patterns.js의 OpenAI 호출 부분을 analyzePatternsForFiles(fileEntries) 같은 순수 함수로 추출. complexity-analysis.js의 callComplexityAnalysis도 이미 분리되어 있음.
    2. 새 통합 핸들러 작성 — 두 분석을 Promise.allSettled로 병렬 호출, 파일별 합본 본문 생성, review comment upsert.
    3. 마이그레이션 로직 — 통합 핸들러 첫 동작 시 기존 dalestudy-complexity-analysis issue comment 삭제.
    4. 디스패처 변경 — webhooks.js에서 complexity-analysis 디스패치 제거, internal-dispatch 라우팅도 정리.
    5. 테스트 갱신 — subrequest-budget 회귀 + 새 통합 테스트.
    6. 문서 업데이트 — 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 안전성 강화로 별도 작업 가능.
  5. lkhoony commented on May 1, 2026

    @lkhoony
    Contributor

    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 끝에 복잡도 섹션 append

     async 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(한 파일분) 으로 축소 후 export
    • callComplexityAnalysis, 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.js

    • tagPatterns 5파일 기댓값: 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.js

    • analyzeComplexity 케이스 삭제
    • callComplexityAnalysis, composeSolution, extractUserAnnotations, bigOEquals 등 단위 테스트는 그대로 유지

    5.4 handlers/internal-dispatch.test.js

    • /internal/complexity-analysis 케이스 삭제

    6. 구현 순서

    커밋 단위 권장. 각 단계 후 bun test handlers/ && bun test tests/ 통과 확인.

    1. callComplexityAnalysis export + formatComplexityCommentBody 를 renderComplexitySection(한 파일분) 으로 추출/export.
    2. tag-patterns.js 에 downloadFileEntries, deleteLegacyComplexityIssueComment, tagSingleFile 시그니처/본문 변경, tagPatterns 본체에 pre-loop/post-loop 추가.
    3. tag-patterns.test.js 갱신 + 새 케이스 추가, subrequest-budget.test.js 의 tagPatterns 기댓값 갱신.
    4. webhooks.js 와 internal-dispatch.js 에서 complexity 디스패치/라우팅 제거.
    5. complexity-analysis.js 잔여 정리 (analyzeComplexity, upsertComplexityComment, formatComplexityCommentBody 제거), 관련 테스트 삭제.
    6. AGENTS.md 의 "AI 핸들러 Worker 분리 아키텍처" 섹션을 2개 핸들러(tag-patterns, learning-status)로 갱신.

    7. 수동 검증

    1. 솔루션 1개 PR → 합본 댓글 1개에 두 섹션 모두 표시
    2. 솔루션 5개 PR → 5개 합본 댓글 + 기존 단독 복잡도 댓글 자동 삭제 확인
    3. synchronize → 변경된 파일에만 합본 댓글 갱신, 다른 파일 댓글은 그대로
    4. OpenAI 키 누락 환경 → 디스패치 자체가 안 일어나서 댓글 없음 (기존 동작 유지)
    5. 복잡도 OpenAI만 일시 실패하도록 mock → 패턴 섹션만 있는 댓글 게시 확인 (단위 테스트로 대체 가능)

    8. 롤백

    문제 발생 시 PR 리버트로 한 번에 복구. complexity-analysis.js 파일 자체는 분석 함수와 헬퍼만 남고 오케스트레이션이 빠진 상태이므로, 리버트 시 자동으로 원상 복구.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions