A code-to-column change-impact knowledge graph for AI coding agents.
| English | 한국어 |
이 엔진을 붙들고 있는 생각은 일곱 개입니다. 하나하나가 태도가 아니라 코드 안의 기제이므로, 그것을 구현한 파일 이름을 옆에 적어 둡니다.
그래프의 모든 엣지는 다섯 등급 중 하나를 답니다. 이것은 격자이고, 요점은 그 격자를 거슬러 올라가는 일이 없다는 것입니다.
| 등급 | 뜻 | 예 |
|---|---|---|
EXACT |
구문과 심벌과 상수로 유일하게 증명됨 | 핸들러가 매퍼 메서드를 직접 호출하는 경우 |
SOUND_SET |
진짜 대상이 반드시 그 안에 있는 보수적 후보 집합 | 인터페이스 디스패치 뒤의 구현체들 |
HEURISTIC |
프로젝트 관행으로 보아 그럴듯함. 복구된 부분 바인딩 | 네이밍 전략 선언 없이 필드 이름에서 유도한 컬럼 이름 |
RUNTIME_ONLY |
정적으로 결정 불가. 런타임 증거가 필요함 | 외부 설정에서 고르는 빈이나 URL |
UNRESOLVED |
분석이 실패했거나 지원하지 않는 형태 | 파싱 실패, 없는 의존성 |
원소가 하나인 후보 집합도 절대 EXACT 로 승격되지 않습니다. 좁히는 것은
증명하는 것이 아닙니다. 격자는 src/core/policy.mjs 딱 한 곳에서 계산되고,
워커들은 어떤 종류의 호출인지, 바인딩 상태가 어떤지 같은 증거만 내보낼 뿐
등급을 내보내지 않습니다. 속성 테스트가 어떤 증거 조합도 격자 위로 분류되지
않음을 확인하고, 두 번째 테스트가 전체성을 확인합니다. 표가 모르는 증거 모양은
침묵이 아니라 POLICY_GAP 진단과 함께 보수적인 HEURISTIC 으로 떨어집니다.
걷기의 등급은 가장 약한 고리를 따릅니다. SOUND_SET 호출을 하나라도 지나는
체인은, 나머지가 아무리 exact 여도 SOUND_SET 입니다.
질의 모드가 하한을 고릅니다. strict 는 확인된 엣지만 쓰고, conservative 는
후보 호출을 더하고, heuristic 은 추측 규칙까지 허용합니다. conservative 에서
아무것도 나오지 않다가 heuristic 에서 무언가 나오는 질문은, 그 코드에 대해
진짜 정보를 하나 알려 준 것입니다.
유효한 응답이 만들어지는 곳은 src/mcp/contract.mjs 하나뿐입니다. 필드에는
비공개 Symbol 로 도장이 찍히므로, 모양만 맞는 객체를 손으로 만들어 넘기면
직렬화 전에 assertContract() 가 거부합니다. 계약을 어긴 서버 응답은 200 이
아니라 500 contract-violation 입니다.
basis 는 답을 매어 두는 닻입니다. 프로젝트, pack 다이제스트, 만들어진
시각, 그리고 current, behind, provisional-overlay, unknown 중 하나의
최신성 판정입니다. unknown 은 진짜 답이며 절대 current 로 읽히지 않습니다.
pack 다이제스트가 없는 basis 는 아예 거부됩니다. 매어 둘 것이 없는 답은 답이
아니기 때문입니다.
trust 는 계산된 수준이며 리터럴이 아닙니다. 이름은 셋
(UNCERTIFIED, GOLDEN_FAIL, GOLDEN_PASS)뿐이고, 그 문자열을 적을 수 있는
파일은 src/core/trust.mjs 하나입니다. 테스트가 트리를 뒤져 그 문자열이 다른
데서 다시 나오면 빌드를 깨뜨립니다. 계산은 순서대로 비관적입니다. 보정 상태가
아예 없으면 UNCERTIFIED, 게이트가 GREEN 이나 BOOTSTRAP 이 아니면
UNCERTIFIED, 승인된 골든 사례가 30 개 미만이면 UNCERTIFIED 입니다. 이 모듈이
직접 밝히는 함의도 함께 읽어야 합니다. 흠 없는 30 개 사례의 Wilson 하한은
0.8865 이므로 N=30 에서는 어떤 95% 목표도 보일 수 없습니다. 30 개는 코퍼스에
점수를 매기기 시작할 수 있는 지점이지 충분해지는 지점이 아닙니다.
trust.knownGaps 는 축 선언(column-axis-degraded, code-axis-not-shipped
같은 것)도 함께 실어 나릅니다.
limits 는 엔진이 보지 못한 것을 문장으로, 각각 범위를 붙여 적은
것입니다. 물린 깊이 제한, 추측하기를 거부한 ${} 치환, 아예 만들어지지 않은
축 같은 것입니다.
truncated 는 목록마다 몇 개를 보여 주었고 전체가 몇 개이며, 어떤 순서로,
어디서부터 이어 받을지를 말합니다. 잘린 목록이 스스로를 완전하다고 말하는
사고를 막으려고 있는 필드입니다.
빈 것에도 뜻이 있습니다. not-shipped(축이 아예 만들어지지 않음),
degraded(만들어졌지만 필요한 것 없이), none(찾아봤고 없었음). 이 셋은 서로
다른 답이며, 0 results 가 “안전하다”로 읽히는 일은 없습니다.
| 인증된 베이스 | 작업 트리 오버레이 | |
|---|---|---|
| 무엇을 설명하는가 | 특정 커밋의 blob | 지금 디스크에 있는 바이트 |
| 결정성 | 바이트 단위로 재현 가능 | 명시적으로 아님. 모든 다이제스트 바깥 |
| 비용 | 초에서 분 | statement 900 개 프로젝트에서 약 0.2 초 |
| 기록되는가 | 예, pack 으로 | 아무것도. pack 도 팩트 캐시도 아님 |
| 어떻게 읽히는가 | current 또는 behind |
provisional-overlay |
베이스는 pack 입니다. 같은 커밋, 같은 카탈로그 스냅샷, 같은 엔진과 프로파일이면
어느 기계에서든 같은 다이제스트가 나옵니다. 빌드 시각, 호스트명, 절대 경로는
meta 에 실리고 다이제스트 바깥에 있습니다.
오버레이는 호출할 때마다 더러운 파일을 다시 파싱해서 나머지의 캐시된 샤드
위에 얹습니다(src/core/overlay.mjs). 베이스가 가진 적 없는 노드에는
provisional 표식이 붙습니다. 등급이 아니라 등급 옆의 표식입니다. 오버레이는
과대 근사할 수는 있어도 빠뜨릴 수는 없으며, 그것은 약속이 아니라
테스트입니다. test/overlay_integration.test.mjs 가 같은 바이트를 전체
재분석한 결과와 비교합니다. 편집을 커밋하면 오버레이는 폐기되고 답은
behind 가 되며 cascade analyze 를 처방으로 알려 줍니다. 편집 이전의 답으로
조용히 되돌아가는 일은 절대 없습니다.
두 번째 속도는 재실행에도 해당합니다. 작은 편집 뒤의 실행은 바뀌지 않은 것의
내용 주소 샤드를 재사용하고, 그 결과는 같은 상태의 cold 실행과 바이트 단위로
같아야 합니다(test/incremental.test.mjs 가 매 라운드 시드 고정 PRNG 로 파일의
무작위 부분집합을 변형합니다). 이 경로가 조용히 넘어가기를 거부하는 것이 넷
있습니다. 모른다는 것은 절대 “바뀐 것 없음”이 아니고, 손상된 샤드는 절대
적재되지 않으며(SHARD_UNUSABLE 로 보고하고 그 단위만 다시 계산합니다), 더러운
트리는 meta.base.dirty 에 명시되고, pack 은 자신이 어떤 모드로 무엇을 다시
읽고 무엇을 재사용했는지 기록합니다.
모든 레인 입력은 선택 사항이고 모든 레인은 이름으로 끌 수 있습니다
(--no-ddl, --no-mappers, --no-java, --no-web, --no-openapi). 그래도
실행은 유효한 pack 을 만들고, pack 은 축별(catalog statements column
code jpa web screen)로 shipped, degraded, not-shipped 중 하나를
이유와 함께 기록합니다.
그 선언은 모든 답에 실립니다. 축 이름은 trust.knownGaps 로, 이유는 limits
로 가고, 빈 목록은 none 이 아니라 not-shipped 라고 말합니다.
답을 깔끔해 보이게 하려고 버리는 것은 없습니다. --no-ddl 로 분석한 프로젝트는
column: degraded 를 선언하고, 그 뒤의 숫자는 실제 숫자입니다. 카탈로그 없이는
귀속할 수 없는 것을 버리지 않고 unresolved 로 기록합니다.
screen 축은 그 왕복의 먼 쪽 끝입니다. 라우터 자신의 선언을 사용자가 실제로
있는 경로로 합성하고, 각각을 그 라우트가 마운트하는 컴포넌트의 프런트엔드
함수들에 RENDERS 엣지로 잇습니다. RENDERS 는 라우트가 선언한 파일로 갈
때 EXACT 이고, 그 컴포넌트가 import 하는 파일로 갈 때 SOUND_SET 입니다.
import 된 컴포넌트의 어느 함수가 실제로 도는지는 런타임 질문이기 때문입니다.
degraded 는 입력이 빠진 경우에만 붙는 것이 아닙니다. web 축은 프런트엔드에
대해 아무것도 추측하지 않았을 때만 shipped 입니다. 엔진이 매칭 개수를 세어
알아낸 URL 접두사나, 프로젝트가 선언하지 않아서 가정한 경로 별칭이 있으면 축은
degraded 가 되고, 이유가 무엇을 선언하면 되는지 알려 줍니다.
축이 보기보다 일찍 끝난다는 뜻일 수도 있습니다. 라우트가 소스가 아니라
OpenAPI 문서에서 온 pack 은 엔드포인트는 있는데 그 아래에는 아무것도 없습니다.
그래서 code 축이 degraded 가 되고 이유는 “endpoints come from an OpenAPI
document, not from source: the routes exist, but nothing below them is walked,
so a frontend call reaches an endpoint and stops there” 이며, 라우트 아래 체인이
필요한 질문은 “찾아봤다”로 읽힐 빈 목록 대신 not-shipped 로 답합니다.
브라우저 기록(--har)은 종류가 다른 사실이고 그렇게 등급이 매겨집니다.
RUNTIME_ONLY 는 모든 모드의 하한 아래이므로 기록된 screen → route 호출은
보여 주기만 하고(observed: true) 절대 걷지 않으며, 옆의 정적 엣지의
등급을 올리지도 않습니다. 기록은 요청이 한 번 일어났음을 증명할 뿐, 코드가 무엇을
할 수 있는지는 전혀 증명하지 않습니다.
cascade estimate 는 분석 전에 같은 질문에 답합니다. 이 트리가 무엇을
shipped, degraded, not-shipped 로 내놓을지입니다.
프로젝트마다 .cascade/calibration/ 에 봉인된 기준선을 둡니다
(src/core/calibration.mjs). 모든 analyze 를 그것과 비교하고, 게이트는 먼저 이
실행이 왜 다른지를 묻습니다.
| 모드 | 언제 |
|---|---|
NO_SEAL |
기준선이 아직 없음. 이번 실행이 기준선이 됩니다 (BOOTSTRAP) |
NO_CHANGE |
같은 엔진, 같은 커밋 핀 |
ENGINE_MOVED |
엔진 지문이 움직임. 업그레이드 |
REPIN |
분석 대상 커밋이 움직임 |
BOTH_MOVED |
둘 다 |
ENGINE_MOVED 가 가장 엄격합니다. “분석기가 바뀌었다”는 바로 손실이 스며들면
안 되는 순간이므로, 하락을 회귀로 보고 막습니다. 게이트는 절대값이 아니라
비교 기준이며 개선도 함께 보고합니다. 분자가 올라간 비율은 답이 작아진
것이 아니라 레인이 넓어진 것이고, 발견 문구가 바로 그 말로 그렇게 적습니다.
RED 실행은 조용히 버려지지 않습니다. 그 pack 은 <packDir>-rejected/ 로 가고,
인증된 pack 은 있던 자리에 그대로 남고, 명령은 종료 코드 3 으로 끝납니다. 유일한
우회는 --accept-baseline 이며, 기준선을 그 실행에서 다시 봉인합니다.
설계상 사람의 결정입니다.
그 옆에서 cascade golden 이 프로젝트 자신의 라벨링된 코퍼스를 관리합니다.
도구는 사례를 제안하고 사람이 승인하며(--ids … 또는 명시적 --all),
해시가 어느 것을 홀드아웃으로 뺄지 정하고, check 가 승인된 사례를 실제
배포되는 MCP 도구를 통해 채점합니다. 도구가 스스로를 승인하는 일은 없으며,
그래서 위의 신뢰 수준이 무언가를 뜻할 수 있습니다.
UNRESOLVED 로 내려갑니다. 절대 위로
반올림하지 않습니다.${} 치환, 해석 불가능한 include)은 추측하지도
버리지도 않고 진단으로 기록합니다.DDL 의 create table item (…) 과 매퍼의 FROM ITEM I 는 HSQLDB 에서도, Oracle
에서도, lower_case_table_names=1 인 MySQL 서버에서도 같은 테이블입니다.
문자열을 정확히 비교하면 그래프에 테이블이 둘 생기고 사실이 반으로 갈립니다.
언제나 접어서 비교하면, 진짜로 둘을 구분하는 데이터베이스에서 서로 다른 테이블
둘을 합쳐 버립니다. 그래서 규칙은 둘 다 아닙니다. 각 데이터베이스의 매뉴얼이
말하는 대로 방언별로 선언하며, 작은 표 하나로 되어 있습니다
(src/core/identifier_case.mjs, 워커용 사본은
adapters/sql/identifier_case.py, 둘 다 돌려 보는 테스트가 교차 검증합니다).
fold-lower, Oracle 과 HSQLDB 와 H2 는
fold-upper, 이 엔진이 모르는 방언은 아무것도 접지 않는 exact 입니다."Item", `Item`)는 어디서나 정확히 비교하므로,
카탈로그가 선언한 철자와만 대조합니다.sqlIdentifierCase(fold-lower | fold-upper | exact |
null, 마지막은 방언 자신의 규칙)가 이 표를 덮어쓰며, 모든 실행이 어떤 규칙을
어디서 가져왔는지 출력합니다.접기는 비교용 키를 만들 뿐 새 이름을 만들지 않습니다. 테이블과 컬럼은
카탈로그가 준 철자를 그대로 유지하므로 pms_product 는 ERD 에서도 모든 답에서도
pms_product 입니다. 그래서 접는 방향은 사실을 바꾸지 못하고 무엇이 매칭되는지만
바꿉니다. 하나의 키로 접히는 카탈로그 이름 둘은
folded_identifier_collision 으로 보고되고 먼저 선언된 쪽이 키를 가지며, 두
테이블 모두 선언된 그대로 pack 에 남습니다. 접어서 비교해도 카탈로그에 없는
테이블은 여전히 unresolved 입니다. 접기는 비교를 느슨하게 만드는 것이 아니라
올바르게 만드는 것입니다.
도구 인자도 같은 방식으로 접힙니다. pack 은 자신이 어떤 규칙으로
만들어졌는지 기록하므로(pack.meta.identifierCase), 여러분이 자기 SQL 에서
읽어 온 철자 그대로 table_usage ORDERS 라고 물어도 카탈로그의 orders 에 대해
답하고, 답에는 그렇게 했다는 limits 한 줄이 붙습니다. column_impact,
endpoint_impact, flow, neighborhood, erd 의 focus 테이블도 같습니다.
아무것과도 맞지 않는 이름은 unknown-table 이나 unknown-column 코드를
유지하면서 가장 가까운 후보를 알려 주고, 선언된 이름 둘로 접히는 이름은
모호하므로 어느 쪽으로도 해석하지 않습니다.