@tienne/gestalt 0.76.0 → 0.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CLAUDE.md +4 -3
  2. package/dist/package.json +1 -1
  3. package/dist/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +2 -2
  4. package/dist/plugin/role-agents/code-review-responder/AGENT.md +1 -1
  5. package/dist/plugin/role-agents/code-review-writer/AGENT.md +3 -2
  6. package/dist/plugin/role-agents/explainer/AGENT.md +1 -1
  7. package/dist/plugin/role-agents/explainer/references/audience.md +1 -1
  8. package/dist/plugin/skills/blast-radius/SKILL.md +58 -9
  9. package/dist/plugin/skills/build-graph/SKILL.md +28 -3
  10. package/dist/plugin/skills/diff-radius/SKILL.md +44 -6
  11. package/dist/plugin/skills/review/SKILL.md +30 -20
  12. package/dist/plugin/skills/review-loop/SKILL.md +3 -3
  13. package/dist/plugin/skills/ship/SKILL.md +1 -1
  14. package/dist/src/code-graph/blast-radius.d.ts +3 -3
  15. package/dist/src/code-graph/blast-radius.d.ts.map +1 -1
  16. package/dist/src/code-graph/blast-radius.js +111 -6
  17. package/dist/src/code-graph/blast-radius.js.map +1 -1
  18. package/dist/src/code-graph/cochange.d.ts +117 -0
  19. package/dist/src/code-graph/cochange.d.ts.map +1 -0
  20. package/dist/src/code-graph/cochange.js +444 -0
  21. package/dist/src/code-graph/cochange.js.map +1 -0
  22. package/dist/src/code-graph/engine.d.ts +13 -1
  23. package/dist/src/code-graph/engine.d.ts.map +1 -1
  24. package/dist/src/code-graph/engine.js +58 -4
  25. package/dist/src/code-graph/engine.js.map +1 -1
  26. package/dist/src/code-graph/index.d.ts +5 -2
  27. package/dist/src/code-graph/index.d.ts.map +1 -1
  28. package/dist/src/code-graph/index.js +2 -1
  29. package/dist/src/code-graph/index.js.map +1 -1
  30. package/dist/src/code-graph/storage.d.ts +93 -1
  31. package/dist/src/code-graph/storage.d.ts.map +1 -1
  32. package/dist/src/code-graph/storage.js +213 -0
  33. package/dist/src/code-graph/storage.js.map +1 -1
  34. package/dist/src/code-graph/types.d.ts +141 -0
  35. package/dist/src/code-graph/types.d.ts.map +1 -1
  36. package/dist/src/mcp/schemas.d.ts +12 -3
  37. package/dist/src/mcp/schemas.d.ts.map +1 -1
  38. package/dist/src/mcp/schemas.js +26 -3
  39. package/dist/src/mcp/schemas.js.map +1 -1
  40. package/dist/src/mcp/server.d.ts.map +1 -1
  41. package/dist/src/mcp/server.js +10 -4
  42. package/dist/src/mcp/server.js.map +1 -1
  43. package/dist/src/mcp/tools/code-graph-passthrough.d.ts +4 -1
  44. package/dist/src/mcp/tools/code-graph-passthrough.d.ts.map +1 -1
  45. package/dist/src/mcp/tools/code-graph-passthrough.js +23 -0
  46. package/dist/src/mcp/tools/code-graph-passthrough.js.map +1 -1
  47. package/dist/src/review-loop/fetch.d.ts +9 -4
  48. package/dist/src/review-loop/fetch.d.ts.map +1 -1
  49. package/dist/src/review-loop/fetch.js +13 -5
  50. package/dist/src/review-loop/fetch.js.map +1 -1
  51. package/dist/src/review-loop/state.js +1 -1
  52. package/package.json +1 -1
  53. package/plugin/.codex-plugin/plugin.json +1 -1
  54. package/plugin/.mcp.json +1 -1
  55. package/plugin/mcp.json +1 -1
  56. package/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +2 -2
  57. package/plugin/role-agents/code-review-responder/AGENT.md +1 -1
  58. package/plugin/role-agents/code-review-writer/AGENT.md +3 -2
  59. package/plugin/role-agents/explainer/AGENT.md +1 -1
  60. package/plugin/role-agents/explainer/references/audience.md +1 -1
  61. package/plugin/skills/blast-radius/SKILL.md +58 -9
  62. package/plugin/skills/build-graph/SKILL.md +28 -3
  63. package/plugin/skills/diff-radius/SKILL.md +44 -6
  64. package/plugin/skills/review/SKILL.md +30 -20
  65. package/plugin/skills/review-loop/SKILL.md +3 -3
  66. package/plugin/skills/ship/SKILL.md +1 -1
package/CLAUDE.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - **Review Pipeline**: Code Review 6종 에이전트(보안/성능/품질/프론트엔드/주석/라이팅) + consensus → 자동 수정 루프
13
13
  - **MCP Server**: stdio transport, API 키 없으면 Passthrough 모드 자동 활성화 (Execute는 항상 Passthrough)
14
14
  - **Skill System**: SKILL.md 기반 확장, chokidar hot-reload
15
- - **Code Knowledge Graph**: 정적 분석 → 의존성 그래프 → Blast-Radius 영향 파일 추출, D3 시각화(`ges_graph_visualize`) 지원
15
+ - **Code Knowledge Graph**: 정적 분석 → 의존성 그래프 → Blast-Radius 영향 파일 추출, D3 시각화(`ges_graph_visualize`) 지원. git 이력에서 뽑은 co-change(함께 바뀐 파일)를 나란히 실어 import가 원리상 못 보는 관계까지 잡는다
16
16
  - **Knowledge Base**: 코드 그래프·도메인 지식을 MD로 내보내고 로컬 임베딩으로 시맨틱 검색
17
17
  - **Memory**: 이전 스펙·실행 이력을 `.gestalt/memory.json`에 축적, 신규 인터뷰에 자동 주입
18
18
  - **Multi-Provider LLM**: frugal/standard/frontier 티어별로 Anthropic/OpenAI 호환 프로바이더 자유 조합
@@ -49,7 +49,7 @@ pnpm tsx bin/gestalt.ts explain-eval --a plugin/role-agents/explainer/AGENT.md
49
49
  - `ges_agent`: action=[list|get], name?
50
50
  - `ges_status`: sessionId?, sessionType?, cwd?
51
51
  - `ges_benchmark`: action=[start|respond|status], scenario?, benchmarkSessionId?, response?
52
- - `ges_code_graph`: action=[build|blast_radius|diff_radius|query|stats|db_exists]
52
+ - `ges_code_graph`: action=[build|blast_radius|diff_radius|query|co_change|stats|db_exists]
53
53
  - `ges_graph_visualize`: repoRoot, port?
54
54
  - `ges_generate_kb`: repoRoot?, outputPath?, types?, summarize?
55
55
  - `ges_search`: query, k?, kbPath?, types?
@@ -73,7 +73,7 @@ src/interview/ — InterviewEngine, ResolutionScorer
73
73
  src/spec/ — SpecGenerator, SpecExtractor
74
74
  src/execute/ — ExecuteEngine, DAG Validator
75
75
  src/resilience/ — Stagnation Detector, Lateral Thinking Personas
76
- src/code-graph/ — CodeGraphEngine, BlastRadius, 언어 플러그인 8개
76
+ src/code-graph/ — CodeGraphEngine, BlastRadius, git 이력 co-change, 언어 플러그인 8개
77
77
  src/graph-viz/ — 코드 그래프 D3 시각화 (ges_graph_visualize 백엔드)
78
78
  src/local-pr/ — 로컬 PR 도메인 (이벤트 소싱, git 연산, gestalt pr·ges_pr 백엔드)
79
79
  src/local-pr-web/ — 로컬 PR 읽기 전용 웹 UI (gestalt pr serve 백엔드)
@@ -163,6 +163,7 @@ plugin/.mcp.json Grok(배포) — plugin/mcp.json과 동일
163
163
  - LLM 호출: temperature 0.3, JSON 응답 파싱 + fallback
164
164
  - 해상도 점수 ≥ 0.8 = 요구사항 충분히 명확
165
165
  - 테스트 DB: `.gestalt-test/xxx-${randomUUID()}.db` 고유 경로 (병렬 안전)
166
+ - blast-radius 결과에서 테스트 러너 인자로 넘기는 건 `impactedFiles`다. `rankedFiles`에는 git 이력으로만 걸린 md나 json이 섞여 있어 그대로 넘기면 vitest 인자가 오염된다
166
167
  - 한글 산문에서 가운뎃점(·) 나열 절제 → 쉼표나 "A랑 B하고 C"로 (표·용어 목록은 예외). 룰은 `ai-tell-quick-rules.md` C-12, `style-guide.md`에 정의
167
168
 
168
169
  ## 커밋 메시지, PR 제목
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tienne/gestalt",
3
- "version": "0.76.0",
3
+ "version": "0.77.0",
4
4
  "description": "TypeScript AI Development Harness - Gestalt psychology-driven requirement clarification",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,7 @@
20
20
 
21
21
  **한자어를 피하는 게 목적이 아니다 (F-4·F-5 오적용 주의):** 겨냥하는 건 개념을 뭉친 **명사화**(-성/-적/-화)이지 한자어 동사가 아니다. "측정하다"를 "재다"로, "판단하다"를 "따지다"로 바꾸면 정확도만 잃는다. 그 자리의 정확한 말이 한자어면 한자어를 쓴다. 반대 방향 실수도 같다 — 한자어를 피하려고 비유 명사(축·증류)나 물리 동사(재다)로 도망가면 F-7에 걸린다. 셋 다 겪은 실례: `네 축을 따로 재고 판정한다` → `4가지 측면에서 각각 측정하고 판단한다`.
22
22
 
23
- **은유를 다 지우라는 게 아니다 (F-9 오적용 주의):** 겨냥하는 건 결함이나 코드 상태, 그 결함의 원인 관계를 뭉개 부른 말이다. 그 자리에서 지어낸 별칭이든 이미 있는 비유든 같다. "널 포인터"를 "널 값을 가리키는 포인터"로, "데드락"을 "서로 기다리다 멈춘 상태"로 풀어 쓰면 길이만 늘고 무엇을 말하는지는 오히려 흐려진다. **가르는 기준은 그 자리에서 무엇을 가리키는지가 말에 남아 있느냐 하나뿐이다.** 남이 정의해 둔 이름을 그 뜻 그대로 쓰면 그 기준을 통과한다. 그게 대표 사례다. 자료구조가 그 이름으로 부르는 자리도 같다 — 트리와 스레드의 root를 "뿌리"라 부르면 구조를 가리키므로 그대로 둔다. **사전에 비유 뜻풀이가 실렸다는 것만으로는 정의해 둔 이름이 아니다** — "뿌리가 하나다"는 사전에 근본 뜻이 실려도 무엇이 같은지가 안 남는다. 명세나 교과서에 뜻이 박혀 있으면 가리킬 데가 있으니 그대로 둔다. 팀에서 몇 번 쓰였다는 것으로는 예외가 되지 않는다 — 아래 [측정 근거](#측정-근거) 문단과 B-4가 "팀에서 굳은 말"을 지키는 것과는 다른 자리다. B-4는 팀이 쓰기 시작하면 예외로 인정하는데 F-9는 그걸 예외로 안 본다. B-4가 막는 건 어색함이라 팀이 받아쓰면 풀리고 F-9가 막는 건 굳는 것 자체라 팀이 받아쓰면 그게 피해다.
23
+ **은유를 다 지우라는 게 아니다 (F-9 오적용 주의):** 겨냥하는 건 결함이나 코드 상태, 그 결함의 원인 관계를 뭉개 부른 말이다. 그 자리에서 지어낸 별칭이든 이미 있는 비유든 같다. "널 포인터"를 "널 값을 가리키는 포인터"로, "데드락"을 "서로 기다리다 멈춘 상태"로 풀어 쓰면 길이만 늘고 무엇을 말하는지는 오히려 흐려진다. **가르는 기준은 그 자리에서 무엇을 가리키는지가 말에 남아 있느냐 하나뿐이다.** 남이 정의해 둔 이름을 그 뜻 그대로 쓰면 그 기준을 통과한다. 그게 대표 사례다. **"뿌리"는 root 한 단어가 그 대상 이름인 자리에서만 남긴다** — 이를테면 트리와 스레드의 root, 디렉토리와 프로젝트와 git ref의 root다. **root cause처럼 root가 다른 명사를 수식하는 자리는 대상이다** — 원인이면 "원인", 출처면 "출처"로 쓴다. **이 기준이 아래 일반 예외보다 앞선다** — root cause analysis가 문헌에 뜻이 박힌 말이어도 "뿌리"로 옮기면 걸린다. **사전에 비유 뜻풀이가 실렸다는 것만으로는 정의해 둔 이름이 아니다** — "뿌리가 하나다"는 사전에 근본 뜻이 실려도 무엇이 같은지가 안 남는다. 명세나 교과서에 뜻이 박혀 있으면 가리킬 데가 있으니 그대로 둔다. 팀에서 몇 번 쓰였다는 것으로는 예외가 되지 않는다 — 아래 [측정 근거](#측정-근거) 문단과 B-4가 "팀에서 굳은 말"을 지키는 것과는 다른 자리다. B-4는 팀이 쓰기 시작하면 예외로 인정하는데 F-9는 그걸 예외로 안 본다. B-4가 막는 건 어색함이라 팀이 받아쓰면 풀리고 F-9가 막는 건 굳는 것 자체라 팀이 받아쓰면 그게 피해다.
24
24
 
25
25
  **근거가 붙은 룰 (→ 아래 [측정 근거](#측정-근거)):** A-2, I-1, C-8은 대조 코퍼스 측정으로 심각도를 조정했고 B-4는 팀이 실제로 쓰는 말이라 예시에서 뺐다. 원어민이 오히려 더 쓰는 표현이나 팀에서 이미 굳은 말을 지우면 사람 글을 AI 글로 만든다.
26
26
 
@@ -143,7 +143,7 @@
143
143
  | F-6 | 복합명사 압축 — 명사구를 조사·동사 없이 이어붙여 개념을 뭉침 ("시안 정합 버그픽스", "유지보수성 개선 작업", "권한 체크 로직") | S2 / **대화·리뷰 S1** | 동사·조사로 풀어 서술 ("디자인 시안과 다르게 렌더링되던 문제", "나중에 유지보수하기 편하게"). 사람은 압축 명사구 대신 무엇을 왜 했는지 풀어 말한다 |
144
144
  | F-7 | 기술·이공계 비유 명사를 일상 대화에 그대로 (증류·배선·결정화·평탄화·오케스트레이션·파이프라인화·축 등, 화학·전기·수학 어휘의 비유 차용) | S2 / **대화·리뷰 S1** | 일상 동사·명사로 환원 (증류 → "추려내다/뽑아내다", 배선 → "연결하다/걸어두다", 결정화 → "정리하다", 평탄화 → "밋밋하게 만들다", **축 → "측면"**·"기준"). 문법은 멀쩡해 룰 매칭이 안 되지만 사람은 대화에서 안 쓴다. "네 축을 잰다"가 아니라 "4가지 측면에서 측정한다". **예외는 그 프로젝트가 개념에 붙인 이름일 때만이고, 그 개념을 다루는 자리에서만이다** — 게슈탈트의 "스펙 결정화"는 `similarity-crystallizer` 에이전트 이름이자 인터뷰→Spec 변환의 정의라서 두지만, 같은 말을 지라 티켓 작성처럼 무관한 자리에 끌어다 쓰면 F-7이다. 코드의 "파이프라인" 자체, 그래프의 x축도 같은 기준. 테스트를 자물쇠에 빗댄 "잠그다"는 방향이 반대라 F-10이 본다 |
145
145
  | F-8 | 추상명사에 물리 측량 동사("근거를 재봤는데", "판단을 재보고 있다", "근거를 재본 결과", "의견을 달아봤는데") | S2 / **대화·리뷰 S1** | 저울에 올린 게 아니라 검토한 것이다. 사람이 하는 행위 동사로 되돌린다("근거를 확인해봤는데", "의견을 검토해봤는데"). F-7 물리 동사 도피의 사촌이라 목적어가 근거, 의견, 판단, 영향, 의미, 가치, 리스크인 자리만 본다 — "온도를 재봤는데"는 대상이 아니다 |
146
- | F-9 | 결함이나 코드 상태, 그 결함의 원인 관계를 비유 명사로 뭉개 부름 ("맨몸 호출", "벌거벗은 API", "뿌리가 하나다") | S2 / **대화·리뷰 S1** | 무엇이 빠졌고 무엇이 틀렸는지 그대로 쓴다 ("맨몸 호출" → "접두 객체 없이 함수 이름만 적혀 있다"). 별칭은 검색이 안 되고 처음 읽는 사람이 못 알아보고 답글에서 재생산돼 팀 어휘로 굳는다. **예외는 남이 정의해 둔 이름을 그 뜻 그대로 쓸 때다** — "널 포인터", "데드락", "고아 노드"처럼 명세나 교과서에 뜻이 박힌 말이고 F-6으로도 안 넘긴다. 다른 결함에 비유로 얹으면 예외가 아니고 팀에서 몇 번 쓰였다는 것도 아니다 — 그렇게 열면 답글에 한 번 재생산된 별칭이 곧바로 빠져나간다. **F-7과 방향이 반대다** — F-7은 기술 비유를 일상 말로 되돌리고 F-9는 일상 비유를 기술 대상에 씌운 쪽을 본다. **원인 관계를 부르는 비유도 같은 자리다** — "뿌리가 하나다"는 원인이 같다는 말을 뿌리에 얹은 것이라 무엇이 같은지가 안 읽힌다. "원인이 같다"로 쓴다. 지어낸 별칭이든 이미 있는 비유든 가르는 건 하나다 — 그 자리에서 무엇을 가리키는지가 말에 남아 있느냐다. **자료구조나 명세가 그 이름으로 부르는 자리는 대상이 아니다** — 트리의 뿌리(root)와 스레드의 뿌리는 그 구조를 부르는 이름이라 가리킬 데가 있다. F-9에 걸린 별칭이 스스로 움직이면 D-5, 조사 없이 뭉치면 F-6도 함께 걸린다. 이 경계는 뜻이라 탐지기가 없다 |
146
+ | F-9 | 결함이나 코드 상태, 그 결함의 원인 관계를 비유 명사로 뭉개 부름 ("맨몸 호출", "벌거벗은 API", "뿌리가 하나다") | S2 / **대화·리뷰 S1** | 무엇이 빠졌고 무엇이 틀렸는지 그대로 쓴다 ("맨몸 호출" → "접두 객체 없이 함수 이름만 적혀 있다"). 별칭은 검색이 안 되고 처음 읽는 사람이 못 알아보고 답글에서 재생산돼 팀 어휘로 굳는다. **예외는 남이 정의해 둔 이름을 그 뜻 그대로 쓸 때다** — "널 포인터", "데드락", "고아 노드"처럼 명세나 교과서에 뜻이 박힌 말이고 F-6으로도 안 넘긴다. 다른 결함에 비유로 얹으면 예외가 아니고 팀에서 몇 번 쓰였다는 것도 아니다 — 그렇게 열면 답글에 한 번 재생산된 별칭이 곧바로 빠져나간다. **F-7과 방향이 반대다** — F-7은 기술 비유를 일상 말로 되돌리고 F-9는 일상 비유를 기술 대상에 씌운 쪽을 본다. **원인 관계를 부르는 비유도 같은 자리다** — "뿌리가 하나다"는 원인이 같다는 말을 뿌리에 얹은 것이라 무엇이 같은지가 안 읽힌다. "원인이 같다"로 쓴다. 지어낸 별칭이든 이미 있는 비유든 가르는 건 하나다 — 그 자리에서 무엇을 가리키는지가 말에 남아 있느냐다. **"뿌리"는 root 한 단어가 그 대상 이름인 자리에서만 쓴다** — root cause처럼 수식하는 자리는 대상이다. 기준과 예시는 위 `F-9 오적용 주의` 문단에 있고 상세는 그 한 자리에만 둔다. F-9에 걸린 별칭이 스스로 움직이면 D-5, 조사 없이 뭉치면 F-6도 함께 걸린다. **별칭과 원인 쪽 경계는 뜻이라 탐지기가 없다.** "뿌리"는 말 하나라 F-10의 "못 박" 탐지기처럼 형태로 걸 수 있지만 안 붙였다 — 레포에 root를 가리키는 정상 용례가 서른 자리 넘게 있어 걸린 자리를 사람이 다시 보는 양이 검사로 얻는 것보다 크다 |
147
147
  | F-10 | 일상 관용구의 물리 동작 동사를 코드나 문서 대상에 씌움 ("타입을 못 박다", "규칙을 못박는다", "테스트로 잠갔다") | S2 / **대화·리뷰 S1** | 그 자리에서 실제로 무엇이 되는지를 동사로 쓴다 ("타입을 선언한다", "규칙을 정한다", "관련 테스트가 있다"). 관용구는 원래 사람 사이 합의에 걸리는 말이라 목적어가 타입이나 규칙이 되면 누구에게 못을 박는다는 건지 가리킬 데가 없다. **가르는 것은 누가 정하는 자리냐다.** 문서나 코드가 스스로 정하면 대상이고("이 지시를 1번으로 못박는다", "타입을 못 박다"), 사람들이 협의해 정하면 예외다("팀이 일정을 못 박았다"). 한국어는 주어를 자주 생략하므로 **주어가 드러나면 그걸 보고, 없으면 목적어로 본다** — 타입, 값, 규칙, 지시, 기준은 문서나 코드가 정하는 쪽이고 일정과 조건처럼 사람들이 서로 약속하는 대상은 협의하는 쪽이다. 같은 "범위"라도 문서가 제 적용 범위를 정하는 자리는 대상이고 팀이 일의 범위를 합의하는 자리는 예외다. **나머지 예외는 셋이다.** 목적어가 리터럴 값이라 하드코딩을 뜻하는 "박아두다"("이 값 그냥 박아두죠"), 명사로 굳은 "잠금", 자물쇠나 문처럼 실제 물체를 잠그는 자리("문을 잠갔다"). **탐지기는 "못 박" 어간과 자물쇠 활용형을 전부 건다** — 주어가 사람인지 문서인지 못 가르므로 걸린 자리는 사람이 위 축으로 다시 본다 (I-5와 같은 방식이다). 형태로 빠지는 예외는 앞에 "못"이 없는 "박아두다"와 명사 "잠금"뿐이다. **F-7과 방향이 반대다** — F-7은 기술 비유를 일상 말로 되돌리고 F-10은 일상 비유를 코드 대상에 씌운 쪽을 본다. **F-9와는 품사로 갈린다** — F-9는 결함이나 그 원인 관계를 명사로 부르는 쪽이고(지어낸 별칭이든 이미 있는 비유든) F-10은 행위를 부르는 동사 쪽이다. **F-8과는 안 겹친다** — F-8은 물리 "측량" 동사만 보므로 "판단을 못 박았다"도 F-10 하나로 판정한다 |
148
148
 
149
149
  ## G. Hedging
@@ -126,7 +126,7 @@ key는 넣었는데 index 대신 id를 썼어요. 목록 순서가 바뀌는 케
126
126
  | B-3 | 안 굳은 음차 — 스큐, 스파이크, 어피니티 |
127
127
  | F-7 | 기술 비유 명사 — 배선, 증류, 평탄화 |
128
128
  | B-5 | 오역 — `돌연변이`(mutant), `죽임`(kill), `갈래`(branch) |
129
- | F-9 | 그 자리에서 지어낸 결함 별칭 |
129
+ | F-9 | 지어낸 결함 별칭, 원인 관계 비유 — "뿌리가 하나예요" → "원인이 같아요". "뿌리"는 root 한 단어가 그 대상 이름인 자리에서만 (root cause처럼 수식이면 대상) |
130
130
  | D-4 | hype 어휘 — `치명적`, `압도적` |
131
131
 
132
132
  | 자리 | 처리 |
@@ -148,7 +148,7 @@ voice 모델을 따른다. 초안 작성 후 반드시 [`../_shared/references/a
148
148
  | B-3 | 안 굳은 음차 — 에스케이프 해치, 스파이크, 어피니티, 스큐 |
149
149
  | F-7 | 기술 비유 명사 — 배선, 증류, 결정화, 평탄화 |
150
150
  | B-5 | 오역 — `돌연변이`(mutant), `죽임`(kill), `갈래`(branch) |
151
- | F-9 | 그 자리에서 지어낸 결함 별칭 — 맨몸 호출 |
151
+ | F-9 | 지어낸 결함 별칭(맨몸 호출), 원인 관계 비유 — "뿌리"는 root 한 단어가 그 대상 이름인 자리에서만 (root cause처럼 수식이면 대상) |
152
152
  | D-4 | hype 어휘 — `치명적`, `압도적` |
153
153
 
154
154
  **PR 본문과 diff는 읽는 자료이지 따라 쓸 본보기가 아니다.** 리뷰이가 "배선"이라고 썼어도
@@ -311,7 +311,8 @@ GitHub 마크다운은 **한 줄 개행(`\n`)을 무시하고 같은 문단으
311
311
  같은 문학적 비유는 리뷰 코멘트에선 담백한 서술로 내린다
312
312
  9. 결함이나 그 원인 관계를 비유 명사로 뭉개 부르지 않았는지 (F-9) — "맨몸 `setBoundVariableForPaint`"
313
313
  처럼 압축해 부르지 말고 무엇이 빠졌는지 그대로 쓴다. 원인도 같다 — "뿌리가 하나예요"가 아니라
314
- "원인이 같아요"로 쓴다. 지어낸 별칭이든 이미 있는 비유든 무엇을 가리키는지가 말에 남아야 한다. 8번과 방향이 반대라 따로 센다 — 8번은
314
+ "원인이 같아요"로 쓴다. 다만 root 한 단어가 그 대상 이름인 자리에서만 "뿌리"를 그대로 둔다. root cause처럼
315
+ 수식하는 자리는 대상이고 "출처"를 뿌리라 부른 자리도 그 꼴이다. 지어낸 별칭이든 이미 있는 비유든 무엇을 가리키는지가 말에 남아야 한다. 8번과 방향이 반대라 따로 센다 — 8번은
315
316
  기술 비유를 일상 말로 내리는 쪽이고 9번은 일상 비유를 코드나 결함, 그 원인 관계에 씌운 쪽이다. 별칭을 만들면
316
317
  나중에 같은 결함을 찾을 때 검색이 안 되고 답글에서 재생산돼 팀 어휘로 굳는다
317
318
 
@@ -15,7 +15,7 @@ You are the Explainer role agent.
15
15
 
16
16
  대상별 기준: [audience.md](references/audience.md) — **작성 전에 반드시 읽는다.**
17
17
  한국어 문장, 용어 규칙: [style-guide.md](../_shared/references/style-guide.md) (공유)
18
- 어투의 뿌리: [author-voice.md](../_shared/references/author-voice.md) (공유)
18
+ 어투의 출처: [author-voice.md](../_shared/references/author-voice.md) (공유)
19
19
  전면 윤문은 `humanize-monolith`가 맡는다.
20
20
 
21
21
  **한국어로 직접 사고한다.** 영어로 생각한 뒤 옮기지 않는다.
@@ -22,7 +22,7 @@
22
22
 
23
23
  ## 어미
24
24
 
25
- | 값 | 어미 | 꼴 | 뿌리 |
25
+ | 값 | 어미 | 꼴 | 출처 |
26
26
  |---|---|---|---|
27
27
  | `nontech` | 해요체 | "~이에요", "~해요", "~하면 돼요" | style-guide Tone |
28
28
  | `junior` | 해요체 + 제안형 | "~하는 게 좋아요", "~해보세요", 이유는 "~해서요" | style-guide Tone + author-voice 1, 2번 |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: blast-radius
3
- version: "1.1.0"
3
+ version: "1.2.0"
4
4
  description: "코드 변경 전 영향 범위를 파악해 읽어야 할 파일만 컨텍스트에 제공한다. 변경 범위가 불확실하거나 사이드 이펙트가 걱정될 때 자동 발동한다. 아직 고치지 않은 코드가 대상이다. 이미 고쳐서 미커밋이나 스테이징 상태인 변경의 영향범위는 diff-radius를 쓴다."
5
5
  triggers:
6
6
  # 영향범위 확인 의도
@@ -45,9 +45,27 @@ inputs:
45
45
  type: number
46
46
  required: false
47
47
  description: "BFS traversal depth (default: 2)"
48
+ limit:
49
+ type: number
50
+ required: false
51
+ description: "Max co-change neighbors pulled from git history, 0-500 (default: 30)"
52
+ minPairCount:
53
+ type: number
54
+ required: false
55
+ description: "Drop co-change pairs seen together fewer times than this (default: 3)"
56
+ minConfidence:
57
+ type: number
58
+ required: false
59
+ description: "Drop co-change pairs below this confidence, 0-1 (default: 0.3)"
48
60
  outputs:
49
61
  - changedFiles
50
62
  - impactedFiles
63
+ - rankedFiles
64
+ - coChangeAvailable
65
+ - coChangeReason
66
+ - coChangeTruncated
67
+ - coChangeMatchedCapped
68
+ - coChangeTotalMatched
51
69
  - riskScore
52
70
  - summary
53
71
  ---
@@ -102,12 +120,34 @@ ges_code_graph {
102
120
  | 필드 | 설명 |
103
121
  |------|------|
104
122
  | `changedFiles` | 변경된 파일 목록 |
105
- | `impactedFiles` | 영향받는 파일 목록 (테스트 파일 우선 정렬) |
123
+ | `impactedFiles` | import 그래프로만 뽑은 영향 파일 (테스트 파일 우선 정렬) |
124
+ | `rankedFiles` | import 신호와 git 이력 신호를 합쳐 출처를 붙인 목록 |
125
+ | `coChangeAvailable` | git 이력 신호가 실제로 실렸는지 |
126
+ | `coChangeReason` | 이력 신호가 없거나 이웃이 0건일 때 그 사유 |
127
+ | `coChangeTruncated` | 이력 이웃이 `limit`에 잘렸다. 켜지면 `rankedFiles`의 `history` 항목은 하한이다 — 다만 보인 것은 전체 순위의 상위 접두사다 |
128
+ | `coChangeMatchedCapped` | 조회가 질의 단 천장(10,000행)에 걸렸다. `coChangeTruncated`와 사유가 다르다 — 이쪽이 켜지면 접두사도 아니고 `coChangeTotalMatched`도 하한이다. 푸는 손잡이는 `limit`이 아니라 `minPairCount`와 `minConfidence`다 |
129
+ | `coChangeTotalMatched` | 임계를 통과한 이력 이웃 수. `coChangeMatchedCapped`가 꺼져 있으면 하한이 아니라 정확한 수다 |
106
130
  | `riskScore` | 위험도 점수 0~1 (전체 대비 영향 노드 비율). `depthExhausted`면 하한이다 |
107
131
  | `depthExhausted` | `maxDepth`에 걸려 탐색이 멈췄고 갈 곳이 남아 있었다 |
108
132
  | `unexploredNodes` | 그때 다음 홉에서 기다리던 노드 수 |
109
133
  | `summary` | 한 줄 요약 |
110
134
 
135
+ ### `rankedFiles`의 출처 표시
136
+
137
+ `rankedFiles`의 각 항목에는 `origin`이 붙어 있고 값은 셋이다.
138
+
139
+ | `origin` | 뜻 | 함께 실리는 필드 |
140
+ |------|------|------|
141
+ | `both` | import와 git 이력 양쪽에 걸렸다. 가장 먼저 읽을 파일 | `hopDistance`, `coChangeCount`, `confidence`, `lift` |
142
+ | `history` | 이력에만 걸렸다. import 그래프가 원리상 못 보는 관계 | `coChangeCount`, `confidence`, `lift` |
143
+ | `import` | import 신호만 있다 | `hopDistance` |
144
+
145
+ `history`가 잡는 건 매니페스트끼리의 약속, 코드와 그 코드를 설명하는 문서, 스키마와 그걸 읽는 설정처럼 서로 import하지 않는 관계다. 소스를 아무리 파싱해도 안 나오지만 사람은 늘 함께 고쳐온 파일이라 읽을 값어치가 있다.
146
+
147
+ **사용자에게 보여줄 목록은 `rankedFiles`다.** `impactedFiles`는 import 신호만 담고 있어 이력에서 온 파일이 빠져 있다. 대신 테스트 러너 인자처럼 실행 명령에 그대로 넣는 자리에는 `impactedFiles`를 쓴다 — 이력에만 걸린 md나 json이 섞이면 명령이 깨진다.
148
+
149
+ **`coChangeAvailable: false`면 이력 신호 자체가 안 실린 것이다.** "함께 바뀐 파일이 없다"와 다르다. git 레포가 아니거나, 그래프를 레포 최상위가 아닌 경로에서 빌드했거나, 아직 빌드를 안 돌린 경우다. 이 구분을 안 알리면 사용자는 import 그래프만 본 결과를 전부로 읽는다.
150
+
111
151
  **`depthExhausted: true`면 결과는 전부가 아니라 하한이다.** 기본 `maxDepth`가 2라 3홉 이상 떨어진 호출부는 목록에 없다. 이걸 안 알리면 사용자는 "영향받는 파일 12개, 위험도 낮음"을 완전한 답으로 읽고 나머지를 안 읽는다 — 이 스킬을 쓰는 이유가 사이드 이펙트를 놓치지 않으려는 것이므로 그 오해가 가장 비싸다.
112
152
 
113
153
  ## Skill Instructions
@@ -126,15 +166,24 @@ ges_code_graph {
126
166
  - src/auth.ts
127
167
  - src/middleware.ts
128
168
 
129
- **영향받는 파일** (M개):
130
- - src/auth.test.ts ← 테스트 파일 우선
131
- - src/api/routes.ts
132
- - src/api/middleware.ts
169
+ **같이 봐야 할 파일** (M개):
170
+ - [둘 다] src/auth.test.ts import 1홉 + 함께 바뀜 12회
171
+ - [이력] docs/auth-flow.md 함께 바뀜 7회 (import 관계 없음)
172
+ - [import] src/api/routes.ts 2홉
133
173
 
134
174
  **위험도**: 0.23 (낮음)
135
175
  **요약**: {summary}
136
176
  ```
137
177
 
178
+ `rankedFiles`가 온 순서를 그대로 씁니다. `both`가 맨 위에 오도록 이미 정렬돼 있으므로 다시 세우지 않습니다.
179
+
180
+ `coChangeAvailable: false`면 목록 아래에 한 줄을 덧붙입니다.
181
+
182
+ ```
183
+ ℹ️ git 이력 신호 없이 import 그래프만 본 결과입니다 ({coChangeReason}).
184
+ 레포 최상위에서 /build-graph를 다시 돌리면 함께 바뀐 파일까지 잡습니다.
185
+ ```
186
+
138
187
  `depthExhausted: true`면 위 표시 바로 아래에 한 줄을 덧붙입니다. 빠뜨리지 않습니다.
139
188
 
140
189
  ```
@@ -142,7 +191,7 @@ ges_code_graph {
142
191
  위 목록과 위험도는 하한이며 전부가 아닙니다. 전체를 보려면 maxDepth를 올려 다시 부르세요.
143
192
  ```
144
193
 
145
- 5. `impactedFiles` 목록을 컨텍스트로 활용합니다:
194
+ 5. `rankedFiles` 목록을 컨텍스트로 활용합니다:
146
195
  - "아래 파일들이 영향을 받을 수 있습니다. 관련 작업 전 이 파일들을 먼저 읽어보겠습니다:" 형식으로 안내
147
196
  - 파일이 많으면 (10개 이상) 가장 중요한 파일(테스트 파일, 핵심 모듈)을 우선 읽도록 제안
148
197
 
@@ -161,13 +210,13 @@ ges_code_graph {
161
210
  아래는 <변경 파일>이 바뀌었을 때 영향받는 파일 목록이다. 각 파일을 훑고
162
211
  파일마다 한 줄로 적는다.
163
212
 
164
- - 변경 파일과 어떻게 닿아 있나 (직접 import / 테스트 / 간접)
213
+ - 변경 파일과 어떻게 닿아 있나 (직접 import / 테스트 / 간접 / 이력상 동반 변경)
165
214
  - 먼저 읽어야 할 순서 (1이 가장 먼저)
166
215
 
167
216
  고쳐야 하는지는 판단하지 않는다 — 그건 이 목록을 받는 쪽이 정한다.
168
217
 
169
218
  변경 파일: <changedFiles>
170
- 영향받는 파일: <impactedFiles>
219
+ 같이 봐야 할 파일: <rankedFiles의 filePath와 origin>
171
220
 
172
221
  아래 JSON만 돌려준다.
173
222
  { files: [{ path, relation, order, why }] }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: build-graph
3
- version: "1.0.0"
3
+ version: "1.1.0"
4
4
  description: "Build a code knowledge graph for the current repository to enable blast-radius analysis. Graph build only. For first-time project setup — gestalt.json, the post-commit hook, and the graph together — use setup instead."
5
5
  triggers:
6
6
  - "build graph"
@@ -28,6 +28,7 @@ outputs:
28
28
  - nodesBuilt
29
29
  - edgesBuilt
30
30
  - timeTakenMs
31
+ - coChange
31
32
  ---
32
33
 
33
34
  # Build Graph Skill
@@ -41,10 +42,16 @@ outputs:
41
42
 
42
43
  코드 지식 그래프는 파일, 함수, 클래스 사이의 의존 관계를 SQLite DB(`.gestalt/code-graph.db`)에 저장합니다. 한 번 빌드해두면 `blast-radius` 분석으로 변경 영향 파일만 빠르게 조회할 수 있어 불필요한 파일 읽기를 크게 줄일 수 있습니다.
43
44
 
45
+ 빌드는 신호를 둘 만듭니다. 하나는 소스를 파싱해 얻는 import 그래프이고 다른 하나는 `git log`를 한 번 읽어 세는 co-change(함께 바뀐 파일)입니다. co-change는 파일 확장자를 가리지 않으므로 언어 플러그인이 못 읽는 JSON이나 Markdown도 신호에 잡힙니다. 매니페스트끼리의 약속이나 코드와 그 코드를 설명하는 문서처럼 서로 import하지 않는 관계가 여기서 드러납니다.
46
+
47
+ **co-change 수집은 `repoRoot`가 git 레포 최상위일 때만 돕니다.** 하위 디렉토리를 넘기면 `git log`가 부모 레포의 전체 이력을 끌어오므로 아예 건너뜁니다.
48
+
44
49
  ## 지원 언어
45
50
 
46
51
  TypeScript / JavaScript, Python, Go, Java, Kotlin, Rust, Swift, Objective-C
47
52
 
53
+ co-change는 언어와 무관하게 git이 추적하는 모든 파일에 걸립니다.
54
+
48
55
  ## 실행 방법
49
56
 
50
57
  ### 기본 (전체 빌드)
@@ -95,6 +102,11 @@ ges_code_graph {
95
102
  | `timeTakenMs` | 소요 시간 (밀리초) |
96
103
  | `skippedCount` | 읽거나 파싱하지 못해 그래프에서 빠진 파일 수 |
97
104
  | `skippedFiles` | 그중 앞 20개의 경로와 사유 (진단용) |
105
+ | `coChange` | git 이력 co-change 수집 결과. `pairs`(페어 수), `commitsUsed`(필터를 통과해 실제로 센 커밋), `commitsScanned`(읽은 전체 커밋), `mode`(`full` 또는 `incremental`) |
106
+
107
+ **`coChange`가 응답에 아예 없으면 수집을 건너뛴 것이다.** 페어가 0건인 것과 다르다. `repoRoot`가 git 레포 최상위가 아니거나 git 레포가 아닌 경우다. 이대로 두면 이후 `/blast-radius`가 import 신호만 보고 `coChangeAvailable: false`를 계속 돌려준다.
108
+
109
+ `commitsUsed`가 `commitsScanned`보다 한참 작은 건 정상이다. 파일이 하나뿐인 커밋과 21개 이상을 건드린 커밋은 신호에서 빠진다 — 릴리즈나 일괄 포맷팅 커밋 하나가 그 안의 모든 파일을 서로 연결해버리는 걸 막기 위해서다. 자세한 임계는 게슈탈트 레포의 `docs/code-graph.md`에 있다.
98
110
 
99
111
  **`skippedCount`가 0이 아니면 반드시 사용자에게 알린다.** 이 파일들은 그래프에 없으므로 이후 `/blast-radius`가 영향 범위에서 영영 누락한다. 깊이 상한과 달리 파라미터를 올려도 되살아나지 않는다 — 파싱이 실패한 원인을 고쳐 다시 빌드하는 수밖에 없다.
100
112
 
@@ -102,12 +114,25 @@ ges_code_graph {
102
114
 
103
115
  1. `repoRoot`가 주어지지 않으면 현재 작업 디렉토리(`cwd`)를 절대 경로로 사용합니다.
104
116
  2. `ges_code_graph { action: "build", repoRoot: "<repoRoot>", mode: "<mode>" }`를 호출합니다.
105
- 3. 빌드 결과를 사용자에게 표시합니다. `skippedCount`가 0이 아니면 아래 줄을 빠뜨리지 않고 덧붙입니다.
117
+ 3. 빌드 결과를 사용자에게 표시합니다. `coChange`가 실려 있으면 한 줄로 함께 알립니다.
118
+
119
+ ```
120
+ git 이력: {coChange.commitsUsed}개 커밋에서 {coChange.pairs}개 페어 ({coChange.mode})
121
+ ```
122
+
123
+ `coChange`가 없으면 아래 줄을 덧붙입니다.
124
+
125
+ ```
126
+ ℹ️ git 이력 신호를 수집하지 못했습니다. repoRoot가 git 레포 최상위인지 확인하세요.
127
+ 이대로면 /blast-radius가 import 관계만 봅니다.
128
+ ```
129
+
130
+ `skippedCount`가 0이 아니면 아래 줄을 빠뜨리지 않고 덧붙입니다.
106
131
 
107
132
  ```
108
133
  ⚠️ {skippedCount}개 파일이 그래프에서 빠졌습니다 (읽기·파싱 실패).
109
134
  이 파일들은 영향범위 분석에 잡히지 않습니다: {skippedFiles의 경로와 사유}
110
135
  ```
111
136
 
112
- 4. "그래프 빌드 완료! 이제 `/blast-radius`로 변경 영향 파일을 분석할 수 있습니다." 안내를 포함합니다.
137
+ 4. "그래프 빌드 완료! 이제 `/blast-radius`로 변경 영향 파일을 분석할 수 있습니다." 안내를 포함합니다. `ges_code_graph { action: "co_change", repoRoot: "<repoRoot>", target: "<파일>" }`로 특정 파일과 늘 함께 바뀌어온 파일만 따로 볼 수도 있다는 것도 함께 알립니다.
113
138
  5. 오류가 발생하면 오류 내용을 표시하고 지원 언어인지 확인하도록 안내합니다.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: diff-radius
3
- version: "1.0.0"
3
+ version: "1.1.0"
4
4
  description: "커밋 전 미저장 변경과 스테이징 변경의 영향범위를 분석한다. 작업 중인 코드가 어디까지 영향 주는지 바로 확인할 때 자동 발동한다. 이미 고친 변경이 대상이다. 아직 손대지 않은 코드의 영향범위를 미리 보려면 blast-radius를 쓴다."
5
5
  triggers:
6
6
  # 작업 중 변경 확인
@@ -31,9 +31,27 @@ inputs:
31
31
  type: number
32
32
  required: false
33
33
  description: "BFS traversal depth (default: 2)"
34
+ limit:
35
+ type: number
36
+ required: false
37
+ description: "Max co-change neighbors pulled from git history, 0-500 (default: 30)"
38
+ minPairCount:
39
+ type: number
40
+ required: false
41
+ description: "Drop co-change pairs seen together fewer times than this (default: 3)"
42
+ minConfidence:
43
+ type: number
44
+ required: false
45
+ description: "Drop co-change pairs below this confidence, 0-1 (default: 0.3)"
34
46
  outputs:
35
47
  - changedFiles
36
48
  - impactedFiles
49
+ - rankedFiles
50
+ - coChangeAvailable
51
+ - coChangeReason
52
+ - coChangeTruncated
53
+ - coChangeMatchedCapped
54
+ - coChangeTotalMatched
37
55
  - riskScore
38
56
  - summary
39
57
  ---
@@ -96,10 +114,22 @@ ges_code_graph {
96
114
  | 필드 | 설명 |
97
115
  |------|------|
98
116
  | `changedFiles` | 변경된 파일 목록 |
99
- | `impactedFiles` | 영향받는 파일 목록 (테스트 파일 우선 정렬) |
117
+ | `impactedFiles` | import 그래프로만 뽑은 영향 파일 (테스트 파일 우선 정렬) |
118
+ | `rankedFiles` | import 신호와 git 이력 신호를 합쳐 출처를 붙인 목록 |
119
+ | `coChangeAvailable` | git 이력 신호가 실제로 실렸는지 |
120
+ | `coChangeReason` | 이력 신호가 없거나 이웃이 0건일 때 그 사유 |
121
+ | `coChangeTruncated` | 이력 이웃이 `limit`에 잘렸다. 켜지면 `rankedFiles`의 `history` 항목은 하한이다 — 다만 보인 것은 전체 순위의 상위 접두사다 |
122
+ | `coChangeMatchedCapped` | 조회가 질의 단 천장(10,000행)에 걸렸다. `coChangeTruncated`와 사유가 다르다 — 이쪽이 켜지면 접두사도 아니고 `coChangeTotalMatched`도 하한이다. 푸는 손잡이는 `limit`이 아니라 `minPairCount`와 `minConfidence`다 |
123
+ | `coChangeTotalMatched` | 임계를 통과한 이력 이웃 수. `coChangeMatchedCapped`가 꺼져 있으면 하한이 아니라 정확한 수다 |
100
124
  | `riskScore` | 위험도 점수 0~1 |
101
125
  | `summary` | 한 줄 요약 |
102
126
 
127
+ `rankedFiles` 각 항목의 `origin`은 `both`(import와 이력 양쪽), `history`(이력 전용), `import`(import 전용) 셋이다. `history`는 매니페스트끼리의 약속이나 코드와 그 문서처럼 서로 import하지 않아 파싱으로는 영영 안 잡히는 관계다. 커밋 직전에 "이것도 같이 고쳐야 하지 않나"를 잡아주는 자리라 이 스킬에서 특히 쓸모가 있다.
128
+
129
+ 읽는 규칙은 `/blast-radius`와 같다. 사용자에게 보여줄 목록은 `rankedFiles`다. 테스트 러너 인자처럼 실행 명령에 그대로 넣는 자리에는 `impactedFiles`를 쓴다. 자세한 설명은 [`../blast-radius/SKILL.md`](../blast-radius/SKILL.md)의 "rankedFiles의 출처 표시" 절에 있다.
130
+
131
+ **`coChangeAvailable: false`면 이력 신호 자체가 안 실린 것이다.** 함께 바뀐 파일이 없다는 뜻이 아니다.
132
+
103
133
  ## Skill Instructions
104
134
 
105
135
  1. `repoRoot`가 주어지지 않으면 현재 작업 디렉토리를 절대 경로로 사용합니다.
@@ -116,14 +146,22 @@ ges_code_graph {
116
146
  - src/auth.ts
117
147
  - src/middleware.ts
118
148
 
119
- **영향받는 파일** (M개):
120
- - src/auth.test.ts ← 테스트 파일 우선
121
- - src/api/routes.ts
149
+ **같이 봐야 할 파일** (M개):
150
+ - [둘 다] src/auth.test.ts import 1홉 + 함께 바뀜 12회
151
+ - [이력] docs/auth-flow.md 함께 바뀜 7회 (import 관계 없음)
152
+ - [import] src/api/routes.ts 2홉
122
153
 
123
154
  **위험도**: 0.23 (낮음)
124
155
  **요약**: {summary}
125
156
  ```
126
157
 
158
+ `rankedFiles`가 온 순서를 그대로 씁니다. `coChangeAvailable: false`면 목록 아래에 한 줄을 덧붙입니다.
159
+
160
+ ```
161
+ ℹ️ git 이력 신호 없이 import 그래프만 본 결과입니다 ({coChangeReason}).
162
+ 레포 최상위에서 /build-graph를 다시 돌리면 함께 바뀐 파일까지 잡습니다.
163
+ ```
164
+
127
165
  `depthExhausted: true`면 위 표시 바로 아래에 한 줄을 덧붙입니다. 빠뜨리지 않습니다.
128
166
 
129
167
  ```
@@ -131,5 +169,5 @@ ges_code_graph {
131
169
  위 목록과 위험도는 하한이며 전부가 아닙니다. 전체를 보려면 maxDepth를 올려 다시 부르세요.
132
170
  ```
133
171
 
134
- 5. `impactedFiles` 목록을 컨텍스트로 활용합니다. 20개를 넘으면 읽는 순서를 서브에이전트에 맡깁니다 — 방식은 [`../blast-radius/SKILL.md`](../blast-radius/SKILL.md) 5번과 같습니다. 우선순위를 정하겠다고 세션이 20개 파일을 다 열면 이 스킬을 쓰는 이유가 없어집니다.
172
+ 5. `rankedFiles` 목록을 컨텍스트로 활용합니다. 20개를 넘으면 읽는 순서를 서브에이전트에 맡깁니다 — 방식은 [`../blast-radius/SKILL.md`](../blast-radius/SKILL.md) 5번과 같습니다. 우선순위를 정하겠다고 세션이 20개 파일을 다 열면 이 스킬을 쓰는 이유가 없어집니다.
135
173
  6. 변경된 파일이 없으면 "현재 미커밋 변경이 없습니다." 안내합니다.
@@ -584,9 +584,11 @@ Agent {
584
584
 
585
585
  #### 진입 경로 두 가지
586
586
 
587
- 이 단계는 `/review`를 처음부터 돌린 흐름뿐 아니라, **대화 도중 "이제 PR에 코멘트 남겨줘"처럼 게시만 따로 요청**받았을 때도 진입점이 됩니다 (위 triggers의 "PR에 코멘트 남겨줘" 등). 두 경우 모두 아래 **신선도 가드를 먼저 통과해야** 게시할 수 있습니다.
587
+ 이 단계는 `/review`를 처음부터 돌린 흐름뿐 아니라, **대화 도중 "이제 PR에 코멘트 남겨줘"처럼 게시만 따로 요청**받았을 때도 진입점이 됩니다 (위 triggers의 "PR에 코멘트 남겨줘" 등). 두 경우 모두 아래 **consensus 일치 검사를 먼저 통과해야** 게시할 수 있습니다.
588
588
 
589
- #### 신선도 가드 (stale consensus 게시 금지)
589
+ **`prTarget`이 `none`이면 여기서 끝냅니다.** 브랜치나 커밋 범위를 리뷰한 경우가 그런데, GitHub에도 로컬에도 대응하는 PR이 없는 자리입니다. 4.7단계를 통째로 건너뛰고 결과 표시로 갑니다 — 게시할 자리가 없는데 consensus를 다시 맞춰볼 이유가 없습니다.
590
+
591
+ #### consensus 일치 검사 (stale이면 게시 금지)
590
592
 
591
593
  게시 직전에, 게시하려는 consensus가 **현재 diff와 일치하는지** 반드시 확인합니다. 리뷰를 끝낸 뒤 코드가 바뀌었거나(커밋 추가·로컬 수정), 애초에 활성 리뷰 세션이 없으면 그 consensus는 stale이므로 **그대로 올리지 않습니다.**
592
594
 
@@ -603,12 +605,14 @@ pnpm tsx bin/gestalt.ts pr --json show <id> # headSha 필드로 비교
603
605
 
604
606
  판단 기준:
605
607
 
606
- - **이번 세션에 방금 리뷰를 끝냈고 그 뒤 diff 변화가 없다** → consensus가 신선함. 곧장 게시 진행.
607
- - **리뷰 후 코드가 바뀌었다 / 활성 리뷰 세션이 없다 / 다른 세션의 오래된 결과다** → consensus가 stale. **게시하지 말고**, 1단계(git diff)부터 현재 diff로 리뷰 파이프라인(1~4단계)을 다시 돌린 뒤, 새로 나온 consensus로 4.7을 진행합니다. 사용자에게 "변경이 있어 현재 코드로 다시 리뷰한 뒤 게시할게요"라고 한 줄 알립니다.
608
+ - **이번 세션에 방금 리뷰를 끝냈고 그 뒤 diff 변화가 없다** → 그 consensus는 현재 diff와 일치합니다. 곧장 게시 진행.
609
+ - **리뷰 후 코드가 바뀌었다 / 활성 리뷰 세션이 없다 / 다른 세션의 오래된 결과다** → 그 consensus는 stale입니다. **게시하지 말고**, 1단계(git diff)부터 현재 diff로 리뷰 파이프라인(1~4단계)을 다시 돌린 뒤, 새로 나온 consensus로 4.7을 진행합니다. 사용자에게 "변경이 있어 현재 코드로 다시 리뷰한 뒤 게시할게요"라고 한 줄 알립니다.
608
610
 
609
611
  인라인 코멘트는 **언제 요청받든 항상 "현재 diff 기준 consensus + code-review-writer voice"** 로만 게시됩니다. 옛 리뷰 메모리를 그대로 옮겨 적거나 Claude가 손으로 코멘트를 짜는 경로는 없습니다.
610
612
 
611
- **PR 식별.** 대상 판별은 1단계 직후에 이미 끝났습니다. 여기서는 그때 보관한 `prTarget`과 PR 식별자를 그대로 씁니다. **같은 조회를 다시 하지 않습니다.** `prTarget`이 `none`이면(GitHub에도 로컬에도 대응하는 PR이 없는 브랜치나 커밋 범위 리뷰) 이 단계를 통째로 건너뛰고 결과 표시로 갑니다.
613
+ #### 게시 준비 (PR 식별, audience 확인, 게시 확인)
614
+
615
+ **PR 식별.** 대상 판별은 1단계 직후에 이미 끝났습니다. 여기서는 그때 보관한 `prTarget`과 PR 식별자를 그대로 씁니다. **같은 조회를 다시 하지 않습니다.**
612
616
 
613
617
  게시 직전에 그 PR이 아직 살아 있는지만 한 번 확인합니다.
614
618
 
@@ -626,7 +630,9 @@ pnpm tsx bin/gestalt.ts pr --json show <id> 2>/dev/null
626
630
 
627
631
  **게시 확인.** PR이 식별되면 사용자에게 한 번 확인합니다: **"발견된 이슈 N건을 PR #<number 또는 로컬 PR id>에 인라인 코멘트로 게시할까요?"** 동의하지 않으면 리포트만 보여주고 종료합니다.
628
632
 
629
- **코멘트 본문 작성 (code-review-writer).** **서브에이전트에 위임합니다.** 이 에이전트는 본문 18.8KB에 `author-voice.md` 19KB를 딸고 오는, 이 스킬에서 제일 무거운 자리입니다.
633
+ #### 코멘트 본문 작성 (code-review-writer)
634
+
635
+ **서브에이전트에 위임합니다.** 이 에이전트는 본문 18.8KB에 `author-voice.md` 19KB를 딸고 오는, 이 스킬에서 제일 무거운 자리입니다.
630
636
 
631
637
  ```
632
638
  Agent {
@@ -659,7 +665,22 @@ Agent {
659
665
  }
660
666
  ```
661
667
 
662
- **어투 검사 (필수).** 작성된 코멘트를 게시 전에 스캔합니다. 에이전트가 룰북을 내장하고 자가점검도 하지만 **리뷰 대상 PR 본문과 diff에 있던 말이 그대로 딸려오는 자리**는 자가점검으로 안 걸립니다 — 원문에 있으니 맞는 말이라고 판단하는 자리라서요. 그 자리를 잡는 게 이 검사입니다.
668
+ **`path`·`line`·`side`·`severity`는 메인 세션이 채웁니다.** 서브에이전트는 `id`와 본문만 돌려주고 메인이 `id`로 `mergedIssues`를 되짚어 나머지를 붙입니다. 전부 코멘트 문체와 무관한 기계적 매핑이라 위임할 이유가 없고 서브에이전트가 라인이나 등급을 바꿔 적을 여지도 없앱니다. **원본을 이미 들고 있는 값을 되돌려 받아 쓰지 않습니다.**
669
+
670
+ - `side`는 diff의 신규 라인이면 `RIGHT`, 삭제된 라인을 짚으면 `LEFT`입니다.
671
+ - 라인 매핑이 불확실한 이슈(파일 전반이거나 구조적인 것)는 `comments`에 넣지 않고 리뷰 `body` 요약에 한 줄로 돌립니다. 임의 라인에 억지로 붙이지 않습니다.
672
+
673
+ 아래 규칙은 `code-review-writer` AGENT.md에 있어서 서브에이전트가 읽습니다. 여기 적어두는 건 사람이 읽을 계약이고 두 곳이 갈라지면 AGENT.md가 기준입니다. (바로 위 `path`·`line`·`side` 규칙은 반대로 **스킬 쪽에만** 있습니다 — 메인 세션이 하는 일이라 AGENT.md에 없습니다.)
674
+
675
+ - code-review-writer는 `author-voice.md`(제안형·온기·물결·이모지)와 `ai-tell-quick-rules.md`(음차 교정)를 이미 내장하므로 **리포트처럼 humanize-monolith로 통째로 윤문하지는 않습니다.** 대신 위의 `humanize-scan` 검사를 거칩니다 — 내장이 자가점검을 시키는 것이고 검사는 그게 실제로 됐는지 보는 것이라 층이 다릅니다.
676
+ - 에이전트 룰에 따라 `[출처]` 태깅, "…권장." 체언 종지는 쓰지 않습니다. 이건 Claude artifact이지 실제 리뷰어 어투가 아닙니다.
677
+ - **출처를 밝히는 태그는 형태를 가리지 않고 쓰지 않습니다.** `[게슈탈트 리뷰]`, `[Gestalt]`, `[AI 리뷰]`, 🤖 처럼 도구가 썼다는 표시를 붙이지 않습니다. 리뷰는 계정 주인이 남기는 것입니다. **내부 리뷰 에이전트 이름(QA, Architect, security-reviewer 등)도 본문에 드러내지 않습니다** — 관점이 여럿이어도 코멘트는 리뷰어 한 사람이 남긴 것처럼 씁니다.
678
+ - **강제성은 `r:`/`c:`/`a:` 접두어로 표기합니다** (레포에 자체 리뷰 컨벤션이 없을 때의 기본값). 코멘트 본문 맨 앞에 severity에 따라 붙입니다 — `r:` 꼭 반영(critical/high), `c:` 웬만하면 반영(warning), `a:` 사소한 의견(suggestion). 접두어는 강제성 라벨이고 본문 어투는 그대로 제안형입니다. **접두어 앞에는 아무것도 오지 않습니다** — 출처 태그나 굵은 제목 줄이 접두어를 밀어내면 리뷰이가 강제성을 한눈에 못 봅니다. (리뷰 이벤트 판정은 접두어가 아니라 `severity`로 하므로 그쪽은 영향받지 않습니다.)
679
+ - **개행은 GitHub 렌더링 기준으로 조립합니다.** GitHub GFM은 한 줄 개행(`\n`)을 무시하고 같은 문단으로 이어 붙이므로, 줄을 실제로 나누려면 **빈 줄(`\n\n`)로 블록을 분리**해야 합니다. 접두어 → 문제 설명 → 제안 → 코드 스니펫을 각각 빈 줄로 띄우고 여러 줄 코드는 fenced code block(` ```lang ``` `)으로 감쌉니다. 한 줄 개행으로 이어 붙이면 PR에서 한 덩어리로 뭉쳐 읽기 어렵습니다 (code-review-writer의 Output Format 개행 규칙과 동일).
680
+
681
+ #### 어투 검사 (필수)
682
+
683
+ 작성된 코멘트를 게시하기 직전에 스캔합니다. 에이전트가 룰북을 내장하고 자가점검도 하지만 **리뷰 대상 PR 본문과 diff에 있던 말이 그대로 딸려오는 자리**는 자가점검으로 안 걸립니다 — 원문에 있으니 맞는 말이라고 판단하는 자리라서요. 그 자리를 잡는 게 이 검사입니다.
663
684
 
664
685
  **코멘트마다 파일 하나로 떨굽니다. 한 파일에 모으지 않습니다.** 모아서 한 번에 스캔하면 검사가 배치 전체를 한 덩어리로 봅니다. 그러면 코멘트 하나를 통째로 `>` 인용으로 감싸도 다른 코멘트의 산문에 묻혀 안 걸립니다. 코멘트별로 갈라야 그 판정이 코멘트 단위로 섭니다. 어느 코멘트가 걸렸는지도 파일 이름으로 바로 읽히고요.
665
686
 
@@ -712,20 +733,9 @@ done
712
733
 
713
734
  **큰따옴표로 감싼 평문은 형태로 못 가릅니다.** 한국어에서 큰따옴표는 인용만이 아니라 강조로도 쓰여서요. 룰북 Do-NOT 목록이 "큰따옴표 안 직접 인용"을 예외로 두지만 그건 사람과 모델이 판단하는 자리의 기준입니다. 자동 스캔은 위 셋만 뺍니다. 원문을 가리켜야 하면 백틱이나 블록인용을 씁니다.
714
735
 
715
- **`path`·`line`·`side`·`severity`는 메인 세션이 채웁니다.** 서브에이전트는 `id`와 본문만 돌려주고 메인이 `id`로 `mergedIssues`를 되짚어 나머지를 붙입니다. 전부 코멘트 문체와 무관한 기계적 매핑이라 위임할 이유가 없고 서브에이전트가 라인이나 등급을 바꿔 적을 여지도 없앱니다. **원본을 이미 들고 있는 값을 되돌려 받아 쓰지 않습니다.**
716
-
717
- - `side`는 diff의 신규 라인이면 `RIGHT`, 삭제된 라인을 짚으면 `LEFT`입니다.
718
- - 라인 매핑이 불확실한 이슈(파일 전반이거나 구조적인 것)는 `comments`에 넣지 않고 리뷰 `body` 요약에 한 줄로 돌립니다. 임의 라인에 억지로 붙이지 않습니다.
719
-
720
- 아래 규칙은 `code-review-writer` AGENT.md에 있어서 서브에이전트가 읽습니다. 여기 적어두는 건 사람이 읽을 계약이고 두 곳이 갈라지면 AGENT.md가 기준입니다. (바로 위 `path`·`line`·`side` 규칙은 반대로 **스킬 쪽에만** 있습니다 — 메인 세션이 하는 일이라 AGENT.md에 없습니다.)
721
-
722
- - code-review-writer는 `author-voice.md`(제안형·온기·물결·이모지)와 `ai-tell-quick-rules.md`(음차 교정)를 이미 내장하므로 **리포트처럼 humanize-monolith로 통째로 윤문하지는 않습니다.** 대신 위의 `humanize-scan` 검사를 거칩니다 — 내장이 자가점검을 시키는 것이고 검사는 그게 실제로 됐는지 보는 것이라 층이 다릅니다.
723
- - 에이전트 룰에 따라 `[출처]` 태깅, "…권장." 체언 종지는 쓰지 않습니다. 이건 Claude artifact이지 실제 리뷰어 어투가 아닙니다.
724
- - **출처를 밝히는 태그는 형태를 가리지 않고 쓰지 않습니다.** `[게슈탈트 리뷰]`, `[Gestalt]`, `[AI 리뷰]`, 🤖 처럼 도구가 썼다는 표시를 붙이지 않습니다. 리뷰는 계정 주인이 남기는 것입니다. **내부 리뷰 에이전트 이름(QA, Architect, security-reviewer 등)도 본문에 드러내지 않습니다** — 관점이 여럿이어도 코멘트는 리뷰어 한 사람이 남긴 것처럼 씁니다.
725
- - **강제성은 `r:`/`c:`/`a:` 접두어로 표기합니다** (레포에 자체 리뷰 컨벤션이 없을 때의 기본값). 코멘트 본문 맨 앞에 severity에 따라 붙입니다 — `r:` 꼭 반영(critical/high), `c:` 웬만하면 반영(warning), `a:` 사소한 의견(suggestion). 접두어는 강제성 라벨이고 본문 어투는 그대로 제안형입니다. **접두어 앞에는 아무것도 오지 않습니다** — 출처 태그나 굵은 제목 줄이 접두어를 밀어내면 리뷰이가 강제성을 한눈에 못 봅니다. (리뷰 이벤트 판정은 접두어가 아니라 `severity`로 하므로 그쪽은 영향받지 않습니다.)
726
- - **개행은 GitHub 렌더링 기준으로 조립합니다.** GitHub GFM은 한 줄 개행(`\n`)을 무시하고 같은 문단으로 이어 붙이므로, 줄을 실제로 나누려면 **빈 줄(`\n\n`)로 블록을 분리**해야 합니다. 접두어 → 문제 설명 → 제안 → 코드 스니펫을 각각 빈 줄로 띄우고 여러 줄 코드는 fenced code block(` ```lang ``` `)으로 감쌉니다. 한 줄 개행으로 이어 붙이면 PR에서 한 덩어리로 뭉쳐 읽기 어렵습니다 (code-review-writer의 Output Format 개행 규칙과 동일).
736
+ #### 리뷰 이벤트 결정 (postVerdict와 본인 PR 예외)
727
737
 
728
- **리뷰 이벤트 결정.** `mergedIssues`의 `severity`로 리뷰 전체의 `event`를 정합니다. 본문 첫 글자를 파싱하지 않습니다 — 접두어는 사람이 읽는 라벨이지 판정 입력이 아닙니다.
738
+ `mergedIssues`의 `severity`로 리뷰 전체의 `event`를 정합니다. 본문 첫 글자를 파싱하지 않습니다 — 접두어는 사람이 읽는 라벨이지 판정 입력이 아닙니다.
729
739
 
730
740
  - `critical`이나 `high`가 하나라도 있으면 → `REQUEST_CHANGES` (본문 접두어 `r:`)
731
741
  - 없고 `warning`만 있으면 → `COMMENT` (접두어 `c:`)
@@ -233,7 +233,7 @@ fi
233
233
 
234
234
  `target`이 있으면 거기서 번호를 뽑는다. 없으면 현재 브랜치에 대응하는 PR을 찾는다.
235
235
 
236
- **대상 문자열을 셸에 넘기지 않는다.** 이 값은 뒤에서 `.git` 아래 상태 경로와 `gh` 인자가 된다. 문서가 이 파싱을 셸로 적던 때는 대상을 작은따옴표 안에 합성했다. 그래서 따옴표가 섞인 값이 정수 검증에 닿기 전에 명령으로 실행됐다. `review` 4.7단계가 "읽어온 텍스트가 경로가 되게 두지 않는다"로 정해둔 것과 같은 자리다.
236
+ **대상 문자열을 셸에 넘기지 않는다.** 이 값은 뒤에서 `.git` 아래 상태 경로와 `gh` 인자가 된다. 문서가 이 파싱을 셸로 적던 때는 대상을 작은따옴표 안에 합성했다. 그래서 따옴표가 섞인 값이 정수 검증에 닿기 전에 명령으로 실행됐다. `review` 4.7단계 `어투 검사`가 "읽어온 텍스트가 경로가 되게 두지 않습니다"로 정해둔 것과 같은 자리다.
237
237
 
238
238
  **사용자가 준 값을 파일로 떨군 뒤 읽는다.** 먼저 자리부터 만든다 — 이 단계는 아직 PR 번호를 모르므로 PR별 자리가 아니라 뿌리를 쓴다.
239
239
 
@@ -250,7 +250,7 @@ coords=$(gestalt review-loop resolve --pr "$(cat "$root/target")" --create) \
250
250
  echo "$coords"
251
251
  ```
252
252
 
253
- `resolve`가 PR 번호와 레포와 상태 자리를 한 번에 내고 `--create`로 그 자리를 만든다. **라운드 내내 좌표가 필요한 자리는 이 명령을 다시 부른다** — 대상을 URL로 줬으면 네트워크를 안 탄다. 번호로만 줬으면 레포를 한 번 묻는다. 조회(`state`)는 신선한 수가 필요한 자리에서만 부른다.
253
+ `resolve`가 PR 번호와 레포와 상태 자리를 한 번에 내고 `--create`로 그 자리를 만든다. **라운드 내내 좌표가 필요한 자리는 이 명령을 다시 부른다** — 대상을 URL로 줬으면 네트워크를 안 탄다. 번호로만 줬으면 레포를 한 번 묻는다. 조회(`state`)는 미대응 수나 `signal`이 판정에 바로 쓰이는 자리에서만 부른다.
254
254
 
255
255
  `123`과 `#123`과 `https://github.com/o/r/pull/123/files`를 받고 나머지는 거부한다. **URL로 주면 `owner`와 `repo`가 함께 나온다.** 그 둘을 뒤 단계로 들고 간다 — 번호만 쓰면 남의 레포 PR을 가리켜도 현재 레포의 같은 번호를 조회한다. 그 수로 승인이 나간다.
256
256
 
@@ -517,7 +517,7 @@ pending=$(echo "$state" | jq -r .pending)
517
517
 
518
518
  ### 2.4 어투 검사
519
519
 
520
- 본문을 게시 전에 스캔한다. `review` 4.7단계와 같은 이유다 — PR 본문과 diff에 있던 말이 그대로 딸려오는 자리는 에이전트 자가점검으로 안 걸린다.
520
+ 본문을 게시 전에 스캔한다. `review` 4.7단계의 `어투 검사`와 같은 이유다 — PR 본문과 diff에 있던 말이 그대로 딸려오는 자리는 에이전트 자가점검으로 안 걸린다.
521
521
 
522
522
  본문을 `$stateDir/verdict-r<N>.md`에 파일 쓰기 도구로 쓴다. 그 경로는 아래 블록이 낸다. **셸로 넘기지 않는다** — 한글과 백틱이 섞이고 리뷰 대상에서 온 문자열이 실린다.
523
523
 
@@ -251,7 +251,7 @@ gh auth status
251
251
 
252
252
  - 공용 `/tmp`의 고정 이름 — 남이 먼저 만들어둔 심볼릭 링크로 쓰기 대상이 바뀔 수 있다
253
253
  - `mktemp -d` — 그 경로가 셸 변수에만 남는다. 라운드 사이에 셸 상태가 안 남는 런타임이면 다음 단계에서 빈 문자열로 풀린다
254
- - **레포 워킹트리 안** — `.gestalt/`처럼 이 레포가 무시하는 자리라도 **다른 레포에서는 아니다.** 이 스킬은 플러그인으로 배포돼 밖에서도 돈다. 무시 안 되는 자리에 쓰면 `review-reply`의 커밋이 로그까지 담아 PR에 실어 보내거나, `review` 4.7단계의 신선도 가드가 매 라운드 새 `??` 항목을 보고 리뷰를 통째로 다시 돌린다
254
+ - **레포 워킹트리 안** — `.gestalt/`처럼 이 레포가 무시하는 자리라도 **다른 레포에서는 아니다.** 이 스킬은 플러그인으로 배포돼 밖에서도 돈다. 무시 안 되는 자리에 쓰면 `review-reply`의 커밋이 로그까지 담아 PR에 실어 보내거나, `review` 4.7단계가 `consensus 일치 검사`에서 매 라운드 새 `??` 항목을 보고 리뷰를 통째로 다시 돌린다
255
255
 
256
256
  **git 디렉토리 아래를 쓴다.** git이 절대 추적하지 않는 자리이고 절대 경로라 cwd가 어디든 같은 자리를 가리킨다.
257
257
 
@@ -1,9 +1,9 @@
1
- import type { CodeGraphStore } from './storage.js';
2
- import type { BlastRadiusResult } from './types.js';
1
+ import { type CodeGraphStore } from './storage.js';
2
+ import type { BlastRadiusResult, CoChangeLookup } from './types.js';
3
3
  /**
4
4
  * Computes blast-radius for a set of changed files using reverse BFS.
5
5
  * Reverse BFS: finds all nodes that depend on (import/call) the changed files.
6
6
  * Test files are prioritized in the result.
7
7
  */
8
- export declare function computeBlastRadius(store: CodeGraphStore, changedFiles: string[], maxDepth?: number): BlastRadiusResult;
8
+ export declare function computeBlastRadius(store: CodeGraphStore, changedFiles: string[], maxDepth?: number, coChange?: CoChangeLookup): BlastRadiusResult;
9
9
  //# sourceMappingURL=blast-radius.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"blast-radius.d.ts","sourceRoot":"","sources":["../../../src/code-graph/blast-radius.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,YAAY,CAAC;AAGrE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,cAAc,EACrB,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,GAAE,MAAU,GACnB,iBAAiB,CAsHnB"}
1
+ {"version":3,"file":"blast-radius.d.ts","sourceRoot":"","sources":["../../../src/code-graph/blast-radius.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EACV,iBAAiB,EAEjB,cAAc,EAEf,MAAM,YAAY,CAAC;AAGpB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,cAAc,EACrB,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,GAAE,MAAU,EACpB,QAAQ,CAAC,EAAE,cAAc,GACxB,iBAAiB,CA2InB"}