@@ -53,7 +53,7 @@ public class DecisionFlowService {
5353 @ PersistenceContext
5454 private EntityManager entityManager ;
5555
56- // 가장 중요한: from-base 생성 시 main 브랜치를 보장
56+ // from-base 생성 시 main 브랜치를 보장
5757 @ Transactional
5858 public DecNodeDto createDecisionNodeFromBase (DecisionNodeFromBaseRequest request ) {
5959 // 한줄 요약: 피벗/옵션 검증 후 라인 생성하고 main 브랜치를 반드시 부착
@@ -75,7 +75,7 @@ public DecNodeDto createDecisionNodeFromBase(DecisionNodeFromBaseRequest request
7575 support .ensureOwnerOfBaseLine (request .userId (), pivot .getBaseLine ());
7676 int sel = support .requireAltIndex (request .selectedAltIndex ());
7777
78- // 한줄 요약(가장 많이 사용하는 호출): main 브랜치가 없으면 생성+init 커밋까지 보장
78+ // main 브랜치가 없으면 생성+init 커밋까지 보장
7979 BaselineBranch main = ensureMainBranch (pivot .getBaseLine ().getId (), pivot .getUser ().getId ());
8080
8181 // === 크리티컬 섹션 시작: 같은 pivot/slot 동시성 차단 ===
@@ -140,8 +140,8 @@ public DecNodeDto createDecisionNodeFromBase(DecisionNodeFromBaseRequest request
140140 : baseNodeRepository .linkAlt2IfEmpty (fresh .getId (), saved .getId ());
141141 if (updated == 0 ) throw new ApiException (ErrorCode .INVALID_INPUT_VALUE , "branch slot was taken by another request" );
142142
143- // ★ 추가: (코리더 한정) 숨은 노드 포함 옵션 동기화 — 증분 Append, 순서 보존
144- // 가장 많이 사용하는 함수 호출 위에 한줄로만 요약 주석: 트리거 옵션 결정(입력값 우선, 없으면 엔티티에서 추출)
143+ // 숨은 노드 포함 옵션 동기화 — 증분 Append, 순서 보존
144+ // 트리거 옵션 결정(입력값 우선, 없으면 엔티티에서 추출)
145145 List <String > leaderOpts = (opts != null && !opts .isEmpty ()) ? opts : support .extractOptions (saved );
146146 syncOptionsAcrossAgeWithinCorridorLite (saved , leaderOpts );
147147
@@ -166,7 +166,7 @@ public DecNodeDto createDecisionNodeFromBase(DecisionNodeFromBaseRequest request
166166 });
167167 }
168168
169- // 한줄 요약: BaseLine에서 main 브랜치를 보장하고, 없으면 생성 + init 커밋 연결 후 반환
169+ // BaseLine에서 main 브랜치를 보장하고, 없으면 생성 + init 커밋 연결 후 반환
170170 private BaselineBranch ensureMainBranch (Long baseLineId , Long authorUserId ) {
171171 return branchRepo .findByBaseLine_IdAndName (baseLineId , "main" )
172172 .orElseGet (() -> {
@@ -263,7 +263,7 @@ public DecNodeDto createDecisionNodeNext(DecisionNodeNextRequest request) {
263263 // 라인 취소 후 피벗 슬롯 언링크 + 슬롯 텍스트 비우기
264264 @ Transactional
265265 public DecisionLineLifecycleDto cancelDecisionLine (Long decisionLineId ) {
266- // 한줄 요약: 라인을 취소하고, 피벗에서 시작된 첫 결정 노드를 기준으로 분기 슬롯 링크 해제 및 슬롯 텍스트 제거
266+ // 라인을 취소하고, 피벗에서 시작된 첫 결정 노드를 기준으로 분기 슬롯 링크 해제 및 슬롯 텍스트 제거
267267 DecisionLine line = support .requireDecisionLine (decisionLineId );
268268
269269 Long currentUserId = currentUserId ();
@@ -278,7 +278,7 @@ public DecisionLineLifecycleDto cancelDecisionLine(Long decisionLineId) {
278278 }
279279 decisionLineRepository .save (line );
280280
281- // 한줄 요약(가장 많이 쓰는 호출): 시간순으로 읽어와 '피벗 기원' 첫 노드 찾기
281+ // 시간순으로 읽어와 '피벗 기원' 첫 노드 찾기
282282 decisionNodeRepository .findByDecisionLine_IdOrderByAgeYearAscIdAsc (line .getId ())
283283 .stream ()
284284 .filter (d -> d .getParentOptionIndex () != null ) // 피벗에서 생성된 첫 노드
@@ -291,7 +291,7 @@ public DecisionLineLifecycleDto cancelDecisionLine(Long decisionLineId) {
291291 Long decisionId = pivotDec .getId ();
292292 Integer slot = pivotDec .getParentOptionIndex ();
293293
294- // 한줄 요약(가장 많이 쓰는 호출): 선택 슬롯(0/1)에 맞춰 정확히 언링크
294+ // 선택 슬롯(0/1)에 맞춰 정확히 언링크
295295 if (slot != null && slot == 0 ) {
296296 baseNodeRepository .unlinkAlt1IfMatches (pivotId , decisionId );
297297 } else if (slot != null && slot == 1 ) {
@@ -335,22 +335,21 @@ public DecisionLineLifecycleDto completeDecisionLine(Long decisionLineId) {
335335 return new DecisionLineLifecycleDto (line .getId (), line .getStatus ());
336336 }
337337
338- // 가장 중요한 함수: 포크 지점 생성 시 옵션 교체(from-base와 동일 규칙) 반영
339- // 가장 중요한 함수 한줄 요약: 기존 라인을 부모로 하여 특정 결정노드에서 포크 라인을 만들고 AI 힌트를 엔티티에도 저장
338+ // 기존 라인을 부모로 하여 특정 결정노드에서 포크 라인을 만들고 AI 힌트를 엔티티에도 저장
340339 @ Transactional
341340 public DecNodeDto forkFromDecision (ForkFromDecisionRequest req ) {
342341 if (req == null || req .parentDecisionNodeId () == null )
343342 throw new ApiException (ErrorCode .INVALID_INPUT_VALUE , "parentDecisionNodeId is required" );
344343 if (req .targetOptionIndex () == null || req .targetOptionIndex () < 0 || req .targetOptionIndex () > 2 )
345344 throw new ApiException (ErrorCode .INVALID_INPUT_VALUE , "targetOptionIndex out of range" );
346345
347- // 가장 많이 사용하는 호출 한줄 요약: 부모 노드 로드
346+ // 부모 노드 로드
348347 DecisionNode parent = decisionNodeRepository .findById (req .parentDecisionNodeId ())
349348 .orElseThrow (() -> new ApiException (ErrorCode .NODE_NOT_FOUND ,
350349 "Parent DecisionNode not found: " + req .parentDecisionNodeId ()));
351350 DecisionLine originLine = parent .getDecisionLine ();
352351
353- // ★ 새 라인에 원본 라인 id 저장(나머지 흐름 동일)
352+ // 새 라인에 원본 라인 id 저장(나머지 흐름 동일)
354353 DecisionLine newLine = decisionLineRepository .save (
355354 DecisionLine .builder ()
356355 .user (originLine .getUser ())
@@ -363,7 +362,7 @@ public DecNodeDto forkFromDecision(ForkFromDecisionRequest req) {
363362
364363 List <BaseNode > orderedBase = support .getOrderedBaseNodes (originLine .getBaseLine ().getId ());
365364
366- // 가장 중요한 함수 위에 한줄로만 요약 주석: 베이스 헤더(BaseNode.parent==null) 선별
365+ // 베이스 헤더(BaseNode.parent==null) 선별
367366 BaseNode baseHeader = null ;
368367 for (BaseNode b : orderedBase ) {
369368 if (b .getParent () == null ) { baseHeader = b ; break ; }
@@ -373,11 +372,11 @@ public DecNodeDto forkFromDecision(ForkFromDecisionRequest req) {
373372 DecisionNode prevNew = null ;
374373
375374 List <DecisionNode > orderedOrigin = decisionNodeRepository
376- // 가장 많이 사용하는 호출 한줄 요약: 기존 라인의 노드 목록을 타임라인 정렬로 로드
375+ // 기존 라인의 노드 목록을 타임라인 정렬로 로드
377376 .findByDecisionLine_IdOrderByAgeYearAscIdAsc (originLine .getId ());
378377
379378 DecNodeDto forkPointDto = null ;
380- // 가장 많이 사용하는 호출 한줄 요약: 포크 앵커 엔티티를 추적해 AI 힌트 저장에 사용
379+ // 포크 앵커 엔티티를 추적해 AI 힌트 저장에 사용
381380 DecisionNode forkAnchorSaved = null ;
382381
383382 for (DecisionNode n : orderedOrigin ) {
@@ -387,7 +386,7 @@ public DecNodeDto forkFromDecision(ForkFromDecisionRequest req) {
387386
388387 BaseNode matchedBase = null ;
389388 try {
390- // 가장 많이 사용하는 호출 한줄 요약: 헤더면 베이스 헤더로, 아니면 age로 매칭
389+ // 헤더면 베이스 헤더로, 아니면 age로 매칭
391390 if (n .getParent () == null ) {
392391 matchedBase = baseHeader ; // 헤더는 항상 베이스 헤더로 고정
393392 } else {
@@ -400,9 +399,9 @@ public DecNodeDto forkFromDecision(ForkFromDecisionRequest req) {
400399 Integer selIdx = n .getSelectedIndex ();
401400
402401 if (isParent ) {
403- // ★ 포크 앵커에서 선택지 교체/강제 규칙
402+ // 포크 앵커에서 선택지 교체/강제 규칙
404403 if (req .options () != null && !req .options ().isEmpty ()) {
405- // 가장 많이 사용하는 호출 한줄 요약: 옵션 검증(1~3개, selectedIndex 일치)
404+ // 옵션 검증(1~3개, selectedIndex 일치)
406405 support .validateOptions (
407406 req .options (),
408407 req .selectedIndex (),
@@ -427,15 +426,15 @@ public DecNodeDto forkFromDecision(ForkFromDecisionRequest req) {
427426 String finalDecision =
428427 (options != null && selIdx != null && selIdx >= 0 && selIdx < options .size ())
429428 ? options .get (selIdx )
430- : situation ; // 옵션 없으면 상황으로 폴백
429+ : situation ;
431430
432431 String background = support .resolveBackground (situation );
433432 NodeMappers .DecisionNodeCtxMapper mapper =
434433 mappers .new DecisionNodeCtxMapper (n .getUser (), newLine , prevNew , matchedBase , background );
435434
436435 Long newParentId = (n .getParent () == null ) ? null : (prevNew != null ? prevNew .getId () : null );
437436
438- // ★★★★★ 핵심(원본 유지): 포크 앵커에서 parentOptionIndex 강제 → 라벨러가 'fork'로 인식
437+ // 포크 앵커에서 parentOptionIndex 강제 -> 라벨러가 'fork'로 인식
439438 Integer parentOptionIndexForCreate = isParent ? req .targetOptionIndex () : null ;
440439
441440 DecisionNodeCreateRequestDto createReq = new DecisionNodeCreateRequestDto (
@@ -455,7 +454,7 @@ public DecNodeDto forkFromDecision(ForkFromDecisionRequest req) {
455454 DecisionNode saved = decisionNodeRepository .save (mapper .toEntity (createReq ));
456455 prevNew = saved ;
457456
458- // ★ 추가: 포크 진행 중에도 해당 age에서 코리더 한정 동기화(프렐류드 포함)
457+ // 포크 진행 중에도 해당 age에서 코리더 한정 동기화(프렐류드 포함)
459458 List <String > leaderOptsHere = (options != null && !options .isEmpty ())
460459 ? options
461460 : support .extractOptions (saved );
@@ -470,12 +469,12 @@ public DecNodeDto forkFromDecision(ForkFromDecisionRequest req) {
470469 if (forkPointDto == null )
471470 throw new ApiException (ErrorCode .INVALID_INPUT_VALUE , "fork parent not materialized" );
472471
473- // 가장 많이 사용하는 호출 한줄 요약: 새 라인의 노드를 타임라인 정렬로 재조회하여 AI 힌트 생성
472+ // 새 라인의 노드를 타임라인 정렬로 재조회하여 AI 힌트 생성
474473 List <DecisionNode > orderedNew =
475474 decisionNodeRepository .findByDecisionLine_IdOrderByAgeYearAscIdAsc (forkPointDto .decisionLineId ());
476475 var hint = aiVectorService .generateNextHint (forkPointDto .userId (), forkPointDto .decisionLineId (), orderedNew );
477476
478- // 가장 많이 사용하는 호출 한줄 요약: 생성된 AI 힌트를 앵커 엔티티에 저장(영속)
477+ // 생성된 AI 힌트를 앵커 엔티티에 저장(영속)
479478 if (forkAnchorSaved != null ) {
480479 forkAnchorSaved .setAiHint (hint .aiNextSituation (), hint .aiNextRecommendedOption ());
481480 decisionNodeRepository .save (forkAnchorSaved );
@@ -561,12 +560,8 @@ private DecisionNode createPreludeUntilPivot(DecisionLine line,
561560 return prev ;
562561 }
563562
564- /**
565- * [추가/가장 중요한 함수] 코리더(공통 경로) 한정 옵션 동기화(Lite)
566- * - 같은 BaseLine + 같은 ageYear 중, 첫 분기 나이(첫 from-base 또는 첫 fork)가 segAge 이상인 라인만 포함
567- * - 포함된 라인의 동일 age 노드(노말/프렐류드/from-base/포크)에 리더 옵션을 증분 Append로 반영(순서 보존)
568- */
569- // 가장 중요한 함수 한줄로만 요약: 코리더 집합(첫 분기 나이 ≥ segAge) 안의 동일 age 노드를 증분 동기화
563+
564+ // 코리더 집합(첫 분기 나이 ≥ segAge) 안의 동일 age 노드를 증분 동기화
570565 @ Transactional
571566 protected void syncOptionsAcrossAgeWithinCorridorLite (DecisionNode trigger , List <String > triggerOptions ) {
572567 if (trigger == null || trigger .getDecisionLine () == null ) return ;
@@ -576,11 +571,11 @@ protected void syncOptionsAcrossAgeWithinCorridorLite(DecisionNode trigger, List
576571 if (segAge == null || triggerLine .getBaseLine () == null ) return ;
577572 final Long baseLineId = triggerLine .getBaseLine ().getId ();
578573
579- // 가장 많이 사용하는 함수 호출 한줄로만 요약: 베이스라인의 모든 라인 조회
574+ // 베이스라인의 모든 라인 조회
580575 List <DecisionLine > allLines = decisionLineRepository .findByBaseLine_Id (baseLineId );
581576 if (allLines == null || allLines .isEmpty ()) return ;
582577
583- // 한줄 요약: 라인별 첫 from-base 나이/첫 fork 나이 계산
578+ // 라인별 첫 from-base 나이/첫 fork 나이 계산
584579 Map <Long , LineBoundary > boundaryByLine = new HashMap <>();
585580 for (DecisionLine ln : allLines ) {
586581 Integer fromBaseAge = null , forkAge = null ;
@@ -593,7 +588,7 @@ protected void syncOptionsAcrossAgeWithinCorridorLite(DecisionNode trigger, List
593588 boundaryByLine .put (ln .getId (), new LineBoundary (fromBaseAge , forkAge ));
594589 }
595590
596- // 한줄 요약: 첫 분기 경계 나이가 segAge 이상인 라인만 코리더로 선별
591+ // 첫 분기 경계 나이가 segAge 이상인 라인만 코리더로 선별
597592 Set <Long > corridorLineIds = new HashSet <>();
598593 for (DecisionLine ln : allLines ) {
599594 if (ln .getStatus () == DecisionLineStatus .CANCELLED ) continue ;
@@ -666,7 +661,7 @@ protected void syncOptionsAcrossAgeWithinCorridorLite(DecisionNode trigger, List
666661 }
667662 }
668663
669- // 가장 많이 사용하는 함수 호출 한줄로만 요약: from-base 표식 여부(피벗 슬롯 타깃 매칭으로 판정)
664+ // from-base 표식 여부(피벗 슬롯 타깃 매칭으로 판정)
670665 private boolean isFromBaseMark (DecisionNode n ) {
671666 BaseNode b = n .getBaseNode ();
672667 if (b == null ) return false ;
@@ -675,15 +670,15 @@ private boolean isFromBaseMark(DecisionNode n) {
675670 || Objects .equals (b .getAltOpt2TargetDecisionId (), id );
676671 }
677672
678- // 가장 중요한 함수 위에 한줄로만 요약 주석: 라인의 첫 분기 경계 나이 스냅샷 컨테이너
673+ // 라인의 첫 분기 경계 나이 스냅샷 컨테이너
679674 private static final class LineBoundary {
680675 final Integer fromBaseAge ; // null 허용
681676 final Integer forkAge ; // null 허용
682677 LineBoundary (Integer fromBaseAge , Integer forkAge ) {
683678 this .fromBaseAge = fromBaseAge ;
684679 this .forkAge = forkAge ;
685680 }
686- // 한줄 요약: 첫 분기 경계 나이(없으면 무한대)
681+ // 첫 분기 경계 나이(없으면 무한대)
687682 int firstDivergenceAge () {
688683 Integer a = (forkAge != null ) ? forkAge : fromBaseAge ;
689684 return (a != null ) ? a : Integer .MAX_VALUE ;
0 commit comments