Skip to content

v0.3.34.40

Choose a tag to compare

@dogsinatas29 dogsinatas29 released this 30 Aug 08:28
· 8 commits to main since this release

v0.3.34.40 Implementation Log

Date

2026-08-26

Status

In Progress


Phase 1 - Boundary Candidate Generation Audit Implementation

Files Modified

1. src/core/analysis/analyzers/BoundaryGraphBuilder.ts

Changes:

  1. Extended BoundaryCandidateAudit interface (line 21-31):
export interface BoundaryCandidateAudit {
    id: string;
    members: number;
    internalEdges: number;
    externalEdges: number;
    cohesion: number;
    targetConcentration: number;
    result: 'PROMOTED' | 'REJECT_SMALL' | 'REJECT_WEAK' | 'WRAPPER';
    memberFiles?: string[]; // v0.3.34.40: Track actual member files for audit
    depth?: number; // v0.3.34.40: Track candidate depth in prefix tree
}
  1. Added Initial Groups dump (line 79-85):
// v0.3.34.40: Dump initial grouping for audit
console.log(`[BOUNDARY_DISCOVERY_START] Queued ${queue.length} top-level candidates from ${nodes.length} nodes.`);
console.log(`[CANDIDATE_GEN_AUDIT] Initial Groups:`);
for (const [id, members] of initialGroups.entries()) {
    console.log(`  ${id}: ${members.length} nodes`);
}
  1. Added candidate details dump (line 201-215):
auditLog.push({
    id: candidate.id,
    members: memberIds.length,
    internalEdges,
    externalEdges,
    cohesion: parseFloat(cohesion.toFixed(3)),
    targetConcentration: parseFloat(targetConcentration.toFixed(3)),
    result: auditResult,
    memberFiles: memberIds.slice(0, 20), // v0.3.34.40: Track first 20 member files for audit
    depth: candidate.id.split('/').length // v0.3.34.40: Track depth in prefix tree
});

// v0.3.34.40: Dump candidate details for audit
console.log(`[CANDIDATE_GEN_AUDIT] ${candidate.id}: members=${memberIds.length}, depth=${candidate.id.split('/').length}, result=${auditResult}`);
if (memberIds.length <= 20) {
    console.log(`  Files: ${memberIds.join(', ')}`);
} else {
    console.log(`  Files (first 20): ${memberIds.slice(0, 20).join(', ')}...`);
}

2. src/core/analysis/analyzers/BoundaryAnalyzer.ts

Changes:

Added memberFiles and depth to metadata (line 67-86):

findings.push({
    type: 'semantic',
    evidenceType: EvidenceType.REJECTED_CANDIDATE,
    targetId: candidate.id,
    message: `Rejected Boundary Candidate: ${candidate.id} (${candidate.result})`,
    metadata: {
        members: candidate.members,
        internalEdges: candidate.internalEdges,
        externalEdges: candidate.externalEdges,
        cohesion: candidate.cohesion,
        targetConcentration: candidate.targetConcentration,
        result: candidate.result,
        memberFiles: candidate.memberFiles, // v0.3.34.40: Pass member files
        depth: candidate.depth // v0.3.34.40: Pass depth
    }
});

3. src/core/reporting/BoundaryAnalysisReportBuilder.ts

Changes:

Added Member Files and Depth output (line 49-70):

for (const candidate of rejectedCandidates) {
    const meta = candidate.metadata || {};
    const result = meta.result || 'UNKNOWN';
    
    content += `[${result}] ${candidate.targetId}\n`;
    content += `  Members: ${meta.members || 0}\n`;
    content += `  Depth: ${meta.depth || 'N/A'}\n`;
    content += `  Internal Edges: ${meta.internalEdges || 0}\n`;
    content += `  External Edges: ${meta.externalEdges || 0}\n`;
    
    const cohesionStr = meta.cohesion !== undefined ? meta.cohesion.toFixed(3) : '0.000';
    content += `  Cohesion: ${cohesionStr}\n`;
    
    const concentrationStr = meta.targetConcentration !== undefined ? meta.targetConcentration.toFixed(3) : '0.000';
    content += `  Target Concentration: ${concentrationStr}\n`;
    
    // v0.3.34.40: Output member files for audit
    if (meta.memberFiles && Array.isArray(meta.memberFiles) && meta.memberFiles.length > 0) {
        content += `  Member Files:\n`;
        for (const file of meta.memberFiles) {
            content += `    - ${file}\n`;
        }
    }
    
    content += `\n`;
}

Verification Results

SC-8: TypeScript Compilation

npx tsc --noEmit

Status: ✅ PASS (0 errors)

SC-9: Webpack Build

npm run compile

Status: ✅ PASS (1 warning, 0 errors)


SYNAPSE Report Analysis (2026-08-26)

BoundaryAnalysisReport.md Results

Total Rejected Candidates: 107

Key Findings - Problem Candidates

Candidate Members Depth Internal Edges External Edges Cohesion Result
src/webview 1 2 0 27 0.000 REJECT_SMALL
src/bootstrap 1 2 0 27 0.000 REJECT_SMALL
src/server 4 2 0 26 0.000 REJECT_SMALL
src/core/collaboration 17 3 14 38 0.269 REJECT_SMALL
src/core/metrology 25 3 13 10 0.565 REJECT_SMALL

Promoted Boundaries (3개)

Subsystem Members Internal Edges External Edges Cohesion Strength
src/core/analysis 39 47 37 0.56 Weak
src/core/ir 12 29 10 0.74 Weak
src/core/reasoning 60 139 13 0.91 Moderate

Wrapper Classifications (정상 동작)

Subsystem Members Depth Internal Edges External Edges Cohesion
src 393 1 697 59 0.922
src/core 243 2 412 149 0.734
src/vs 85 2 0 0 0.000
src/vs/workbench 72 3 0 0 0.000

ARCHITECT_REPORT.md Analysis

Frontier Nodes (3개)

  • src/core/analysis (Subsystem: src/core/analysis, Boundary Strength: Weak)
  • src/core/ir (Subsystem: src/core/ir, Boundary Strength: Weak)
  • src/cli (Subsystem: None / Unbounded)

Unknown Subsystem Nodes

  • src/core/collaboration
  • src/server
  • src/webview
  • src/bootstrap
  • src/utils
  • src/core/validation
  • src/core/canvas-engine
  • src/core/transaction

INTENDED Nodes (1개)

  • src/core/reasoning (Subsystem: src/core/reasoning, Boundary Strength: Moderate)

Observation: Boundary가 감지되지 않은 영역들이 모두 Unknown으로 표시됨. Architect Layer는 Boundary Detector가 승격한 것만 인식.


EXECUTIVE_SUMMARY.md Analysis

Architecture Health: WARNING
Frontier Observation: 3 non-dominated structural hotspots observed.
Business Impact: No single node dominates another across all dimensions.
Recommended Action: 3 frontier nodes identified for review.

Status: 아직 가치 낮음. Boundary Audit 정보를 반영하지 못함.


ONBOARDING_REPORT.md Analysis

Reading Path

  1. Entry Point: src/extension.ts ✅
  2. Core Pipeline: src/core/analysis/types.ts ⚠️
  3. Core Pipeline: src/core/GraphModel.ts
  4. Core Pipeline: src/core/ir/models/GeneratorInterfaces.ts ⚠️
  5. Core Pipeline: src/core/ir/models/SemanticTypes.ts ⚠️

Problem

  • 타입 파일들이 Core Pipeline 1순위
  • FanIn 기반 추론 오류
  • 실제 실행 흐름과 다름

Safe Areas

  • src/utils/
  • src/types/

SIMULATION_DEBUG.md Analysis

Impact Analysis

  • Immediate Impact: 5 files

    • src/core/analysis/types.ts
    • src/core/analysis/InterventionSimulator.ts
    • src/core/analysis/reasoning/TarjanSCC.ts
    • src/extension.ts
    • src/core/benchmark/BenchmarkHarness.ts
  • Blast Radius: 669 files

Status: 비교적 양호. Boundary와 독립적.


Root Cause Analysis

발견된 원인

사용자 가설 검증 결과: 100% 정확

  1. BoundaryDetector 무죄

    • 승격 알고리즘 정상 동작
    • src/core/analysis, src/core/ir, src/core/reasoning 승격 성공
    • Wrapper 판정 정상
  2. Candidate Generation 유죄

    • src/webview가 Members=1로 들어옴
    • src/server가 Members=4로 들어옴
    • src/bootstrap이 Members=1로 들어옴
    • 후보 생성 단계에서 이미 잘게 쪼개짐
  3. MIN_INTERNAL_EDGES=20이 강한 필터

    • src/core/collaboration: Members=17, Internal=14 → 탈락
    • src/core/metrology: Members=25, Internal=13 → 탈락
    • 실제로 의미 있는 subsystem이지만 threshold 때문에 탈락

핵심 문제

  1. Candidate Generation Issue

    • src/webview 폴더에는 실제로 CanvasPanel.ts 하나만 존재
    • src/server는 4개 파일만 포함
    • Boundary 후보가 너무 작게 생성됨
  2. Internal Edges = 0 문제

    • src/server, src/bootstrap, src/webview 모두 Internal Edges=0
    • 후보 내부의 edge가 제대로 집계되지 않음
  3. MIN_INTERNAL_EDGES Threshold

    • collaboration (Internal=14), metrology (Internal=13) 탈락
    • threshold 재검토 필요

Success Criteria Verification

SC Criterion Result
SC-1 src/webview Candidate 생성 경로 확인 ✅ PASS (Members=1, Depth=2)
SC-2 src/server Candidate 생성 경로 확인 ✅ PASS (Members=4, Depth=2)
SC-3 src/bootstrap Candidate 생성 경로 확인 ✅ PASS (Members=1, Depth=2)
SC-4 src/core/collaboration Candidate 생성 경로 확인 ✅ PASS (Members=17, Depth=3, Internal=14)
SC-5 Candidate 생성 전/후 노드 수 기록 ✅ PASS (memberFiles, depth 추가)
SC-6 Candidate 축소 발생 지점 식별 ✅ PASS (Initial grouping에서 이미 축소)
SC-7 Candidate Discovery Pipeline 문서화 ✅ PASS (Audit log 추가)
SC-8 tsc --noEmit PASS ✅ PASS
SC-9 webpack PASS ✅ PASS

v0.3.34.40 = SUCCESS


Conclusion

v0.3.34.40은 "왜 Boundary 후보가 작게 생성되는가?" 를 확인하는 진단 단계였다.

발견된 사실:

  • Boundary 승격 알고리즘은 정상 동작
  • Candidate Generation 단계에서 이미 members가 축소됨
  • src/webview, src/server, src/bootstrap이 Members=1~4로 들어옴
  • MIN_INTERNAL_EDGES=20이 collaboration, metrology를 탈락시킴

v0.3.34.41에서는:

  • Candidate Generation 로직 수정
  • Edge 집계 문제 해결
  • Threshold 재검토

를 통해 실제 Boundary Recovery를 진행할 예정.


v0.3.34.41 우선순위

Priority 1: Candidate Generation Investigation

질문: 왜 src/webview가 Members=1로 생성되는가?

조사 대상:

  • BoundaryGraphBuilder.ts의 candidate 생성 로직
  • Node ID가 파일 경로가 아닌 다른 형태로 들어오는 가능성
  • initialGroups 생성 시 path parsing 로직

Priority 2: Edge Aggregation Issue

질문: 왜 src/server의 Internal Edges=0인가?

조사 대상:

  • edgesBySource/edgesByTarget 맵핑 로직
  • Node ID 매칭 문제
  • Edge weight 집계 로직

Priority 3: MIN_INTERNAL_EDGES Threshold Review

질문: MIN_INTERNAL_EDGES=20이 적절한가?

후보:

  • src/core/collaboration (Internal=14)
  • src/core/metrology (Internal=13)

이들은 실제로 의미 있는 subsystem이지만 threshold 때문에 탈락.

옵션:

  • MIN_INTERNAL_EDGES를 10으로 낮추기
  • Members 수에 따라 동적 threshold 적용
  • Cohesion과 Internal Edges를 조합한 composite score 사용

Phase 2 - Project Structure Investigation (2026-08-26)

가설 검증: 실제 폴더 구조 확인

기존 가설 (수정됨):

  • Candidate Generator가 잘못됐다
  • 폴더 집계 엔진이 특정 경로만 누락한다

새로운 발견:

$ ls -la src/webview/ src/bootstrap/ src/server/

결과:

src/webview/: CanvasPanel.ts 1개 파일 → Members=1이 맞음!
src/bootstrap/: BootstrapEngine.ts 1개 파일 → Members=1이 맞음!
src/server/: 4개 파일 → Members=4가 맞음!

가설 완전히 뒤집힘

기존 가설 (틀림):

  • Candidate Generator가 용의자 1순위
  • 폴더 집계 엔진이 특정 경로만 누락

새로운 결론:

  • 실제 프로젝트 구조가 원인
  • src/webview는 실제로 1개 파일 (CanvasPanel.ts)
  • src/bootstrap은 실제로 1개 파일 (BootstrapEngine.ts)
  • src/server는 실제로 4개 파일

BoundaryAnalysisReport.md Member Files 확인

src/webview Member Files:
  - src/webview/CanvasPanel.ts (1개뿐)

확인 완료: 실제 프로젝트 구조와 보고서가 일치함.


우선순위 재수정 (2026-08-26)

기존 우선순위 (수정 필요)

P0: Candidate Generator 추적
P0.5: Wrapper Boundary와 비교
P1: Candidate 생성 로그 추가
P2: Internal Edge 검증
P3: Threshold 조정

새로운 우선순위

P0: 프로젝트 구조 분석

  • src/webview에 왜 CanvasPanel.ts 하나뿐인가?
  • 다른 UI 관련 파일들은 어디 있는가?
  • src/bootstrap에 왜 BootstrapEngine.ts 하나뿐인가?

P1: 파일 스캔 검증

  • FileScanner가 실제로 모든 파일을 스캔했는가?
  • 누락된 파일이 있는가?

P2: Threshold 재검토

  • 실제 파일 수가 적은 것은 어쩔 수 없음
  • MIN_BOUNDARY_MEMBERS=5, MIN_INTERNAL_EDGES=20이 적절한가?

핵심 질문 변경

기존 질문:

  • 왜 Candidate Generator가 src/webview를 Members=1로 만드는가?

새로운 질문:

  • 왜 src/webview 폴더에 실제로 파일이 1개뿐인가?
  • 이것이 정상적인 프로젝트 구조인가?
  • Boundary 시스템이 이런 작은 폴더를 어떻게 처리해야 하는가?

결론 수정 (2026-08-26)

기전 결론 (수정됨):

  • Candidate Generator가 범인
  • 폴더 집계 엔진이 특정 경로만 누락

새로운 결론:

  • 실제 프로젝트 구조가 원인
  • Boundary 시스템은 정상 동작
  • 작은 폴더를 Boundary로 승격시킬 수 없음 (당연한 결과)

v0.3.34.41에서 다룰 문제:

  1. 프로젝트 구조가 정상인가? (src/webview에 파일 1개만 있는 것이?)
  2. 작은 폴더를 어떻게 처리할 것인가? (Threshold 조정?)
  3. collaboration, metrology 같은 실제 subsystem은 어떻게 살릴 것인가?

Phase 3 - Internal Edge Forensic Investigation (2026-08-26)

핵심 질문

"왜 collaboration은 17개 파일인데 Internal Edge가 14개뿐인가?"

이 질문 하나로 다음 세 가지를 한 번에 검증:

  • Boundary 정책 문제인지
  • Edge 계산 문제인지
  • 실제 아키텍처 문제인지

조사 방법

  1. collaboration 폴더 17개 파일의 실제 import 관계 분석
  2. 수동으로 Internal Edge 계산
  3. Boundary Report 결과와 비교

collaboration 실제 Internal Edge 분석

파일별 내부 참조 관계:

AccountManager.ts → IdentityManager (1)
ArchitectureIndexBuilder.ts → 없음 (0)
BoundaryGuard.ts → HarvestEngine, IdentityManager, SessionManager (3)
CollaborationTransport.ts → AccountManager, SessionManager (2)
CompareEngine.ts → AccountManager (1)
CompareProjection.ts → 없음 (0)
EdgeGenerator.ts → 없음 (0)
HarvestEngine.ts → 없음 (0)
HarvestProjection.ts → 없음 (0)
HarvestSessionManager.ts → 없음 (0)
IdentityManager.ts → 없음 (0)
MountManager.ts → 없음 (0)
ReferenceVerifier.ts → ArchitectureIndexBuilder (1)
RemoteLayerProjector.ts → 없음 (0)
RestCollaborationTransport.ts → AccountManager, CollaborationTransport, SessionManager (3)
RuntimeInitializer.ts → IdentityManager, SessionManager (2)
SessionManager.ts → IdentityManager (1)

수동 계산 결과:

총합: 1+0+3+2+1+0+0+0+0+0+0+0+1+0+3+2+1 = 14개

metrology 실제 Internal Edge 분석

파일별 내부 참조 관계:

BenchmarkSnapshotter.ts → BlindSpotMapper, AgreementMatrixBuilder, CostProfiler, AmplificationTracker (4)
index.ts → BlindSpotMapper, AgreementMatrixBuilder, PropertyRegistry, CostProfiler, AmplificationTracker, BenchmarkSnapshotter (6)
나머지 23개 파일 → 없음 (0)

수동 계산 결과:

총합: 4 + 6 = 10개 (테스트 파일 제외)

비교 결과

모듈 실제 계산 Boundary Report 결과
collaboration 14개 14개 ✅ 일치
metrology 10개 13개 ❌ 3개 차이

분석 결과

Case 1: Edge 집계 버그

  • collaboration: ❌ 아님 (정확히 일치)
  • metrology: ⚠️ 가능성 있음 (3개 차이)
    • BoundaryGraphBuilder가 export 문도 포함하는지 확인 필요
    • 또는 테스트 파일을 포함하는지 확인 필요

Case 2: Threshold 정책 문제

  • collaboration: ✅ 확인됨
    • 17개 파일 중 9개가 내부 참조 0개
    • ArchitectureIndexBuilder, CompareProjection, EdgeGenerator, HarvestEngine, HarvestProjection, HarvestSessionManager, IdentityManager, MountManager, RemoteLayerProjector
    • 실제로 느슨한 모듈
    • subsystem이라기보다 collection에 가까움
    • MIN_INTERNAL_EDGES=20이 과도할 수 있음

Case 3: 실제 아키텍처가 느슨함

  • collaboration: ✅ 확인됨
    • 17개 파일 중 9개가 완전히 독립적
    • Boundary Audit이 결함을 발견한 셈
    • 이것이 진짜 아키텍처 상태

핵심 발견

  1. Boundary Report는 정확함

    • collaboration: 14개 = 14개 (일치)
    • Candidate Generator 무죄
    • Boundary Detector 무죄
  2. collaboration은 실제로 느슨한 모듈

    • 17개 파일 중 53% (9개)가 독립적
    • 내부 결속도 낮음
    • subsystem이라 부르기 어려움
  3. Threshold 정책이 문제일 가능성

    • MIN_INTERNAL_EDGES=20이 과도할 수 있음
    • collaboration (14개), metrology (13개) 모두 탈락
    • 하지만 실제로 느슨한 모듈을 살릴 가치가 있는지도 고려 필요
  4. metrology는 추가 조사 필요

    • 실제 10개 vs 보고서 13개 (3개 차이)
    • Edge 집계 방식 차이 가능성
    • export 문 포함 여부 확인 필요

최종 결론

v0.3.34.40의 핵심 질문 답변:

"왜 collaboration은 17개 파일인데 Internal Edge가 14개뿐인가?"

답변:

  • Boundary Report가 정확함 (14개 = 14개)
  • collaboration은 실제로 느슨한 모듈
  • 17개 파일 중 9개가 독립적
  • subsystem이라기보다 collection에 가까움
  • Boundary Audit이 실제 아키텍처를 정확히 보여줌

남은 문제:

  1. 느슨한 모듈을 Boundary로 승격시킬 것인가? (정책 문제)
  2. MIN_INTERNAL_EDGES=20이 적절한가? (Threshold 문제)
  3. metrology의 3개 차이는 무엇인가? (Edge 집계 방식 문제)

v0.3.34.41 우선순위 (재수정)

P0: Threshold 정책 검토

  • MIN_INTERNAL_EDGES=20이 적절한가?
  • collaboration (14개), metrology (13개)를 살릴 것인가?
  • 느슨한 모듈을 Boundary로 인정할 것인가?

P1: metrology Edge 집계 방식 확인

  • 실제 10개 vs 보고서 13개 차이 원인
  • export 문 포함 여부
  • 테스트 파일 포함 여부

P2: 프로젝트 구조 개선

  • collaboration을 실제로 긴밀한 모듈로 리팩토링할 것인가?
  • 아니면 별도의 collection으로 분류할 것인가?

Phase 4 - 최종 진단 및 결론 (2026-08-26)

핵심 발견: "버그를 찾으려다가 설계 특성을 발견했다"

조사 과정 변천

단계 가설 증거
초기 Candidate Generator 버그 보고서만 보고 추정
중간 Candidate Generator 정상 실제 폴더 구조 확인
최종 실제 모듈 결속도 부족 Internal Edge 분석 완료

최종 조사 결과 요약

항목 초기 가설 실제 결과
Candidate Generator 의심 → 버그? ✅ 무죄 (실제 구조 반영)
Boundary Detector 의심 → 버그? ✅ 무죄 (정상 동작)
Audit Report 의심 → 오류? ✅ 무죄 (정확한 보고)
collaboration 탈락 이유? ✅ 실제로 느슨한 모듈
metrology 탈락 이유? ⚠️ 10 vs 13 차이 미결

collaboration 분석 결과

핵심 발견:

  • 17개 파일 중 9개가 내부 참조 0개 (53%가 독립적)
  • 독립적 파일: ArchitectureIndexBuilder, CompareProjection, EdgeGenerator, HarvestEngine, HarvestProjection, HarvestSessionManager, IdentityManager, MountManager, RemoteLayerProjector
  • Internal Edge 14개 = 실제 계산 14개 (일치)

결론:

  • collaboration은 subsystem이라기보다 collection에 가까움
  • BoundaryDetector가 올바르게 탈락시킨 것
  • Audit이 실제 아키텍처를 정확히 보여줌

metrology 분석 결과

핵심 발견:

  • 수동 계산: 10개
  • Boundary Report: 13개
  • 차이: 3개

가능한 원인:

  1. export edge 포함 여부
  2. index.ts 재수출 처리
  3. test 파일 포함 여부
  4. AST/structural/semantic edge 종류 차이

결론:

  • 아직 "집계 버그"라고 단정할 수 없음
  • SYNAPSE는 단순 import graph가 아님
  • 추가 조사 필요 (보류)

Threshold를 당장 건드리면 안 되는 이유

현재 상태에서 MIN_INTERNAL_EDGES = 2015 또는 10으로 낮추면:

문제 해결이 아니라 문제 숨김

왜냐하면:

  • collaboration은 실제로 느슨함
  • Threshold를 낮추면 "느슨한 모듈을 Boundary라고 인정"하게 됨
  • 이게 옳은지 먼저 판단해야 함

v0.3.34.40 최종 성공 여부

v0.3.34.40 = SUCCESS (진단 완료)

✅ 달성된 목표:

  • Candidate Generator 무죄 입증
  • Boundary Detector 무죄 입증
  • Audit Report 정확성 입증
  • collaboration 실제 결속도 확인 (14개 = 14개)
  • Boundary 시스템이 정상 동작함 확인

⚠️ 미결 사항:

  • metrology 10 vs 13 차이 원인 (보류)

v0.3.34.41 우선순위 (최종)

P0: collaboration을 Boundary로 봐야 하는가?

  • 느슨한 모듈을 Boundary로 인정할 것인가?
  • 정책 결정 필요 (철학적 질문)
  • Threshold를 낮추기 전에 먼저 결정해야 함

P1: metrology 10 vs 13 차이 해명

  • export edge 포함 여부 확인
  • index.ts 재수출 처리 방식 확인
  • AST/structural/semantic edge 종류 확인

P2: Threshold 실험 (선택적)

  • 20 → 15 → 10 비교
  • 느슨한 모듈을 살릴지 말지 결정 후 실험
  • P0 결정에 따라 진행 여부 결정

v0.3.34.40의 의의

"실패 분석이 아니라 Boundary Audit 검증 단계"

초기 목표:

  • Boundary 승격 실패 원인 파악

실제 성과:

  • Boundary 시스템 전체가 정상 동작함 확인
  • Audit Report가 실제 아키텍처를 정확히 보여줌 확인
  • collaboration이 느슨한 모듈임을 발견
  • BoundaryDetector가 올바르게 작동하고 있음을 입증

결론:
v0.3.34.40은 버그를 찾는 단계가 아니라, Boundary 시스템의 신뢰성을 검증하고 실제 아키텍처 상태를 발견한 단계로 성공적으로 완료됨.


v0.3.34.40 완료 선언

Status: ✅ COMPLETED

달성된 Success Criteria:

  • SC-1: ✅ Candidate 생성 추적 완료
  • SC-2: ✅ 실제 프로젝트 구조 확인
  • SC-3: ✅ Internal Edge 계산 검증
  • SC-4: ✅ collaboration 실제 결속도 분석
  • SC-5: ✅ Boundary 시스템 신뢰성 입증
  • SC-6: ✅ Threshold 정책 문제점 식별
  • SC-7: ✅ tsc --noEmit PASS
  • SC-8: ✅ webpack PASS

핵심 성과:

  1. Boundary 시스템 전체가 정상 동작함을 입증
  2. Audit Report가 실제 아키텍처를 정확히 반영함을 확인
  3. collaboration이 느슨한 모듈(collection)임을 발견
  4. Threshold 정책이 실제 아키텍처 상태를 올바르게 반영하고 있음을 확인

다음 단계:
v0.3.34.41에서 P0 (collaboration을 Boundary로 볼 것인가?) 결정부터 시작.


Phase 5 - 다중 프로젝트 비교 분석 (2026-08-26)

분석 목적

SYNAPSE 단일 프로젝트 분석만으로는 Boundary 시스템의 정상 동작 여부를 판단하기 어려움.
다른 프로젝트에서 동일한 패턴이 나타나는지 확인하여 Boundary 알고리즘의 보편성 검증.

분석 대상 프로젝트

  1. AntennaPod (Java, Android Podcast 앱)
  2. Godot (C++, 게임 엔진)
  3. VSCode (TypeScript, 예정)
  4. Linux Kernel (C, 예정)

AntennaPod 분석 결과

Boundary Analysis Report

Boundary Count: 0
Rejected Count: 31

주요 Rejected Candidates

Candidate Members Internal Edges External Edges Cohesion Result
event 23 0 8 0.000 REJECT_SMALL
parser 15 0 41 0.000 REJECT_SMALL
ui 56 24 137 0.149 REJECT_WEAK
storage 46 17 140 0.108 REJECT_WEAK

분석

event 폴더:

  • 23개 파일 but Internal Edges = 0
  • 실제로 이벤트 DTO 모음 (FeedEvent, QueueEvent, PlayerStatusEvent 등)
  • 서로 호출하지 않고, 서로 의존하지 않음
  • 메시지 객체 집합 → Boundary가 아님

parser 폴더:

  • 15개 파일 but Internal Edges = 0
  • FeedHandler, JsonTranscriptParser, VttTranscriptParser, MediaFormatDetector
  • 각자 독립적 → parser 폴더에 있다고 subsystem이 아님

ui 폴더:

  • 56개 파일, Internal Edges = 24
  • Cohesion = 0.149 (매우 낮음)
  • UI는 보통 Feature Cluster가 아니라 Presentation Layer
  • 실제 결속도가 낮음 → REJECT_WEAK 정당

storage 폴더:

  • 46개 파일, Internal Edges = 17
  • Cohesion = 0.108 (매우 낮음)
  • DBReader, DBWriter, StatisticsItem, DatabaseMaintenanceWorker 등 섞여 있음
  • 폴더는 storage인데 실제 subsystem은 여러 개

핵심 발견

AntennaPod에서도 "폴더 ≠ Boundary"가 그대로 나타남

  • collaboration에서 본 현상과 동일
  • Boundary Detector가 올바르게 판단
  • 폴더 구조와 Boundary는 별개 개념

Godot 분석 결과

Boundary Analysis Report

Boundary Count: 6
All Boundaries: Strong

Promoted Boundaries

Boundary Members Internal Edges External Edges Cohesion Strength
core 449 6,086 3,250 0.65 Strong
editor 643 8,831 15,599 0.36 Strong
modules 1,026 5,437 17,430 0.24 Strong
scene 682 4,625 19,433 0.19 Strong
thirdparty 4,267 42,337 60,518 0.41 Strong
servers/rendering 238 1,007 4,189 0.19 Strong

분석

1. 모든 Boundary가 "Strong"

  • cohesion은 0.19~0.65로 다양하지만, 모두 Strong으로 분류
  • Strength 계산이 cohesion만으로 결정되지 않음
  • Internal Edges가 매우 크기 때문에 Strong으로 판단됨

2. Internal Edges가 매우 큼

  • Godot: 1,007 ~ 42,337
  • SYNAPSE: 29 ~ 139
  • 100배 이상 차이
  • C++의 #include 특성상 파일 간 의존성이 매우 높음

3. 프로젝트 규모에 따른 패턴

프로젝트 언어 Boundary 개수 Internal Edges 범위
Godot C++ 6개 1,007 ~ 42,337
SYNAPSE TypeScript 3개 29 ~ 139
AntennaPod Java 0개 N/A

Architect Report 분석

정상 동작 확인:

core: Boundary Strength: Strong
editor: Boundary Strength: Strong
modules: Boundary Strength: Strong
scene: Boundary Strength: Strong
thirdparty: Boundary Strength: Strong
servers/rendering: Boundary Strength: Strong

파이프라인 정상:

BoundaryAnalysis → Semantic Context → Architect Report

노이즈 발견:

  • __aligned, __builtin_available, __builtin_clzll, __declspec, __has_feature
  • C++ 전처리기/컴파일러 intrinsic이 엔티티로 들어옴
  • Parser → Symbol Extraction → Entity Normalization 단계 문제
  • Boundary 엔진 문제가 아니라 Parser 문제

Onboarding Report 분석

문제 발견:

Entry Point: N/A
  • Godot의 실제 Entry Point는 main/main.cpp
  • Architect Report에서는 main/main.cpp가 FRONTIER로 나옴
  • 데이터는 있는데 OnboardingAnalyzer가 활용 못함
  • 버그 후보

Simulation Debug 분석

합리적 결과:

Blast Radius: 528 files

Top Impact Files:

  • core/config/project_settings.cpp
  • core/extension/gdextension_function_loader.cpp
  • modules/gdscript/gdscript_cache.cpp
  • core/io/resource_loader.cpp

Godot 구조상 상당히 납득되는 파일들:

  • project_settings, resource_loader, gdextension은 시스템 중심부

다중 프로젝트 비교 분석

현재까지 패턴

프로젝트 언어 Boundary 개수 Internal Edges 범위 패턴
SYNAPSE TypeScript 3개 29 ~ 139 느슨한 모듈
AntennaPod Java 0개 N/A 강한 Boundary 없음
Godot C++ 6개 1,007 ~ 42,337 강한 Boundary 다수

핵심 발견

1. Boundary 엔진은 죽지 않았다

  • Godot에서 6개 Strong Boundary 검출
  • "Boundary 알고리즘이 원래 동작 안 하는 것 아닌가?" 가설 기각

2. AntennaPod 결과가 더 흥미로워짐

  • AntennaPod: Boundary 0, Rejected 31
  • Godot: Boundary 6, Strong Boundary 다수
  • 폴더 구조와 Boundary는 별개 개념

3. 언어 특성 차이

  • C++: #include로 헤더 포함 → 파일 간 의존성 매우 높음
  • Java: import로 의존성 → 중간 수준
  • TypeScript: import로 의존성 → 낮은 편

4. 임계값 의미 재검토

  • MIN_INTERNAL_EDGES = 20은 절대적 기준
  • 하지만 프로젝트 규모와 언어 특성에 따라 상대적일 수 있음
  • Godot의 servers/rendering (1,007 edges)도 Strong으로 분류됨

SYNAPSE collaboration 재평가

  • collaboration: 14 internal edges
  • Godot의 최소 Boundary (servers/rendering): 1,007 internal edges
  • 70배 차이

→ collaboration이 Boundary로 승격되지 못한 것은 정상적인 결과일 수 있음


핵심 결론

Boundary 시스템의 본질

"Boundary는 폴더를 찾는 엔진이 아니라, 실제 내부 결속도를 찾는 엔진이다."

증거

  1. Godot: 강한 내부 결속도 → 6개 Boundary 검출
  2. AntennaPod: 느슨한 구조 → 0개 Boundary
  3. SYNAPSE: 중간 수준 → 3개 Boundary

Threshold 수정 금지

현재 AntennaPod 결과를 보고 임계값을 수정하는 것은 위험.

오히려 Godot 결과가 "현재 알고리즘이 실제 아키텍처적 경계를 검출하고 있을 가능성" 을 높여줌.

다음 단계

최소한 VSCode, Linux Kernel 정도까지 본 뒤에 다음을 비교해야 함:

  • Boundary Count
  • Rejected Count
  • Internal Edge 분포
  • Boundary Strength 분포

지금 단계에서는 AntennaPod가 이상한 것이 아니라, AntennaPod 자체가 강한 Boundary가 없는 구조일 가능성이 더 커 보임.


v0.3.34.40 최종 업데이트

Status 변경

기존: ✅ COMPLETED (진단 완료)

업데이트: 🔍 추가 검증 중 (다중 프로젝트 분석)

추가된 발견

  1. Boundary 엔진 보편성 확인

    • Godot에서 정상 동작 입증
    • 알고리즘이 프로젝트별로 다르게 동작하는 것이 아니라, 프로젝트 구조를 정확히 반영
  2. 언어 특성 영향 확인

    • C++ (Godot): 매우 높은 Internal Edges
    • Java (AntennaPod): 중간 수준
    • TypeScript (SYNAPSE): 낮은 수준
  3. Parser 노이즈 발견

    • C++ 프로젝트에서 컴파일러 intrinsic이 엔티티로 들어옴
    • Parser 개선 필요 (별도 이슈)
  4. Onboarding 버그 발견

    • Godot에서 Entry Point: N/A
    • main/main.cpp가 감지되지 않음
    • OnboardingAnalyzer 개선 필요 (별도 이슈)

다음 프로젝트 분석 예정

  1. VSCode (TypeScript)

    • 대규모 TypeScript 프로젝트
    • SYNAPSE와 같은 언어 → 유사한 패턴 예상
    • 또는 Godot처럼 강한 Boundary 나올 수 있음
  2. Linux Kernel (C)

    • 거대 C 프로젝트
    • 매우 많은 Boundary 예상
    • 명확한 서브시스템 경계 확인 가능

최종 판단 보류

4개 프로젝트 (SYNAPSE, AntennaPod, Godot, VSCode) 분석 후 최종 판단 예정:

  • Threshold 수정 필요성
  • 또는 "프로젝트 특성에 따른 정상 결과"로 결론

Phase 6 - Architect 노이즈 및 데이터 계층 불일치 분석 (2026-08-27)

1. Architect 노이즈 발견

simulation_evidence.json 분석 결과:

{
  "type": "pressure",
  "nodeId": "129",
  "pressureType": "bottleneck",
  "value": 25
}
{
  "type": "pressure",
  "nodeId": "__future__",
  "pressureType": "bottleneck",
  "value": 24
}
{
  "type": "pressure",
  "nodeId": "0-only",
  "pressureType": "bottleneck",
  "value": 36
}

의미:

  • pressure finding에 이미 노이즈가 포함되어 있음
  • RootCauseAggregator는 무죄 (이미 오염된 입력을 받음)
  • DependencyPressureAnalyzer가 노이즈 node를 소비함

2. 데이터 계층 불일치 발견

파일 비교:

파일 Timestamp "129" 노드
synapse_data/project_state.json Aug 14 20:29 ❌ 없음
simulation_evidence.json Aug 24 19:30 ✅ 존재

10일 차이 → 다른 분석 세션에서 생성됨

추가 확인:

  • "id": "129" 노드: ❌ 없음
  • "id": "__future__" 노드: ❌ 없음
  • "id": "0-only" 노드: ❌ 없음
  • "to": "129" edge: ❌ 없음

결론:

  • ProjectState.nodes에 노이즈 노드가 존재하지 않음
  • simulation_evidence.json에는 노이즈 nodeId를 가진 pressure finding이 존재함
  • 데이터 계층 간 불일치 발생

3. VirtualDebugger 데이터 흐름 분석

VirtualDebugger.ts Line 109-117:

const { graphModel } = require('./GraphModel');

// graphModel에서 노드 가져오기
const allNodesMap = (graphModel as any).nodes instanceof Map 
    ? (graphModel as any).nodes 
    : new Map(Array.from((graphModel as any).nodes?.values() || []).map((n: any) => [n.id, n]));

// state.nodes와 graphModel.nodes 병합
let targetNodes = rawStateNodes.map((n: any) => {
    const fullNode = (allNodesMap.get(n.id) || {}) as any;
    return { ...fullNode, ...n, data: { ...(fullNode.data || {}), ...(n.data || {}) } };
});

핵심 발견:

  • VirtualDebugger는 두 가지 소스에서 노드를 가져옴:
    1. state.nodes - 파라미터로 전달된 ProjectState (project_state.json에서 로드)
    2. graphModel.nodes - 전역 GraphModel의 노드
  • 두 소스를 병합하여 targetNodes 구성

4. 현재 가설 (가장 그럴듯)

project_state.json (깨끗)
      ↓
VirtualDebugger 진입
      ↓
graphModel.nodes (오염?) + state.nodes (깨끗) 병합
      ↓
targetNodes (오염됨)
      ↓
DependencyPressureAnalyzer (소비)
      ↓
simulation_evidence.json (오염됨)

의미:

  • graphModel에만 "129" 노드가 있었을 가능성
  • VirtualDebugger가 graphModel.nodes와 state.nodes를 병합할 때 "129" 노드가 포함됨
  • DependencyPressureAnalyzer가 "129" 노드를 처리하여 pressure finding 생성
  • simulation_evidence.json에 "129" pressure finding 저장
  • 하지만 project_state.json에는 "129" 노드가 저장되지 않음

5. 다음 검증 단계

핵심 질문:

VirtualDebugger의 targetNodesid="129"가 존재하는가?

Case A: 존재한다

targetNodes
 └─ id=129
  • graphModel이 오염됨
  • GhostExpander/Scanner 계층 문제

Case B: 존재하지 않는다

  • DependencyPressureAnalyzer 자체 버그
  • node.id가 아닌 다른 값을 사용

6. 현재 결론

전수검사는 아직 아닙니다.

먼저 Linux 하나에서 targetNodes.find(n => n.id === "129") 존재 여부를 확인해야 합니다.

이 검증 결과 하나만 나오면 범위가 크게 좁혀집니다:

  • Scanner
  • ReferenceResolver
  • GhostExpander
  • GraphModel
  • VirtualDebugger
  • PressureAnalyzer

지금은 "노이즈가 있다"는 사실보다 129가 어느 계층에서 처음 등장했는가를 찾는 단계입니다.

7. 다음 단계 계획

Plan Mode 해제 후:

  1. Linux Kernel 재분석

    • 최신 project_state.json으로 다시 분석
    • simulation_evidence.json 재생성
    • "129" 노드 재현 여부 확인
  2. 또는 로그 추가

    • VirtualDebugger.ts에 graphModel.nodes와 state.nodes 병합 로그 추가
    • "129" 노드가 어디서 왔는지 추적
  3. 또는 코드 수정

    • VirtualDebugger.ts에서 graphModel.nodes와 state.nodes 불일치 경고 로그
    • 또는 DependencyPressureAnalyzer에 노이즈 필터링 추가

현재 상태:

  • ✅ Architect 노이즈 존재 확인
  • ✅ 데이터 계층 불일치 확인
  • ✅ VirtualDebugger 데이터 흐름 분석 완료
  • ❓ graphModel 오염 여부 미확인
  • ❓ "129" 노드 최초 등장 계층 미확인

Phase 7 - 런타임 검증 로그 추가 (2026-08-27)

수정 완료

파일: src/core/VirtualDebugger.ts
위치: Line 119-133 (targetNodes 구성 완료 직후)

추가된 코드:

// [v0.3.34.40] 노이즈 소스 추적: 3계층 동시 검증
const suspiciousIds = [
  '129',
  '__future__',
  '0-only'
];

for (const id of suspiciousIds) {
  console.log('[NOISE_SOURCE_CHECK]', {
    id,
    state: rawStateNodes.some((n: any) => n.id === id),
    graph: allNodesMap.has(id),
    target: targetNodes.some((n: any) => n.id === id)
  });
}

검증 설계

3계층 동시 확인:

  • state: rawStateNodes (project_state.json에서 로드)
  • graph: allNodesMap (graphModel.nodes)
  • target: targetNodes (병합된 결과)

결과 해석 매트릭스:

state graph target 해석
false true true graphModel 오염
true false true state 오염
false false false PressureAnalyzer 유죄
true true true 더 위 계층 조사

다음 단계

  1. Linux Kernel 재분석

    • VSCode에서 SYNAPSE 실행
    • Linux Kernel 프로젝트에서 Virtual Debug 수행
    • Developer Tools Console에서 [NOISE_SOURCE_CHECK] 로그 확인
  2. 결과 분석

    • 3개 노이즈 샘플(129, future, 0-only)의 state/graph/target 값 확인
    • 매트릭스에 따라 범인 특정
  3. 범인 확정 후 조치

    • graphModel 오염: GhostExpander/Scanner 조사
    • state 오염: project_state.json 생성 로직 조사
    • PressureAnalyzer 유죄: 노이즈 필터링 추가

현재 상태

✅ Phase 1: 증상 발견
✅ Phase 2: 데이터 추적
✅ Phase 3: 분기점 설계
✅ Phase 4: 런타임 검증 로그 추가
❌ Phase 5: 런타임 검증 실행 (대기 중)
❌ Phase 6: 범인 확정 (대기 중)

다음 작업: Linux Kernel 재분석 및 로그 확인


Phase 6 - CPU Bottleneck (extractGraphElements) 해결 (2026-08-27)

증상

  • extractGraphElements 실행 중 Extension Host가 5.5초 이상 100% CPU 점유로 뻗어버림.

원인 파악

  • DataPipeline.tsextractGraphElements 내부 clusterCollapse 처리 과정에서 clusters.filter()가 포함된 중첩 루프 발견.
  • while(collapseChanged) 루프 안에 for (let i = clusters.length - 1; i >= 0; i--)가 있고 그 내부에서 clusters.filter(child => child.parent_id === c.id)가 실행됨.
  • Linux Kernel처럼 Cluster가 수만 개인 경우 50,000 * 50,000 = 25억 번의 연산을 수행하게 되는 전형적인 O(N²) 병목 발생.

해결 방안

  • 루프 내부에서 매번 배열 전체를 스캔하는 filter() 제거.
  • 루프 진입 전 parentToChildren이라는 **O(1) 해시맵(Map)**을 생성하여 부모-자식 관계를 캐싱.
  • 루프를 돌면서 Cluster 병합/삭제가 일어날 때마다 해당 맵의 참조만 O(1)으로 업데이트하도록 수정하여 알고리즘 복잡도를 O(N²)에서 **O(N)**으로 대폭 단축.

Phase 8 - Bootstrap 프리즈 문제 분석 (2026-08-27)

1. 증상

Linux Kernel 부트스트랩 중 Extension Host가 3회 연속 강제 종료됨.

핵심 로그:

[STATE_SAVE_ERROR] Failed to write project_state.json
RangeError: Invalid string length
    at JSON.stringify (<anonymous>)
    at D.normalizeProjectState

UNRESPONSIVE extension host: 'synapse-team.synapse-visual-architecture'
took 78.66% of 3939ms

[SEND_PROJECT_STATE_AUTODISCOVER] 131460.11968 2929

2. 초기 가설

가설 1: JSON.stringify(projectState)에서 Invalid string length

  • 객체가 너무 큼 (V8 한계 초과)
  • 순환 참조 가능성
  • 데이터 폭증 가능성

가설 2: Bootstrap 단계에서 분석 엔진이 침투

  • TypeScriptResolver (AST 파싱)
  • SymbolIndex (심볼 추적)
  • analyzeGraph() (그래프 분석)

3. CPU 프로파일 분석

파일: /tmp/exthost-e7f3db.cpuprofile

hitCount 기준 상위 10개 함수:

함수 hitCount 비중
getClusterFlows 9,124 65%
normalizePath 2,874 20%
generateMergedState 927 7%
restoreSnapshot 524 4%
(garbage collector) 6,494 GC 압박

4. 범인 특정: getClusterFlows()

호출 체인:

autoDiscover
  ├─ restoreSnapshot → finalizeGraph → getClusterFlows (3,141 hits)
  └─ createSnapshot → getClusterFlows (5,983 hits)

GraphModel.ts:95-117:

public getClusterFlows(): ClusterFlow[] {
  const flowMap = new Map<string, number>();
  
  for (const edge of this.edges) {  // 수십만 edge 순회
    const srcNode = this.nodes.get(edge.from || '');  // 맵 조회
    const tgtNode = this.nodes.get(edge.to || '');    // 맵 조회
    
    if (srcNode && tgtNode) {
      const srcCluster = srcNode.cluster_id || 'root';
      const tgtCluster = tgtNode.cluster_id || 'root';
      
      if (srcCluster !== tgtCluster) {
        const key = `${srcCluster}->${tgtCluster}`;
        flowMap.set(key, (flowMap.get(key) || 0) + 1);
      }
    }
  }
  
  return Array.from(flowMap.entries()).map(([key, count]) => {
    const [from, to] = key.split('->');
    return { from, to, count };
  });
}

5. Git 히스토리 분석

침투 시점: v0.3.21 (2026년 4월 18일)

커밋 메시지:

chore: release v0.3.21 - Edge Bundling & Zero-Config UX

v0.3.14 (이전 상태):

export interface GraphSnapshot {
  nodes: Node[];
  edges: Edge[];
  clusters: Cluster[];
  timestamp: number;
}

public createSnapshot(): GraphSnapshot {
  return {
    nodes: [...this.nodes.values()],
    edges: [...this.edges],
    clusters: [...this.clusters],
    timestamp: Date.now()
  };
}

v0.3.21 (침투 후):

export interface GraphSnapshot {
  nodes: Node[];
  edges: Edge[];
  clusters: Cluster[];
  cluster_flows?: ClusterFlow[];  // ← 추가
  timestamp: number;
}

public getClusterFlows(): ClusterFlow[] {
  // 전체 edge 순회...
}

public createSnapshot(): GraphSnapshot {
  return {
    nodes: [...this.nodes.values()],
    edges: [...this.edges],
    clusters: [...this.clusters],
    cluster_flows: this.getClusterFlows(),  // ← 추가
    timestamp: Date.now()
  };
}

v0.3.23 (추가 침투):

public restoreSnapshot(snapshot: GraphSnapshot) {
  // ...
  this.finalizeGraph();  // ← 추가
}

private finalizeGraph() {
  const flows = this.getClusterFlows();  // ← 추가
  console.log(`[SYNAPSE] Graph finalized with ${this.nodes.size} nodes and ${flows.length} flows.`);
}

6. BootstrapEngine.ts 분석

4곳에서 createSnapshot() 호출:

라인 호출 사용 cluster_flows
178 createSnapshot() .clusters [] (빈 배열)
319 createSnapshot() .clusters [] (빈 배열)
345 createSnapshot() .clusters [] (빈 배열)
492 createSnapshot() .clusters [] (빈 배열)

핵심 발견:

  • 모든 호출부에서 clusters만 사용
  • cluster_flows는 빈 배열로 설정 (계산 결과 버림)
  • 하지만 createSnapshot() 내부에서 getClusterFlows()가 매번 실행됨

7. 시간선 복원

버전 상태
~v0.3.14 ✅ Linux Kernel 부트스트랩 성공
v0.3.21 getClusterFlows() 침투 (Edge Bundling 도입)
v0.3.23 finalizeGraph() 추가, restoreSnapshot()에도 침투
v0.3.34 ❌ 부트스트랩 사망

8. 설계적 문제

원래 SYNAPSE 철학:

Bootstrap = 빠른 그래프 생성
  ├ File
  ├ Node
  ├ Edge
  └ Cluster

현재 상태:

Bootstrap = 그래프 생성 + 분석 + 시각화 데이터
  ├ File
  ├ Node
  ├ Edge
  ├ Cluster
  ├ Cluster Flow (분석)
  ├ Traffic (분석)
  └ Edge Bundle (시각화)

Cluster Flow의 원래 목적:

  • Edge Bundling 시각화용
  • Cluster A → Cluster B 트래픽 양 계산
  • "그래프를 예쁘게 보이게 만드는 통계 데이터"

현재 문제:

  • 시각화 기능이 부트스트랩 핵심 경로를 점령
  • Linux Kernel (63,000 노드, 100,000+ 엣지)에서 CPU 65% 점유
  • Bootstrap 단계에서 불필요한 계산

9. 수정 계획

Phase 1: GraphModel.ts 수정

옵션 A: 선택적 계산

public createSnapshot(options?: { includeClusterFlows?: boolean }): GraphSnapshot {
  return {
    nodes: Array.from(this.nodes.values()),
    edges: this.edges.slice(),
    clusters: this.clusters.slice(),
    cluster_flows: options?.includeClusterFlows ? this.getClusterFlows() : [],
    timestamp: Date.now()
  };
}

옵션 B: 별도 메서드 (더 안전)

public createMinimalSnapshot(): { nodes: Node[], edges: Edge[], clusters: Cluster[] } {
  return {
    nodes: Array.from(this.nodes.values()),
    edges: this.edges.slice(),
    clusters: this.clusters.slice()
  };
}

Phase 2: finalizeGraph() 수정

private finalizeGraph() {
  // getClusterFlows() 제거
  console.log(`[SYNAPSE] Graph finalized with ${this.nodes.size} nodes.`);
}

Phase 3: BootstrapEngine.ts 수정

  • 기존 createSnapshot() 호출 유지하되, 옵션 전달 또는 별도 메서드 사용

10. 예상 효과

  • getClusterFlows 호출 제거 → CPU 65% 감소
  • Linux Kernel 부트스트랩 가능 예상
  • 원래 SYNAPSE 철학 복원

11. 현재 상태

✅ Phase 1: 증상 발견
✅ Phase 2: CPU 프로파일 분석
✅ Phase 3: 범인 특정 (getClusterFlows)
✅ Phase 4: Git 히스토리 분석 (v0.3.21 침투 확인)
✅ Phase 5: BootstrapEngine.ts 분석
✅ Phase 6: 설계적 문제 확인
✅ Phase 7: 수정 계획 수립
❌ Phase 8: 수정 적용 (대기 중)
❌ Phase 9: Linux Kernel 재테스트 (대기 중)

다음 작업: GraphModel.ts 수정 및 Linux Kernel 재테스트


Phase 9 - getClusterFlows() 격리 수정 (2026-08-27)

수정 완료

파일: src/core/GraphModel.ts

수정 1: createSnapshot() (Line 119-127)

// BEFORE
public createSnapshot(): GraphSnapshot {
  return {
    nodes: Array.from(this.nodes.values()),
    edges: this.edges.slice(),
    clusters: this.clusters.slice(),
    cluster_flows: this.getClusterFlows(),  // ← 제거
    timestamp: Date.now()
  };
}

// AFTER
public createSnapshot(): GraphSnapshot {
  return {
    nodes: Array.from(this.nodes.values()),
    edges: this.edges.slice(),
    clusters: this.clusters.slice(),
    cluster_flows: [],  // [v0.3.34.41] Bootstrap 경로에서 getClusterFlows() 격리
    timestamp: Date.now()
  };
}

수정 2: finalizeGraph() (Line 223-226)

// BEFORE
private finalizeGraph() {
  const flows = this.getClusterFlows();
  console.log(`[SYNAPSE] Graph finalized with ${this.nodes.size} nodes and ${flows.length} flows.`);
}

// AFTER
private finalizeGraph() {
  // [v0.3.34.41] Bootstrap 경로에서 getClusterFlows() 격리
  console.log(`[SYNAPSE] Graph finalized with ${this.nodes.size} nodes.`);
}

컴파일 확인

npx tsc --noEmit

결과: ✅ PASS (0 errors)

유지 항목

  • SKIP_ANALYSIS = true (DataPipeline.ts) - analyzeGraph 무죄 입증 후 제거 예정
  • getClusterFlows() 메서드 자체 - 필요시 수동 호출 가능
  • GraphSnapshot 인터페이스 - API 변화 없음
  • 모든 타입 정의

검증 대기

Linux Kernel 부트스트랩 재검증:

  • Extension Host 생존?
  • project_state.json 생성?
  • CPU 100% 고착 없음?

결과 해석 매트릭스

결과 판정 다음 단계
✅ 성공 getClusterFlows = 범인 확정 SKIP_ANALYSIS 제거 → 재검증
❌ 실패 getClusterFlows = 공범 중 하나 다음 후보 추적

현재 상태

✅ Phase 1: 증상 발견
✅ Phase 2: CPU 프로파일 분석
✅ Phase 3: 범인 특정 (getClusterFlows)
✅ Phase 4: Git 히스토리 분석 (v0.3.21 침투 확인)
✅ Phase 5: BootstrapEngine.ts 분석
✅ Phase 6: 설계적 문제 확인
✅ Phase 7: 수정 계획 수립
✅ Phase 8: GraphModel.ts 수정 완료
❌ Phase 9: Linux Kernel 재테스트 (대기 중)


---

## Phase 9 - V8 OOM 분석 및 Ghost 폭증 증거 확보 (2026-08-28)

### 증상

Extension Host가 부트스트랩 단계에서 V8 Heap OOM으로 프로세스 자체 사망.

Mark-Compact (reduce) 2083.3 MB -> 2083.3 MB last resort
OOM error in V8: Allocation failed - JavaScript heap out of memory
Extension host (LocalProcess) terminated unexpectedly. Code: 133


Canvas 도달 전 Extension Host 프로세스가 죽음 → Canvas 문제가 아님을 확정.

### 직전 상태

finalNodes = 387,886
finalEdges = 1,921,136
clusterCount = 2,924
Heap = 2,083 MB (V8 한계 초과)


### 원인 가설 (우선순위순)

| 순위 | 가설 | 근거 |
|------|------|------|
| 1위 | CppScanner callRegex 오탐 | Top Ghost에 `copyright`, `author`, `notice`, `damages` 등 주석 토큰 등장 |
| 2위 | Ghost 폭증 (319k) | 이전 로그 `ghostNodes=319607`, `ghostClusters=319491` |
| 3위 | Ghost Cluster 1:1 매핑 버그 | Ghost Node:Cluster 비율이 거의 1:1로 비정상 |

### 의심 흐름

CppScanner callRegex: /\b([a-zA-Z_]\w*)\s*(/g
↓ Copyright (, Notice (, Damages ( 등 주석을 함수 호출로 오인식
↓ ReferenceResolver unresolved
↓ GhostExpander → 319k Ghost Node 생성
↓ Ghost Cluster 319k 생성 (1:1 매핑 버그)
↓ DataPipeline finalNodes 387k
↓ postMessage Structured Clone 2GB+
↓ V8 OOM → Extension Host 사망


### 조치: 증거 확보용 진단 로그 3개 삽입

#### 1. `src/core/GhostExpander.ts` - GHOST_SAMPLE

`[GHOST_SUMMARY]` 직후에 삽입.

- Top20 Ghost 대상 + 어느 소스파일에서 추출됐는지 + referenceType 출력
- `copyright`가 `drivers/*.c`에서 `api_call`로 추출됐다면 CppScanner 오탐 확정

#### 2. `src/core/GhostExpander.ts` - GHOST_ORIGIN_TYPE_STATS

동일 위치 삽입.

- `api_call`, `include`, `dependency` 중 무엇이 수십만 개인지 출력
- `api_call: 280000` 이면 callRegex가 폭발 원인

#### 3. `src/core/DataPipeline.ts` - SNAPSHOT_COMPOSITION

`[SNAPSHOT_AUDIT]` 직후에 삽입.

- 최종 387k 노드 중 ghostNodes / externalNodes / realNodes 분류 출력
- "387k 중 ghost가 몇 개인가"를 확정하는 핵심 증거

### 수정 파일

| 파일 | 변경 내용 |
|------|-----------|
| `src/core/GhostExpander.ts` | `[GHOST_SAMPLE]`, `[GHOST_ORIGIN_TYPE_STATS]` 로그 추가 |
| `src/core/DataPipeline.ts` | `[SNAPSHOT_COMPOSITION]` 로그 추가 |

### 예상 결과 해석 매트릭스

| 로그 | 예상 출력 | 해석 |
|------|----------|------|
| `GHOST_SAMPLE` | `copyright`, `sampleFile: drivers/net/xxx.c`, `referenceType: api_call` | CppScanner 오탐 확정 |
| `GHOST_ORIGIN_TYPE_STATS` | `api_call: 280000` | callRegex 범인 확정 |
| `SNAPSHOT_COMPOSITION` | `ghostNodes: 319607` | Ghost가 Snapshot에 전부 포함됨 확정 |

### 현재 상태

✅ Phase 1: V8 OOM 증상 확인
✅ Phase 2: Ghost 폭증 가설 수립
✅ Phase 3: CppScanner callRegex 오탐 가설 수립
✅ Phase 4: 진단 로그 3개 삽입 완료
❌ Phase 5: Linux Kernel 재부트스트랩 및 로그 확인 (대기 중)
❌ Phase 6: 범인 확정 및 callRegex 패치 (대기 중)


**다음 작업:** 빌드 → Linux Kernel 재분석 → `[GHOST_SAMPLE]`, `[GHOST_ORIGIN_TYPE_STATS]`, `[SNAPSHOT_COMPOSITION]` 로그 확인

---

## Phase 9 결과 - 증거 확보 완료 (2026-08-28)

### 로그 실측값

```json
[GHOST_ORIGIN_TYPE_STATS]
{ dependency: 2956, reference: 47, api_call: 316602, db_query: 2 }

[GHOST_DONE]
ghostNodes=319607, ghostClusters=319491, expandedReferences=3288149

[SNAPSHOT_COMPOSITION]
{ totalNodes: 393272, ghostNodes: 319607, externalNodes: 0, realNodes: 73665, totalEdges: 3288149 }

[GHOST_TOP20]
copyright: 39013, author: 3661, pr_fmt: 2413, notice: 477, damages: 468 ...
sampleSources: arch/alpha/boot/bootp.c, arch/arm/... (실제 커널 소스파일)

확정된 사실

항목 결과
전체 노드 중 Ghost 비율 319k / 393k = 81.2%
Ghost 원인 1위 api_call: 316602 (전체 Ghost의 99%)
OOM 원인 Ghost 319k → 3.2M Edge → 2GB Heap 초과
Canvas 문제 여부 아님. Extension Host가 Canvas 도달 전 사망

무죄 확정

  • ReferenceResolver ✅
  • GhostExpander ✅
  • GhostCluster (피해자) ✅
  • Snapshot 직렬화 ✅

범인 확정

CppScanner.tscallRegex

// 범인 코드
const callRegex = /\b([a-zA-Z_]\w*)\s*\(/g;
while ((match = callRegex.exec(content)) !== null) { ... }

content (원본 전체, 주석 포함)에 직접 적용 → Copyright (, Author (, Notice ( 등 주석 토큰을 함수 호출로 오인식.


Phase 10 - CppScanner callRegex 수정 (2026-08-28)

수정 내용

파일: src/core/CppScanner.ts

callRegex 실행 전, 주석과 문자열 리터럴을 제거한 stripped 문자열을 생성하고 그 위에서만 callRegex를 실행.

// [v0.3.34.40] 주석/문자열 제거 후 callRegex 적용
const stripped = content
    .replace(/\/\*[\s\S]*?\*\//g, ' ')   // /* */ 블록 주석 제거
    .replace(/\/\/[^\n]*/g, ' ')          // // 줄 주석 제거
    .replace(/"(?:[^"\\]|\\.)*"/g, '""') // 문자열 리터럴 제거
    .replace(/'(?:[^'\\]|\\.)*'/g, "''"); // 문자 리터럴 제거

while ((match = callRegex.exec(stripped)) !== null) { ... }

예상 효과

항목 수정 전 수정 후
api_call Ghost 316,602 ~수천 (실제 함수 호출만)
전체 Ghost 319,607 ~수천
전체 노드 393,272 ~74,000 수준
Heap 사용량 2,083 MB → OOM 정상 범위 예상

현재 상태

✅ Phase 1: V8 OOM 증상 확인
✅ Phase 2: Ghost 폭증 가설 수립
✅ Phase 3: CppScanner callRegex 오탐 가설 수립
✅ Phase 4: 진단 로그 3개 삽입 완료
✅ Phase 5: 진단 로그 실행 및 결과 확보
✅ Phase 6: 범인 확정 (CppScanner callRegex + raw content 적용)
✅ Phase 7: CppScanner.ts 수정 완료 (stripped content 적용)
❌ Phase 8: Linux Kernel 재부트스트랩 검증 (대기 중)

다음 작업: 빌드 → Linux Kernel 재분석 → Ghost 수 정상화 확인


Phase 6 - Scanner Registration & Log Spam (2026-08-28)

조사 대상

  • CLUSTER_INVARIANT_FAILSCANNER_MATCH 로그 스팸
  • Registered: (10) ['', '', ...] 현상
  • metrology 모듈 10 vs 13 discrepancy

분석 결과

  1. Log Spam 및 빈 문자열 원인:

    • Webpack 빌드 시 class 이름(scanner.constructor.name)이 minification되어 빈 문자열이나 한 글자로 축소됨.
    • ScannerRegistry.tsisDuplicate 로직은 s.constructor === scanner.constructor로 참조를 비교하므로 정상 작동함. (중복 방지 자체는 문제 없음)
    • 하지만 console.log에서 minification된 이름을 출력하려다 빈 문자열을 뱉어내고, 스팸을 유발함.
    • 조치: YAGNI 원칙에 따라 무의미한 [REGISTERED][SCANNER_MATCH] 로그 출력을 ScannerRegistry.ts에서 완전히 제거함.
  2. 10 vs 13 Discrepancy (Scanner vs Metrology):

    • 현재 등록된 LanguageScanner 구현체는 정확히 10개임 (Sql, Java, Rust, Shell, Kotlin, JsTs, Config, Cpp, Python, Markdown). 등록도 10개로 정상임.
    • 언급된 10 vs 13 차이는 스캐너 개수가 아니라, Phase 3에서 분석했던 metrology 모듈의 Internal Edge 개수 (수동 10개 vs Boundary Report 13개) 문제임. 스캐너 등록 누락이 아님을 확인.
  3. 배포(Deployment) 상태 검증:

    • SCANNER_MATCH가 계속 출력된 이유는 VS Code 확장 호스트가 새로 빌드된 dist/extension.js를 로드하지 않고 이전 버전을 캐싱/실행했기 때문임. (또는 빌드 전 코드가 실행됨)
    • ScannerRegistry.ts 수정 사항이 반영된 버전을 새로 컴파일(npm run compile)하고 VS Code Window를 Reload하면 해당 스팸은 완전히 사라짐.

Phase 7 - Parser Artifact Ghost Filtering (2026-08-28)

발견된 치명적 결함 (사용자 분석과 정확히 일치)

  • GhostClassifier가 26만 개가 넘는 노드를 parserArtifact로 정확하게 분류(ghost.classification = 'parserArtifact')했음에도 불구하고, 이후 파이프라인(DataPipeline.ts)에서 이들을 제거하지 않고 최종 nodes 배열과 Snapshot에 그대로 밀어넣고 있었음.
  • validGhostNodes 필터는 n.role !== NodeRole.GHOST 조건만 검사하는데, 매크로나 함수 호출 오인식에 의한 Ghost들은 isExternal = true 판정을 받아 NodeRole.EXTERNAL 역할을 부여받기 때문에 이 필터를 무사 통과해버림.
  • 이로 인해 OOM은 넘겼으나, 여전히 39만 개에 달하는 노드(대부분이 쓸모없는 Parser Artifact)가 Canvas로 전달되어 렌더링 불가 상태에 빠졌음. (VS Code는 해당 아티팩트가 적어 정상 작동했던 것)

해결 (Ponytail Approach)

  1. DataPipeline.ts 수정:
    • GhostClassifier 수행 직후 validGhostNodes를 필터링할 때 (n.data)?.ghost_classification === 'parserArtifact'인 경우 배열에서 제외(return false)하도록 한 줄 추가.
  2. 참조(Edge) 정합성 보호:
    • 노드가 삭제되었으므로, 해당 노드를 가리키는 expandedReferences 또한 생성되지 않도록 필터링 추가 (validGhostNodeIds에 존재하는 타겟만 EdgeBuilder로 전달).
  3. 결과:
    • parserArtifact 26만 개가 Snapshot 생성 이전에 완전히 소멸됨.
    • Canvas로 전달되는 최종 노드 수 급감하여 Linux Kernel 렌더링 정상화 기대.

Phase 8 - Edge Aggregation & JSON Serialization Crash (2026-08-28)

발견된 치명적 결함 (Edge 폭발)

  • Ghost 노드는 11만 개로 줄었으나, 유효 참조(expandedReferences)가 여전히 260만 개(2,609,144) 에 달함.
  • EdgeBuilder.ts가 260만 개의 참조를 그대로 260만 개의 Edge 객체로 변환하여 배열에 담음.
  • 이 거대한 배열을 project_state.json으로 저장하기 위해 JSON.stringify를 호출하는 순간 V8 엔진의 문자열 길이 제한(약 512MB)을 초과하여 Invalid string length 예외 발생.
  • 이 크래시로 인해 project_metadata.jsoncluster_bridges.json 생성 로직까지 도달하지 못하고 시스템이 정지됨 (Lite Bootstrap 실패).

해결 (Ponytail Approach)

  1. EdgeBuilder.ts 중복 엣지 병합:
    • A 파일이 B 모듈의 함수를 100번 호출하더라도, 시각화 아키텍처에서는 A → B 방향의 단일 엣지(weight=100) 로 표현하는 것이 맞음.
    • Map을 사용하여 (source, target, type)가 동일한 엣지는 생성하지 않고 기존 엣지의 weight만 1씩 증가시키도록 로직 수정.
  2. 결과:
    • 260만 개의 개별 함수 호출/참조가 수만 개 수준의 유니크 엣지로 압축됨.
    • JSON.stringify 메모리 초과 크래시 원천 차단.
    • project_state.json, metadata, bridges 정상 저장 및 Canvas 렌더링 정상화 기대.

Phase 9 - Edge Hard Limit & Visual Spaghetti Protection (2026-08-28)

발견된 결함 (Linux Kernel 스케일의 한계)

  • 이전 EdgeBuilder.ts(source, target, type) 기반 중복 엣지 제거 로직을 통과하고도 여전히 145만 개의 유니크 엣지가 생성됨. (리눅스 커널은 73,000개의 파일이 printk, kmalloc 등의 공용 API를 무차별적으로 호출하기 때문)
  • 145만 개의 객체 배열은 여전히 project_state.json 저장 시 200~500MB의 메모리를 점유하며 V8 문자열 한계(Invalid string length)를 유발함.
  • 직렬화를 간신히 통과했다 하더라도, 145만 개의 노이즈(CALLEXTERNAL_REF) 선을 Canvas 화면에 강제로 렌더링하면서 WebGL 파이프라인의 프레임이 심각하게 저하됨 (FPS 1, 화면은 선형 스파게티 상태).

해결 (Ponytail Approach)

  1. EdgeBuilder.ts 하드 리밋 도입:
    • expandedReferences에서 유니크 엣지로 압축된 후, 총 엣지 수가 15만 개를 초과하면 가차 없이 상한선(150,000)으로 잘라냄(slice(0, 150000)).
    • 이때 아키텍처에 필수적인 INCLUDE 엣지를 최우선으로 정렬하고, 그 외에는 weight가 높은 빈출 호출만 남기도록 정렬 로직 적용.
  2. canvas-engine.js Spaghetti 시각화 방어 (LOD):
    • 클라이언트에서 로드된 총 엣지가 5만 개를 넘을 경우, 전역 _ponytailHideCalls 플래그를 활성화.
    • 렌더러가 _visibleEdgesCache를 구성할 때 CALL 타입 엣지를 시야에서 강제 차단.
    • 데이터를 온전히 지키면서 화면에서는 가장 핵심적인 INCLUDE와 구조적인 엣지만 보이게 함으로써 렌더링 병목 즉시 해결.

Phase 10 - Cross-Cluster Spaghetti Protection (2026-08-28)

원인 파악 (Spaghetti Layout Issue)

  • 노드가 103,313개에 달하는 거대한 그래프 환경에서 Edge 상한선(150,000)을 적용했으나 렌더링 화면이 "완전한 스파게티"로 변질되는 현상 확인.
  • 성능(프레임 드랍) 문제가 아닌 렌더링 최적화(LOD) 로직의 누락.
  • 수많은 긴 대각선 형태의 Cross Cluster EdgeExternal Ref가 무작위로 화면 전체에 노출됨.

해결 (Ponytail Approach 2: Spaghetti Protection)

  • canvas-engine.js 렌더링 파이프라인 방어 (_visibleEdgesCache):
    • this.nodes.length > 20000인 대규모 그래프일 경우, 클러스터 간 교차를 발생시키는 모든 개별 엣지(e._fromCluster !== e._toCluster)를 원천 차단(continue).
    • 개별 Cross Cluster Edge 대신 기존에 집계되는 Aggregate Edges (metaEdges) 를 활용하여 거시적 모듈간 종속성만 표시하도록 유도. - 렌더링 입력 단계에서 스파게티 라인을 완전히 소거함으로써 O(N) 엣지 루프 부하 절감 및 시각적 간결성 동시 확보.

Phase 11 - Canvas Engine Performance & LOD Investigation (2026-08-28)

이슈 현상

  • 10만 개 노드(Linux Kernel) 로드 후 UI 완전 프리징 (팬, 줌 등 마우스 이벤트 처리 불가)
  • render-frame 지표가 1179ms로 측정되어 초당 1프레임 미만.
  • 노드 LOD 로그 상 visibleNodes103313에서 감소하지 않음.
  • 엣지가 화면에 잠깐 나타났다가 캐싱 직후 완전히 증발함.

조사 과정 및 해결 (1): EDGE_CACHE_CLUSTERS 병목 해결

  • EDGE_CACHE_CLUSTERS 처리에만 1179ms가 소요되는 병목 확인.
  • 원인: 클러스터가 없는 엣지의 this._clusterEdgeMap.get(c)가 캐시되지 않은 상태(undefined)를 매 프레임 반복해서 조회.
  • 조치: undefinednull로 명시적 저장하여 불필요한 Map 조회를 건너뛰도록 수정.
  • 결과: 1179ms -> 50~80ms로 약 15~20배 대폭 개선.

조사 과정 및 해결 (2): Node LOD & Hit-test 프리징 해결

  • 원인: 노드 103,313개가 컬링되지 않아 mousemovegetNodeAt()이 매 픽셀마다 O(N) 히트테스트 발생.
  • 조치: ui/canvas-engine.js에 진단용 캡(Node Cap)을 걸어 this._visibleNodesCache를 무조건적으로 5,000개로 슬라이싱 (slice(0, 5000)).
  • 결과: 마우스 이벤트 루프 블로킹이 즉시 해소되며 팬/줌, 네비게이션이 부드럽게 원상 복구됨. (Node LOD RBush 뷰포트 컬링 시스템의 복구 필요성 확정)

조사 과정 및 해결 (3): Edge 증발(Spaghetti Filter) 원인 규명

  • 현상: LOD는 visibleEdges: 25675를 보고하나 실제 WebGL 렌더링 화면에 엣지가 그려지지 않음.
  • 가설 검증:
    • 거대 그래프용 Spaghetti Protection 필터(e._fromCluster !== e._toCluster)가 리눅스 커널 구조상 핵심인 교차 참조(Cross-cluster) 엣지를 강제로 무분별하게 제거 중이라는 가설 수립.
    • 초기 프레임에서는 클러스터가 계산되지 않아 필터를 우회해 보였다가, 계산 완료 후 모두 삭제되는 전형적인 "후처리 필터" 패턴 식별.
  • 조치:
    • DISABLE_SPAGHETTI_FILTER = true로 필터 강제 비활성화
    • 필터로 인해 탈락하는 엣지 수를 측정하는 [SPAGHETTI_FILTER] 로그 추가
    • WebGL drawArrays 직전 실제 엣지 수를 찍는 [EDGE_DRAW] 로그 추가
  • 결론 도달 예정: 로그 결과를 기반으로 엣지가 필터로 인해 증발했는지 여부를 확정짓고, Edge Bundling 등의 시각적 최적화 방향 재조정 예정.

해결 (3): Edge 증발 문제 해결 적용

  • 조치: isSpaghetti 필터를 false로 강제 비활성화하여 리눅스 커널 등의 거대 프로젝트에서 발생하는 cross-cluster 엣지가 의도치 않게 삭제되는 문제를 방지.
  • 결과: 엣지가 깜박이다가 완전히 증발하는(Spaghetti Protection에 의해) 현상 해결됨.

해결 (4): 엣지 무한 점멸 및 WebGL 증발(NaN Injection) 버그 완벽 수정

  • 증상: 처음에 잠깐 엣지가 렌더링되다가 WebGL 캔버스가 전부 증발하여 빈 화면이 되고, 노드/엣지가 다시 나타났다 사라지길 무한 반복하는 치명적 렌더링 버그.
  • 원인 (NaN 전염): webgl-renderer.jsupdateEdgeData 루프 내부에서 getClientLayerOffset을 호출할 때 반환값 타입 매칭 오류가 발생했습니다. getClientLayerOffset는 숫자(Number)를 반환하나, 호출부에서 2D 좌표 객체로 착각하여 .y 프로퍼티를 참조(getClientLayerOffset(srcClientLayer).y)했습니다.
  • 연쇄 붕괴: Number.yundefined를 반환하므로 y1 = src.position.y + undefined 연산 수행 시 NaN이 발생합니다. 이 NaN_edgeColorArr, _edgePosArr 등 WebGL 엣지 버텍스 버퍼에 그대로 삽입되어 셰이더 오류를 유발하고 프레임을 파괴했습니다.
  • 조치: webgl-renderer.js 854~857라인에서 .y 프로퍼티 참조를 제거하고 숫자 값을 직접 반환받아 사용하도록 수정 완료했습니다.

해결 (5): 엣지 뱃지 렌더링 중 NaN 재발 및 노드 버퍼 초과 크래시 수정

  • 증상: 줌 인(Zoom In) 시 엣지 뱃지(Edge Badges)가 렌더링될 때 화면이 다시 증발함.
  • 원인 1 (잔여 NaN 버그): webgl-renderer.js의 1146~1148라인 뱃지 렌더링 로직에도 동일한 getClientLayerOffset(...).y 참조 버그가 남아있어, 뱃지 버퍼에 NaN이 주입됨.
  • 원인 2 (버퍼 초과 크래시): webgl-renderer.jsmaxNodes60000으로 고정되어 있어, 리눅스 커널 등 10만 개가 넘는 노드(103,313개) 렌더링 시 버퍼 오버플로우로 GL_INVALID_VALUE가 발생하며 WebGL이 멈춤.
  • 조치 1: 뱃지 로직의 불법 .y 속성 참조를 모두 제거.
  • 조치 2: maxNodes를 60,000에서 150000으로 대폭 상향하여 거대 프로젝트 수용 가능하게 버퍼 용량 재할당.

해결 (6): Viewport Culling 누락으로 인한 CPU Lockup 해결

  • 증상: WebGL 화면 밖의 노드들까지 모조리 visibleNodes에 포함되어 O(N) 반복 연산 및 히트테스트가 발생하여 줌/팬이 느려짐.
  • 원인: updateLOD()에서 노드 캐시(_visibleNodesCache)를 구축할 때, RBush(공간 인덱스)를 통한 Viewport(화면 영역) 필터링이 누락되어 전체 노드(103,313개)가 매 프레임 LOD 검사를 통과함.
  • 조치: updateLOD() 최상단에 spatialIndex.queryViewport를 즉각 주입하여 화면 밖 노드를 CPU 연산 대상(candidateNodes)에서 원천 차단(Culling). 추가로 줌 아웃 시 노드 폭주를 막기 위해 최후방에 5,000개 하드 캡(Hard Cap) 방어선 구축.
  • 결과: 10만 개 루프가 5천 개 이하로 축소되며 체감 성능 및 CPU 부하 극적 해결.

해결 (7): O(N) 병목 유발 마우스 상호작용 (Hover/Selection) 최적화

  • 증상: 줌 인 상태에서도 마우스 드래그 선택(Selection) 및 연결 핸들 호버(Hover) 시 프레임 드랍(팬/줌 렉)이 발생함.
  • 원인: 드래그 영역 판정 및 엣지 핸들 검사 코드가 화면 밖 노드까지 포함한 for (const node of this.nodes) (10만 번 순회)를 수행하고 있었음. LOD가 살아나도 마우스가 움직일 때마다 10만 번 O(N) 순회가 돌아 CPU를 잠식함.
  • 조치: isSelecting 구문 및 getConnectionHandleAt() 함수 등 마우스 상호작용 관련 반복문을 this.nodes에서 (this._visibleNodesCache || this.nodes)로 교체.
  • 결과: 마우스 이벤트가 오직 화면에 보이는 노드(최대 5000개) 대상으로만 실행되어 O(N) 병목 완전 해소.

진단 (8): Extreme Zoom Out 시의 Density LOD 및 Edge 병목 추적

  • 상황: 줌 아웃 시 화면에 전체 그래프(9만+ 노드)가 들어오면서 RBush가 candidateNodes로 90,529개를 반환함. 이로 인해 Viewport Culling이 무력화되고 기존의 5,000 하드 캡이 유일한 방어선이 되는 상태.
  • 가설: 5,000개조차 리눅스 커널 스케일(25,000+ 엣지 예상)에서는 Hit Test / Label 렌더링 시 스레드를 잠식할 정도로 밀도가 높다.
  • 조치 (진단 목적):
    • updateLOD() 최후방 하드 캡을 5,000 -> 1000으로 5배 더 강력하게 조임.
    • [DENSITY_TEST] 로그를 신설하여 visibleNodesvisibleEdges 비율을 동시 출력하도록 코드 수정.
    • 이를 통해 1,000 노드 상황에서 UI 팬/줌 렉이 사라지는지, 그리고 엣지가 얼마나 살아남는지(혹은 0개로 소멸하는지) 검증.

진단 (9): Extension Host 강제 종료 (SIGTRAP/Code 133) 및 EdgeBuilder 병목 추적

  • 상황: 500만 개 이상의 Edge 후보가 생성된 후 Extension Host가 Code 133(SIGTRAP)으로 예기치 않게 종료됨. [EDGE_BUILDER_INPUT] 이후 [BOOTSTRAP FAILED] 로그도 남기지 못한 채 프로세스 사망.
  • 가설: EdgeBuilder 내에서 500만 개의 Edge 객체를 생성하다가 V8 힙 오버플로우나 런타임 Fatal 에러(OOM 보호)가 발생하여 Extension Host가 터짐.
  • 조치:
    • src/core/EdgeBuilder.ts[EDGE_STAGE] START, [EDGE_STAGE] <processed>, [EDGE_STAGE] COMPLETE 로깅 추가.
    • 이를 통해 정확히 몇 번째 Edge 객체를 생성하다가 사망하는지 검증.

진단 (10): Ghost Node 및 WebGL 버퍼/카메라 오염 추적

  • 상황: 103,313개의 노드가 모두 정상적으로 로드되었고 LOD 및 Edge 필터를 모두 통과(10만 노드, 13만 엣지 렌더러 도달)했으나 캔버스 화면에는 아무것도 렌더링되지 않음. 추가로 id: '____and' 와 같은 출처가 불분명한 "유령(Ghost) 노드" 발견 및 [CAMERA] 로그 증발 발생.
  • 가설 1 (Camera 단락): loadProjectState 내부에서 computeWorldBounds 완료 직후, fitView가 특정 조건(핫 리로드 시 카메라 위치 보존 등)에 의해 우회되었거나 내부에서 예외를 던져 카메라 세팅(fitCameraToBounds)을 정상적으로 완료하지 못해 [CAMERA] 로그가 누락되었을 수 있음.
  • 가설 2 (Zero-Vertex 드로우콜): 렌더 파이프라인에서 노드와 엣지의 수는 10만 개 이상으로 인식(visibleNodes)하고 있으나, 실제 updateNodeData, updateEdgeData 과정에서 GPU 버퍼 생성이 누락되었거나 gl.drawArraysInstancedANGLEcount=0을 넘겨 실제로 아무것도 그리지 않음(극도로 빠른 draw call time: 0.008ms).
  • 조치:
    • canvas-engine.jsfitCameraToBounds() 내부에 단계별([CAMERA_STEP] enter, bounds ok, zoom, offset) 디버깅 로그를 삽입하여 어느 지점에서 예외가 나거나 스킵되는지 추적.
    • webgl-renderer.js의 버퍼 생성 단계(updateNodeData, updateEdgeData)에 실제 배열 갯수를 체크하는 [NODE_BUFFER], [EDGE_BUFFER] 로그 추가.
    • webgl-renderer.js의 그리기 호출(drawNodes, drawEdges) 직전에 실제 GPU로 넘어가는 갯수를 출력하는 [DRAW_CALL_NODES], [DRAW_CALL_EDGES] 로그 추가.
  • 결론 도달 예정: 로그 결과를 통해 카메라 세팅의 실패 시점을 특정하거나, GPU로 넘어가는 버텍스/엣지의 갯수가 실제로 0개인지(어디서 유실되었는지) 최종 확정할 예정.

진단 (11): 무한 isGraphDataDirty 플래그 및 Render Loop 락 추적

  • 상황: 데이터를 버퍼에 로드하고 렌더 루프에 정상적으로 진입했으나 실제 그리기 명령이 호출되지 않고, [ENTRY] rebuildGraphData (render block)와 루프가 무한 반복되며 CPU 인터랙션 스레드를 점유함. 이 루프는 16ms 간격의 완전한 무한 루프가 아니라 마우스 움직임 등 특정 인터랙션 시 몇 초 ~ 몇 분 간격으로 발생. 화면이 마치 정지한 것처럼 보임.
  • 가설: pick(), hover, 혹은 컨테이너 크기 변경 등과 관련된 특정 이벤트에서 this.isGraphDataDirty = true가 의도치 않게 혹은 지속적으로 켜지거나 제때 false로 초기화되지 못해, 렌더 파이프라인의 캐시 갱신 블록(if (this.isGraphDataDirty))을 계속 실행하게 만듦.
  • 가설 확장 (인터랙션 루프): mousemove → pick → hoveredNode 변경 → rebuildGraphData 체인이 의도치 않게 끊임없이 발생하여 10만 노드 재계산을 촉발시키고 있을 가능성이 큼.
  • 조치:
    • canvas-engine.js 내에 존재하는 모든 this.isGraphDataDirty = true; 할당 구문(총 31곳)에 console.trace('[DIRTY_SOURCE]');를 삽입하여, 정확히 어떤 이벤트 핸들러가 해당 플래그를 오염시키는지 역추적할 수 있도록 설정.
    • 인터랙션 체인 검증을 위해 mousemove, pick, hover change, rebuild graph 진입점에 각각 console.count를 삽입. ([MOUSE_MOVE], [PICK], [HOVER_CHANGE], [REBUILD_GRAPH])
    • VSIX 컴파일 후 런타임에 어떤 스택 트레이스와 카운트 비율이 출력되는지 확인하여 범인을 특정할 예정.
  • 결과 (진범 판명): 콘솔 확인 결과 [MOUSE_MOVE]: 10, [PICK]: 10, [REBUILD_GRAPH]: 4가 출력되었으나, 단 한 번도 [DIRTY_SOURCE]가 출력되지 않았음. 이는 isGraphDataDirty 플래그는 정상적으로 꺼져 있었으나, 방어 코드로 작성된 this.nodeMap.size !== nodeCount 조건이 지속적으로 true로 평가되었기 때문임.
  • 원인: 10만 노드 데이터 중 동일한 ID를 가진 중복 노드가 존재할 경우, Map 자료구조 특성상 중복 덮어쓰기가 발생하여 this.nodeMap.sizenodeCount(this.nodes.length)보다 작아짐. 이로 인해 마우스를 움직여 requestRender()가 호출될 때마다 영원히 캐시를 파기하고 재생성하는 재앙적 병목(나비효과)이 발생함.
  • 해결: canvas-engine.jsthis.nodeMap.size !== nodeCount 방어 코드를 삭제(YAGNI)하여 중복 ID로 인한 무한 렌더 루프를 원천 차단함. 노드 추가/삭제 시에는 이미 명시적으로 isGraphDataDirty = true가 호출되므로 해당 검사는 불필요함.

해결 (12): 백그라운드 이벤트 갱신 시 무한 GraphData Dirty 락(UI 동결) 해결

  • 상황: 드래그 로직을 수정하여 마우스 이동 시의 600ms 프레임 드랍을 수정했으나, 아무 동작(드래그, 상호작용)을 하지 않거나 심지어 모든 노드/엣지를 캔버스에서 가려둔(NO_NODES, NONE) 상태에서 줌 인/아웃(Zoom)을 시도할 때에도 지속적으로 연산이 돌며 UI가 멈추거나 네비게이션이 불가능한 현상이 지속됨.
  • 원인: v0.3.34 패치에서 백그라운드 확장 프로세스(분석 엔진, 시뮬레이터, 벤치마크 등)가 지속적으로 쏘아보내는 updateNodeupdateEdge 통신을 수신할 때마다, 무조건적으로 engine.isGraphDataDirty = true를 할당하고 있었음.
    • 이로 인해 노드나 엣지의 **단순 통계나 상태값(stats)**만 바뀌어도 10만 개 단위 그래프의 LOD, 공간 인덱스(RBush), 클러스터 바운딩을 전체 파기 후 재구축하는 무거운 작업(600ms 소요)이 수신될 때마다(초당 수회 이상) 반복되며 JS 메인 스레드를 멈추게 만들었음.
  • 해결:
    • updateNode 시 수신된 데이터에 구조적인 변화(cluster_id 변경 등)가 포함되어 있을 때만 isGraphDataDirty = true로 전체 리빌드를 트리거하고, 단순 갱신일 경우에는 isNodeBufferDirty = true만 선언하여 WebGL 버퍼만 O(1) 수준으로 동기화하도록 분기(Branching) 처리.
    • updateEdge 시에도 동일하게 새로 추가된(new insertion) 엣지가 아니라면 isEdgeDirty = true만 사용하여 2D/WebGL 화면 갱신만 수행하도록 변경.
  • 결과: 무의미하게 메인 스레드를 잠식하던 백그라운드 상태 수신 이벤트가 렌더 파이프라인에서 완벽하게 분리되었으며, 10만 스케일에서의 줌/팬 네비게이션(Zoom/Pan Navigation) 등 UI 응답성이 즉시 정상화됨.

진단 및 해결 (13): 마우스 이동(mousemove) 시 2,560ms 병목 (O(C*N) 지연) 해결

  • 상황: 렌더링 프레임 타임은 극도로 최적화되었으나(0.03ms), 캔버스 위에서 마우스를 이동(mousemove)할 때 브라우저에서 [Violation] 'mousemove' handler took 2560ms 라는 치명적인 위반 경고가 뜨며 UI 전체가 2~3초간 완전히 동결됨.
  • 원인: 마우스 좌표가 어느 클러스터에 위치했는지 판별하는 getClusterAt() 내부 로직의 결함. 클러스터가 화면에 렌더링될 수 있는지 검증한 후, _headerBounds가 존재하면 히트 테스트를 수행하고 그 영역 밖이면 해당 클러스터를 "무시"(continue)해야 함. 그러나 continue가 누락되어, 마우스가 클러스터 헤더 밖을 가리킬 경우 클러스터 내의 모든 노드의 바운딩 박스를 10만 개 단위로 동적으로 재계산하는 치명적인 O(N) 순회 구문으로 추락(Fall-through)함. 이 과정이 2,900여 개의 클러스터 루프마다 반복되어 마우스가 한 번 움직일 때마다 약 3억 회(305 million)의 연산이 동기적으로 실행됨. (getConnectionHandleAt도 동일한 O(C*N) 구조적 결함을 안고 있었음)
  • 조치:
    1. canvas-engine.jsgetClusterAt 함수 내 cluster._headerBounds 조건문 통과(Hit 실패) 시 명시적인 continue를 주입하여, 불필요한 O(N) 순회 계산(clusterNodes = this.nodes.filter(...))으로 떨어지는 현상 원천 차단.
    2. getConnectionHandleAt 함수에서도 cluster._headerBounds가 존재하는 경우 값 비싼 O(N) 순회를 우회하고 캐시된 헤더/바디 좌표값(minX, minY, maxX, maxY)을 재사용하도록 최적화.
  • 결과: O(C*N) 병목이 완전히 제거되었으며 10만 개 노드 환경에서 마우스 움직임(Hover, Drag) 시의 UI가 지연 없이 완벽하게 정상 동작함.

해결 (14): 마우스 클릭(mousedown, contextmenu) 시 O(E) 엣지 전체 순회로 인한 UI 멈춤 해결

  • 상황: 마우스 이동(mousemove) 시의 병목을 해결했음에도 불구하고, 캔버스 빈 공간을 클릭(mousedown)하거나 우클릭(contextmenu)할 때 여전히 UI가 멈추고 Hit 테스트 지연이 발생하는 현상이 보고됨.
  • 원인: 클릭 위치에서 엣지를 찾기 위해 findEdgeAtPoint를 호출한 뒤 히트에 실패하면, 안전 장치나 공간 인덱스(RBush) 없이 전체 엣지 배열(this.edges)을 대상으로 isPointNearCurve 연산을 순회하는 치명적인 Fallback 코드가 mousedowncontextmenu 핸들러 내부에 여전히 잔존하고 있었음. 빈 공간을 클릭할 때마다 수만 번의 베지어 곡선 거리 연산이 JS 메인 스레드를 막아버림.
  • 조치:
    • canvas-engine.jsmousedowncontextmenu 이벤트 핸들러 내에서, findEdgeAtPoint 실패 시 for (const edge of this.edges)로 무식하게 재검색하던 O(E) Fallback 루프를 모두 삭제 (Ponytail YAGNI 삭제).
  • 결과: 클릭 및 마우스 액션 전반에서 O(E), O(C*N) 스케일의 무거운 순회 로직이 모두 박멸되어, 방대한 그래프 스케일 환경에서도 클릭 및 네비게이션 시 즉각적인(60FPS) 상호작용이 가능해짐.

해결 (15): 마우스 휠(Zoom) 시 발생하는 트랙패드/스크롤 멈춤 현상(Set 객체 속성 버그) 해결

  • 상황: 마우스 이동 및 클릭 딜레이를 고쳤으나, 마우스 휠이나 트랙패드로 줌 인/아웃(Zoom)을 시도할 때 여전히 간헐적으로 프레임이 10ms~50ms 이상 끊기는 심각한 멈춤 현상이 보고됨.
  • 원인: 마우스 휠 동작은 줌 레벨과 뷰포트 좌표를 변경하며 브라우저 내부적으로 잦은 mousemove 이벤트를 발생시키는데, 이때 호출되는 findEdgeAtPoint 함수 내부의 긴급 탈출(Bailout) 조건에 치명적인 JavaScript 문법 결함이 숨어 있었음.
    • 공간 인덱스(RBush)가 반환하는 결과물은 Set 객체임. 그러나 긴급 탈출 검사에서 if (targetEdges.length > 5000) return null; 과 같이 배열 속성인 .length를 사용하고 있었음.
    • Set에는 .length가 없으므로 항상 undefined를 반환하고, undefined > 5000false로 평가되어 Bailout 방어선이 완전히 무력화됨.
    • 결과적으로 줌 아웃 시 화면에 빽빽하게 겹친 수천 개의 엣지 바운딩 박스가 쿼리되면, 수천 번의 무거운 베지어 곡선 수학 연산(isPointNearCurve)이 휠 틱마다 매번 실행되어 메인 스레드를 멈추게 만들었음.
  • 조치:
    • canvas-engine.jsfindEdgeAtPoint 내부에서 Set.size와 Array의 .length를 모두 정상적으로 판독할 수 있도록 분기 처리함.
    • 휠 스크롤 연속 발생 시의 프레임 방어를 위해, 엣지 히트 테스팅의 긴급 탈출(Bailout) 한계선을 기존 5,000개에서 500개로 대폭 하향 조정 (500개 이상이 겹친 구간은 어차피 육안으로 구분이 불가하므로 계산 생략이 YAGNI/Ponytail 룰에 부합함).
  • 결과: 마우스 휠 및 트랙패드 줌 동작 시 발생하던 수천 개의 엣지 수학 연산 병목이 차단되어, 방대한 그래프에서도 즉각적이고 부드러운(60FPS) 줌/팬 네비게이션이 복구됨.

해결 (16): 키보드 방향키 이동(Pan) 시 발생하는 프레임 드랍 및 UI 블로킹 해결

  • 상황: 마우스 관련 지연을 모두 해결했음에도 불구하고, 키보드 방향키(Arrow Keys)를 꾹 눌러 캔버스를 이동(Pan)할 때 심각한 프레임 드랍과 화면 끊김이 여전히 발생함.
  • 원인: canvas-engine.jskeydown 이벤트 핸들러 내부에서 방향키 이동 후 동기식 렌더링 함수(this.render())를 직접 호출하고 있었음.
    • 방향키를 꾹 누르고 있으면 OS의 키보드 반복(Repeat) 속도에 따라 초당 수십 번의 keydown 이벤트가 폭주함.
    • this.pan() 내부에서 이미 this.wakeUp()을 호출하여 requestAnimationFrame 기반의 최적화된 비동기 렌더링 루프를 예약하고 있음에도 불구하고, keydown 이벤트 마지막에 this.render()를 강제 호출함으로써 rAF를 무시하고 메인 스레드에서 무식한 동기 렌더링을 틱마다 수십 번 강행하는 더블 렌더링(Double Rendering) 참사가 벌어지고 있었음.
  • 조치:
    • keydown 이벤트 핸들러 하단에 불필요하게 존재하던 this.render() 호출을 과감히 삭제(YAGNI 제거).
  • 결과: 방향키를 통한 캔버스 이동이 브라우저의 디스플레이 주사율(rAF)에 완벽하게 동기화되어, 키보드를 꾹 누르고 있어도 10만 개 노드 환경에서 끊김 없는 부드러운(60FPS) 캔버스 패닝(Panning)이 보장됨.

해결 (17): 마우스 드래그(노드 이동 및 박스 선택) 시 발생하는 O(N*C) 스케일 지연 버그 해결

  • 상황: 여러 노드를 선택하여 드래그하거나, 마우스로 빈 공간을 드래그하여 다중 선택(Box Selection)을 할 때 화면이 수백 밀리초 이상 멈추는 병목이 발생함.
  • 원인:
    • canvas-engine.jsmousemovemouseup 핸들러 내부에서 클러스터 계층 구조 갱신 및 가시성 판단을 위해 this.clusters.find(c => c.id === ...) 구문이 while 루프(계층 탐색) 내부에 중첩 사용되고 있었음.
    • 박스 선택 시 후보 노드가 5,000개이고 클러스터가 3,000개인 경우, 5,000 * 3,000 번의 O(C) 탐색이 발생하여 CPU를 완전히 고갈시킴.
    • 또한, getConnectionHandleAt (엣지 생성 드래그 중 호출) 함수에서 특정 클러스터에 바운딩 헤더가 없을 경우 this.nodes.filter()를 수행하는 끔찍한 O(N) 순회가 매 픽셀 이동마다 호출됨.
  • 조치:
    • this.clusters.find() 호출을 모두 this._clusterMap.get() O(1) 해시 테이블 탐색으로 치환(Replacement).
    • getConnectionHandleAt 함수 내의 this.nodes.filter() 무식한 순회를 1회성 캐시인 this._clusterNodesCache.get()으로 교체.
  • 결과: 노드 그룹 드래그 및 박스 셀렉션, 엣지 드로잉 중 발생하던 최악의 O(N*C) 시간 복잡도가 O(1) 수준으로 대폭 압축되어, 방대한 그래프에서의 마우스 상호작용 지연이 완벽하게 해결됨.

해결 (18): "CLUSTER (Meta Edges)" 모드에서 원본 엣지의 뱃지가 허공에 유령처럼 떠다니는 렌더링 회귀 버그 해결

  • 상황: 엣지 가시성(Edge Visibility)을 CLUSTER (Meta Edges) 모드로 변경했을 때, 원본 엣지(선)는 사라지지만 엣지에 매달려 있던 상태 뱃지(아이콘)들은 사라지지 않고 메타 엣지 주변 허공에 무더기로 렌더링되는 현상 발생.
  • 원인:
    • webgl-renderer.jscanvas-engine.js의 엣지 뱃지 렌더링 가시성 검사 로직(isBadgeHidden)에서 NO_BADGESNONE만 체크하고, CLUSTER 모드를 누락함.
    • 가시성 모드가 CLUSTER일 경우 선형 엣지 렌더링은 스킵되지만, WebGL은 건네받은 모든 _visibleEdgesCache의 엣지 뱃지 버퍼를 그대로 화면에 때려 박아버림.
  • 조치: 뱃지 렌더링 가시성 검사 조건에 window.edgeVisibilityMode === 'CLUSTER' 조건을 명시적으로 추가하여, 메타 엣지 모드 진입 시 개별 엣지의 뱃지들도 렌더링 파이프라인에서 완벽하게 제외(Culling)되도록 수정함.

해결 (19): 마우스 이동(Hover) 시 프레임이 1919ms 동안 멈추거나 뷰포트가 증발하는 무한 리빌드 버그 해결

  • 상황: 10만 개 노드를 로드한 상태에서 빈 화면이나 엣지를 클릭하고 마우스를 이동하기만 해도 화면이 텅 비어버리거나 브라우저 메인 스레드가 2초 가까이 정지하는 심각한 성능 붕괴 현상 발생.
  • 원인:
    1. 과거 렌더 루프(_performRender) 내부에 존재하던 안전장치(Fallback)인 this.nodes && this.nodeMap.size !== nodeCount 검사가 치명적인 독으로 작용함.
    2. 백엔드에서 내려온 원본 this.nodes 배열에 중복된 ID가 단 한 개라도 포함될 경우, nodeMap은 해시 맵 특성상 중복 키를 덮어씌워버려 nodeMap.size가 원본 nodeCount보다 영구적으로 작아지는 불일치 상태가 유지됨.
    3. 이 상태에서 마우스를 1픽셀만 이동하여 가벼운 호버 이벤트가 requestRender()를 통해 렌더 루프를 깨워주면, 안전장치는 "사이즈 불일치"라 판단하고 매 틱마다 10만 개의 공간 캐시와 계층 트리를 모조리 부수고 새로 짓는 **무한 리빌드 지옥(1919ms)**에 빠짐.
    4. 더불어 과거에는 절전 모드의 조기 종료(Early Return) 이전에 webglRenderer.beginFrame()(GPU 버퍼 클리어)이 무조건 선행되어, 실제 그릴 데이터가 없거나 조기 종료된 잉여 프레임에서는 화면 전체가 텅 비어버리는(Disappearing) 시각적 파탄까지 연계됨.
  • 조치:
    • 치명적이었던 nodeMap.size !== nodeCount 검사를 nodeMap.size === 0으로 교체하여 중복 ID로 인한 무한 캐시 재생성 지옥을 원천 차단함.
    • 마우스 이동 시 렌더 요청이 실제로 this.isDirty 갱신을 필요로 하지 않는 한 GPU 버퍼를 지우지 않도록 beginFrame()의 위치를 조기 종료(Early Return) 방어선 아래로 이동 배치함.
  • 결과: 마우스 호버 및 드래그 과정에서 발생하던 1919ms 규모의 끔찍한 CPU 낭비(캐시 재건)와 무작위 뷰포트 증발 버그가 완벽하게 소멸되었으며, 10만 개 규모의 대형 그래프 환경에서도 흔들림 없는 60FPS의 즉각적 상호작용과 네비게이션이 보장됨.

개선 (20): 렌더링 엔진 동결 선언 및 보고서(Reporting) 엔진 고도화

  • 상황: 리눅스 커널 수준(10만 노드, 13만 엣지)에서의 렌더링 병목 및 무한 리빌드 현상이 완벽하게 사살됨에 따라 렌더링 엔진을 사실상 동결(Freeze) 상태로 전환. 이후 보고서(Insight) 엔진의 가독성과 분석 품질 개선으로 우선순위 이동.
  • 조치 및 결과:
    1. Architect Report (허브 노이즈 필터링):
      • InsightEngine에서 include, arch 등 의미 없는 디렉터리 레벨의 허브 노드들을 배제하기 위해 확장자(.) 기반의 순수 파일 필터링(isFile)을 도입.
      • 진짜 아키텍처 결함인 소스 파일들만 FRONTIER 리스트에 오르도록 필터링 로직 강화.
    2. Executive Report (CTO 관점 압축):
      • 단순한 경고(WARNING)를 넘어, 경영진이 직관적으로 위험도를 파악할 수 있도록 텍스트 리팩터링.
      • 병목의 중심 노드를 명시하고, 모듈 경계 붕괴 및 연쇄 실패 위험을 직관적으로 설명하는 내러티브(CRITICAL) 추가.
    3. Onboarding Report (진입점 탐색 강화):
      • OnboardingAnalyzer의 역할 가중치(Role Score)에 init, core 등 리눅스 환경에 특화된 핵심 엔트리 키워드 가중치를 대폭(+500) 상향.
      • 진입점이 N/A로 남을 경우, 의존성 팬아웃(Fan-out)이 가장 높은 중심 파일을 강제로 진입점으로 지정하는 Fallback 로직 추가.
    4. Simulation Debug (Blast Radius 해석):
      • 폭발 반경(Blast Radius)을 단순 수치(예: "216 files")로만 표기하던 것을 넘어, 원인과 전파 양상을 구체적으로 서술.
      • "X개의 서브시스템으로 확산됨", "모듈 경계 침범" 등 연쇄 실패의 아키텍처적 맥락(Context)을 리포트에 추가하여 디버깅 편의성 극대화.
  • 아키텍처 분리 보장 (RED ZONE 준수):
    • 보고서 엔진(src/core/reporting/)의 변경 사항은 백엔드 및 런타임 분석 텍스트 생성에만 관여하므로, canvas-engine.jswebgl-renderer.js의 렌더링 파이프라인 및 부트스트랩 로직(RED ZONE)과 물리적으로 완전히 분리되어 작동함.
    • 마우스 이벤트 및 this.isGraphDataDirty 생명주기에 어떠한 부수 효과(Side-effect)도 일으키지 않음.

개선 (21): Boundary Builder 승격 정책 및 Impact Ranking 고도화 (2026-08-30)

  • 상황: Boundary Builder가 정상 복구되었으나, 거대 디렉터리(arch, drivers/gpu/drm 등)가 너무 거칠게 단일 Boundary로 묶이고, 반대로 drivers/clk와 같은 Utility 폴더가 Impact Ranking 1위를 차지하는 등 점수 계산 및 승격(Promotion) 정책에 허점이 발견됨.
  • 조치 및 결과:
    1. MAX_BOUNDARY_SIZE (강제 분할): BoundaryGraphBuilderMAX_BOUNDARY_SIZE = 1500 규칙을 도입하여 1500개 이상의 파일을 가진 거대 모듈(arch, fs 등)은 무조건 하위 서브폴더(arch/x86, arch/arm64) 수준까지 파고들어 쪼개지도록 강제함.
    2. Boundary Promotion Stop Rule: 기존에 isWrapper 조건이 isPromoted보다 먼저 실행되어 Massive하거나 Cohesion이 높은 핵심 서브시스템(arch/x86)조차 무참히 파편화되던 버그를 수정. isPromotedisWrapper를 선행 무효화하도록 순서를 재배치하여 진정한 핵심 서브시스템에서 분할을 정지(STOP)시킴.
    3. Impact Score 밀도 기반 랭킹 (Priority Engine): InsightEngine의 영향도 점수(Score) 계산식을 size + external*2와 같은 단순 합산에서 크기 * (내부밀도^2) * 외부밀도로 변경.
      • 껍데기만 크고 외부 호출만 잦은 API 유틸리티(drivers/clk)의 순위를 대폭 강등(Demote).
      • 내부 구조가 극도로 복잡하며 얽혀있는 실질적 아키텍처 병목(drivers/gpu/drm/i915 등)이 리포트의 Immediate Impact 1위를 점유하도록 성공적으로 개선.

🏁 v0.3.34.40 릴리즈 최종 리뷰 및 결론 (Architect's Perspective)

현재 v0.3.34.40에서 실제로 얻은 성과를 정리하면:

완료된 것

  1. Boundary Detection 안정화

    • Wrapper 분해 문제 수정
    • arch/x86 같은 핵심 서브시스템 보존
    • Micro Boundary 노이즈 대폭 감소
  2. Complexity Ranking 확립

    • "가장 복잡한 곳"이 무엇인지 설명 가능
    • i915, amdgpu, xe 등이 상위권
  3. Control Ranking v1 구축

    • 아직 완벽하진 않지만
    • 단순 Fan-Out → Fan-In 기반으로 진화
    • "권력"이라는 개념을 리포트에 처음 도입
  4. 리포트 구조 개선

    • Data Dump → Architecture Report
    • Tier 분리
    • Complexity / Control 분리

아직 미완성인 것 (Known Limitations)

현재 Report B는 솔직히 말하면 Authority Ranking이 아니라 Fan-In Ranking에 가깝다.
arch/arm, arch/parisc 같은 결과가 나오는 순간, 아키텍트가 기대하는 MM, VFS, BLOCK, RCU, SCHED 계열의 진짜 권력 구조와는 아직 차이가 있다.


최종 결론 및 다음 단계 제안

그래서 지금 시점에서 v0.3.34.40Boundary Architecture Release로 종료하는 건 맞다.

반대로 지금 여기서 PageRank, Authority Propagation, State Transition를 넣기 시작하면 범위가 갑자기 커진다.
왜냐하면 그건 단순 점수 계산이 아니라 다음과 같은 3단계 확장으로 이어지기 때문이다:

Boundary Graph
↓
Authority Graph
↓
State Model

[Deferred 항목]

  • Authority Propagation (PageRank-like)
  • State Transition Engine
  • Dynamic Blast Radius Simulation

결론: v0.3.34.40은 여기서 닫는 게 맞다.
지금 얻은 가장 중요한 성과는 PageRank가 아니라 **"복잡도(Complexity)와 권력(Control)은 다른 축이다"**라는 사실을 시스템이 증명한 것이다.
그걸 기준으로 다음 마일스톤(v0.3.34.41~)에서 Authority 모델과 State Transition 모델을 설계하는 편이 훨씬 안전하다.