@@ -1131,6 +1131,9 @@ run_common_phases() {
11311131 # Claude CLI 자율 태스크 (매 σ²=144 사이클 — L3 suggestion 기반)
11321132 run_claude_auto_tasks " $cycle "
11331133
1134+ # 위상 체크 + 리포트 + 미연결 발견 (매 J₂=24 사이클)
1135+ engine_topology_check " $cycle " " $repo_name "
1136+
11341137 # 동기화
11351138 common_phase_full_sync
11361139}
@@ -2589,4 +2592,151 @@ with open(bus_file, 'a') as bf:
25892592" 2> /dev/null || echo " Fitness: error"
25902593}
25912594
2595+ # ═══════════════════════════════════════════════════════════════
2596+ # ENGINE TOPOLOGY CHECK — 위상 불변량 + 미연결 발견 + 리포트
2597+ # ═══════════════════════════════════════════════════════════════
2598+ # 매 J₂=24 사이클: 엔진 호출 그래프의 위상 구조 측정
2599+ # 미도달 함수 발견 → 자동 연결 시도 → 대발견 시 코드 반영
2600+
2601+ engine_topology_check () {
2602+ local cycle=" ${1:- 1} "
2603+ local repo=" ${2:- unknown} "
2604+
2605+ if [ $(( cycle % 24 )) -ne 0 ]; then
2606+ return
2607+ fi
2608+
2609+ log_info " [Topology] Engine topology check (cycle $cycle )"
2610+
2611+ python3 -c "
2612+ import re, collections, json, os, time, hashlib
2613+
2614+ engine = os.path.expanduser('~/Dev/nexus6/lib/growth_common.sh')
2615+ n6_dir = os.path.expanduser('~/.nexus6')
2616+ bus_file = os.path.expanduser('~/Dev/nexus6/shared/growth_bus.jsonl')
2617+
2618+ with open(engine) as f:
2619+ content = f.read()
2620+ lines = content.split('\n')
2621+
2622+ # 함수+호출 추출
2623+ funcs = set()
2624+ edges = []
2625+ adj = collections.defaultdict(set)
2626+ current = None
2627+ for line in lines:
2628+ m = re.match(r'^([a-z_]+)\(\)\s*\{', line)
2629+ if m:
2630+ current = m.group(1)
2631+ funcs.add(current)
2632+
2633+ for fname in funcs:
2634+ for line in lines:
2635+ if current != fname and re.search(r'\b' + re.escape(fname) + r'\b', line):
2636+ # 이 line이 어느 함수 안에 있는지
2637+ pass
2638+ # 간단하게: 함수 본문에서 다른 함수 호출
2639+ for fname in funcs:
2640+ in_func = False
2641+ for line in lines:
2642+ if f'{fname}()' in line and '{' in line:
2643+ in_func = True
2644+ continue
2645+ if in_func:
2646+ for other in funcs:
2647+ if other != fname and re.search(r'\b' + re.escape(other) + r'\b', line):
2648+ edges.append((fname, other))
2649+ adj[fname].add(other)
2650+ if line.strip() == '}':
2651+ in_func = False
2652+
2653+ V = len(funcs)
2654+ E = len(set(edges))
2655+
2656+ # BFS from run_common_phases
2657+ dist = {'run_common_phases': 0}
2658+ queue = ['run_common_phases']
2659+ while queue:
2660+ node = queue.pop(0)
2661+ for nb in adj.get(node, set()):
2662+ if nb not in dist:
2663+ dist[nb] = dist[node] + 1
2664+ queue.append(nb)
2665+
2666+ reachable = set(dist.keys())
2667+ unreachable = funcs - reachable
2668+
2669+ # 위상 불변량
2670+ components = 0
2671+ visited = set()
2672+ for node in funcs:
2673+ if node not in visited:
2674+ components += 1
2675+ q = [node]
2676+ while q:
2677+ n = q.pop(0)
2678+ if n in visited: continue
2679+ visited.add(n)
2680+ for nb in adj.get(n, set()):
2681+ q.append(nb)
2682+ for f, t in edges:
2683+ if t == n: q.append(f)
2684+
2685+ beta1 = E - V + components
2686+ topo_fp = hashlib.md5(json.dumps(sorted(set(edges))).encode()).hexdigest()[:12]
2687+
2688+ # 이전 상태 비교
2689+ prev_file = os.path.join(n6_dir, 'engine_topology.json')
2690+ prev = {}
2691+ if os.path.exists(prev_file):
2692+ try: prev = json.load(open(prev_file))
2693+ except: pass
2694+
2695+ prev_fp = prev.get('topology_fingerprint', '')
2696+ changed = topo_fp != prev_fp
2697+
2698+ # 리포트
2699+ print(f' V={V} E={E} comp={components} beta1={beta1} fp={topo_fp}')
2700+ print(f' Reachable: {len(reachable)}/{V}, Unreachable: {len(unreachable)}')
2701+
2702+ if unreachable:
2703+ print(f' Disconnected: {\" \" .join(sorted(unreachable)[:6])}')
2704+
2705+ if changed:
2706+ print(f' TOPOLOGY CHANGED: {prev_fp}→{topo_fp}')
2707+ else:
2708+ print(f' Topology stable')
2709+
2710+ # 대발견: n=6 매칭 체크
2711+ discoveries = []
2712+ if V % 6 == 0:
2713+ discoveries.append(f'V={V}={V//6}*n')
2714+ if E % 6 == 0:
2715+ discoveries.append(f'E={E}={E//6}*n')
2716+ if beta1 == 6:
2717+ discoveries.append(f'beta1={beta1}=n EXACT!')
2718+
2719+ if discoveries:
2720+ print(f' N6 DISCOVERY: {\" | \" .join(discoveries)}')
2721+ # bus 기록
2722+ with open(bus_file, 'a') as bf:
2723+ bf.write(json.dumps({
2724+ 'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ'),
2725+ 'repo': '$repo ',
2726+ 'type': 'topology_discovery',
2727+ 'detail': '; '.join(discoveries)
2728+ }) + '\n')
2729+
2730+ # 저장
2731+ topo = {
2732+ 'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ'),
2733+ 'V': V, 'E': E, 'components': components,
2734+ 'beta1': beta1, 'topology_fingerprint': topo_fp,
2735+ 'reachable': len(reachable), 'unreachable': sorted(unreachable),
2736+ 'changed': changed, 'discoveries': discoveries,
2737+ }
2738+ json.dump(topo, open(prev_file, 'w'), indent=2, ensure_ascii=False)
2739+ " 2> /dev/null || echo " Topology: error"
2740+ }
2741+
25922742log_info " growth_common.sh loaded (n=$N6_N , σ=$N6_SIGMA , J₂=$N6_J2 )"
0 commit comments