실전

X 가 공개한 코드 전부에 zzop 을 돌렸다

X(구 Twitter)와 xAI 의 레포 열두 개 — For You 피드, Grok 의 빌드 시스템, xAI SDK, 커뮤니티 노트 — 를 zzop v0.33.0 으로 2026-08-16 에 한 번에 분석했다. 아래는 먼저 전체 의존 그래프를 한 캔버스에 올린 것이고, 이어서 나머지 네 개 도메인 내보내기다. 잘려 나간 것과 못 본 것은 전부 그림 자체가 말한다.

4,457dep 노드(파일)
10,614트리 안에서 풀린 import 엣지
6import 순환
1,870,057LOC(dep 노드)
171발견(12개 중 4개 레포)
6 / 3변경 라우트 / 인증 증거 없음

이 그림이 흔한 그래퍼와 다르게 생긴 이유

흔한 그래퍼는 "모든 파일과 모든 참조"를 한 덩어리 털뭉치로 그린다. zzop 은 세 군데서 갈린다. (1) 엣지는 트리 안에서 실제로 풀린 import 뿐이다 — npm/pip 패키지 참조와 못 푼 스펙파이어는 버린다. 그래서 레포 열두 개가 서로 이어지지 않은 열두 개로 나온다(정직한 모양이다 — 이 레포들은 서로를 import 하지 않는다). (2) mermaid 뷰는 도메인마다 상한이 걸리는데, 상한이 버린 것은 문서 안에서 공시된다 — 아래의 drawn/in-scope/total 줄이 그 계수다. (3) join 그래프의 노드는 파일이 아니라 (source, side, kind, key) 관계다. 노드가 HTTP 라우트와 DB 테이블인 그림이 파일 그래프와 같은 모양일 수는 없다.

전체 의존 그래프 — 섬 열두 개

cosmograph 내보내기를 손대지 않고 그대로 그린 것 — 파일 4,457개와 트리 안 import 엣지 10,614개 전부, 상한 없음. 레포별로 뭉쳐 있고, 뭉치의 중심 쪽 노드일수록 트리 안 연결이 많다. 점 크기는 LOC 이고, 빨간 테두리 노드는 import 순환 안에 있다. 파일 경로는 마우스를 올리면 나온다.

레포파일엣지순환 안loc
grok-build2,5397,26301,577,731
the-algorithm1,2502,3616169,450
xai-sdk-python219440044,516
x-algorithm207305025,121
the-algorithm-ml91307,921
communitynotes72149029,214
xai-cookbook433806,151
grok-build-plugin-cc285006,136
grok-14502,300
plugin-marketplace4001,517

zzop 이 스스로 낸 공시를 그대로 옮기면: 이 그래프는 트리 안에서 풀린 엣지만 든다 — 수가 작다는 것은 "import 가 적다"가 아니라 "리졸버가 걸어 본 파일로 매핑하지 못한 import 가 많다"일 수도 있다.

join — 크로스레이어 조인(노드가 io 관계다)

edges 2/2 · unconsumedProvides 17/17 · unprovidedConsumes 12/12 · unresolvedConsumes 25/27 · externalConsumes 1/1 — 버킷당 상한 25. 빠진 2개는 문서가 스스로 공시한다

join 그래프 — mermaid 소스(아무 mermaid 뷰어에나 붙여 넣으면 된다)
zzop graph --domain join
flowchart LR
%% zzop graph — the cross-layer join as a mermaid flowchart (paste into any mermaid viewer).
%% tool: zzop/0.32.0 zzop-parser-typescript=typescript/swc_core-71.0.5/0.22.0+resource-query-v1+trpc-leaf-procedure-v1+dispatch-branch-symbol-v1+exported-signature-names-v1+function-spans-v1+same-file-const-prepend-v1+raw-sql-db-table-v1+same-file-url-binding-v1+same-file-fn-url-v1+retry-wrapper-binding-v1+generated-verb-member-v1+dispatch-verb-order-v1/40a3929e327ed6c4 zzop-parser-prisma=prisma/0.22.0/e304ee9aa867fdc5 zzop-parser-python-3=python3/ruff-0.0.4/0.24.0/a4828a9b00410327 zzop-parser-java-21=java21/tree-sitter-java-0.23.5/0.21.0/136c757da5a9ad98 zzop-parser-rust=rust/syn-2/0.21.0/600b025b2c0669ea zzop-parser-go=go/tree-sitter-go-0.25.0/0.21.1/357aa9602d00bce8 zzop-parser-sql=sql/0.21.0+dml-table-refs-v1/6777e8cbc6882f6d zzop-parser-csharp=csharp/tree-sitter-c-sharp-0.23.5/0.21.0/ab82e240fbcdc44b zzop-engine=10e497a2fad7e151
%% scope: (none — every source)
%% top: 25 drawn relations per bucket
%% relations drawn/inScope/total (and the call sites they aggregate):
%%   edges: 2/2/2 from 8 site(s)
%%   unconsumedProvides: 17/17/17 from 19 site(s)
%%   unprovidedConsumes: 12/12/12 from 76 site(s)
%%   unresolvedConsumes: 25/27/27 from 40 site(s)
%%   externalConsumes: 1/1/1 from 3 site(s)
%%   ambiguousConsumes: 0/0/0 from 0 site(s)
%% a node is (source, side, kind, key): CALL SITES ARE AGGREGATED and no file/line appears here — use `zzop facts` or `zzop cross` for per-site detail.
%% NOT rendered by this surface: crossLayerFindings (the drift/near-miss VERDICTS — findings about the join, not members of it), hostRekeyCounts, wildcardRoutePartitions (a route lifted OUT of the join for being an ANT pattern — it is in no bucket, so it is in no node either), warnings/configWarnings/disclosure.
  subgraph s0["communitynotes"]
    n0(["unresolved · http chat_completions_url"]):::unresolved
  end
  subgraph s1["grok-1"]
    s1note["extracted no joinable io — invisible to the join (blindness, not an empty contract)"]:::note
  end
  subgraph s2["grok-build"]
    n1(["unprovided · db-table table:chunks"]):::unprovided
    n2(["unprovided · db-table table:chunks_fts"]):::unprovided
    n3(["unprovided · db-table table:chunks_vec"]):::unprovided
    n4(["unprovided · db-table table:chunks_vec_rowids"]):::unprovided
    n5(["unprovided · db-table table:meta"]):::unprovided
    n6(["unprovided · db-table table:session_docs"]):::unprovided
    n7(["unprovided · db-table table:session_docs_fts"]):::unprovided
    n8(["unprovided · db-table table:sqlite_schema"]):::unprovided
    n9(["unprovided · db-table table:threads"]):::unprovided
    n10(["unprovided · db-table table:worktrees"]):::unprovided
    n11(["unresolved · http #quot;x-grok-context-window#quot;"]):::unresolved
    n12(["unresolved · http #quot;x-grok-max-completion-tokens#quot;"]):::unresolved
    n13(["unresolved · http #quot;x-models-etag#quot;"]):::unresolved
    n14(["unresolved · http #quot;x-should-retry#quot;"]):::unresolved
    n15(["unresolved · http &download_resp.signed_url"]):::unresolved
    n16(["unresolved · http &poll_url"]):::unresolved
    n17(["unresolved · http &resp.download_url"]):::unresolved
    n18(["unresolved · http &start_url"]):::unresolved
    n19(["unresolved · http &url"]):::unresolved
    n20(["unresolved · http AUTHORIZATION"]):::unresolved
    n21(["unresolved · http HeaderName::from_static(#quot;x-api-key#quot;)"]):::unresolved
    n22(["unresolved · http current_url.as_str()"]):::unresolved
    n23(["unresolved · http format!(#quot;{url}/control/resize#quot;)"]):::unresolved
    n24(["unresolved · http format!(#quot;{url}/control/send#quot;)"]):::unresolved
    n25(["unresolved · http format!(#quot;{url}/control/stop#quot;)"]):::unresolved
    n26(["unresolved · http format!(#quot;{url}/query/cursor#quot;)"]):::unresolved
    n27(["unresolved · http format!(#quot;{url}/query/screen#quot;)"]):::unresolved
    n28(["unresolved · http format!(#quot;{url}/query/status#quot;)"]):::unresolved
    n29(["unresolved · http format!(#quot;{url}/wait#quot;)"]):::unresolved
    n30(["unresolved · http reqwest::header::RETRY_AFTER"]):::unresolved
    n31(["unresolved · http signed_url"]):::unresolved
    n32(["unresolved · http url"]):::unresolved
    n33["unconsumed · http GET /callback"]:::unconsumed
    n34["unconsumed · http GET /logs"]:::unconsumed
    n35["unconsumed · http GET /query/cursor"]:::unconsumed
    n36["unconsumed · http GET /query/screen"]:::unconsumed
    n37["unconsumed · http GET /query/scrollback"]:::unconsumed
    n38["unconsumed · http GET /query/status"]:::unconsumed
    n39["unconsumed · http GET /ready"]:::unconsumed
    n40["unconsumed · http GET /statusz"]:::unconsumed
    n41["unconsumed · http GET /wait"]:::unconsumed
    n42["unconsumed · http GET /ws"]:::unconsumed
    n43["unconsumed · http POST /control/resize"]:::unconsumed
    n44["unconsumed · http POST /control/send"]:::unconsumed
    n45["unconsumed · http POST /control/stop"]:::unconsumed
    n46["unconsumed · http POST /v1/storage"]:::unconsumed
  end
  subgraph s3["grok-build-plugin-cc"]
    s3note["extracted no joinable io — invisible to the join (blindness, not an empty contract)"]:::note
  end
  subgraph s4["grok-prompts"]
    s4note["extracted no joinable io — invisible to the join (blindness, not an empty contract)"]:::note
  end
  subgraph s5["plugin-marketplace"]
    s5note["extracted no joinable io — invisible to the join (blindness, not an empty contract)"]:::note
  end
  subgraph s6["the-algorithm"]
    n47["unconsumed · db-table table:twttr-recos-ml-prod.realgraph.candidates_sampled"]:::unconsumed
    n48["unconsumed · db-table table:twttr-recos-ml-prod.realgraph.labeled_candidates$table_suffix$"]:::unconsumed
  end
  subgraph s7["the-algorithm-ml"]
    s7note["extracted no joinable io — invisible to the join (blindness, not an empty contract)"]:::note
  end
  subgraph s8["x-algorithm"]
    s8note["extracted no joinable io — invisible to the join (blindness, not an empty contract)"]:::note
  end
  subgraph s9["xai-cookbook"]
    n49(["unprovided · http DELETE /sessions/{}"]):::unprovided
    n50(["linked · http GET /health"]):::linked
    n51(["linked · http POST /session"]):::linked
    n52(["unprovided · http POST /sessions"]):::unprovided
    n53(["external · http POST https://api.x.ai/v1/realtime/client_secrets"]):::external
    n54(["unresolved · http SESSION_REQUEST_URL"]):::unresolved
    n55["unconsumed · http GET /"]:::unconsumed
    n56["linked · http GET /health"]:::linked
    n57["linked · http POST /session"]:::linked
  end
  subgraph s10["xai-proto"]
    s10note["extracted no joinable io — invisible to the join (blindness, not an empty contract)"]:::note
  end
  subgraph s11["xai-sdk-python"]
    n58(["unresolved · http url"]):::unresolved
  end
  n50 -. "low confidence: generic path shared by many services (health/ping/metrics/status/login)" .-> n56
  n51 --> n57
  disclosure0["TRUNCATED — unresolvedConsumes 25/27 (drawn/inScope relations). Raise --top (now 25) or narrow --scope; `zzop facts` is uncapped."]:::note
  classDef linked fill:#e6f4ea,stroke:#1e7d32,color:#111
  classDef candidate fill:#f1f8e9,stroke:#558b2f,color:#111
  classDef unconsumed fill:#fff4e5,stroke:#b26a00,color:#111
  classDef unprovided fill:#fdecea,stroke:#c62828,color:#111
  classDef ambiguous fill:#f3e5f5,stroke:#7b1fa2,color:#111
  classDef unresolved fill:#eceff1,stroke:#546e7a,color:#111
  classDef external fill:#e8eaf6,stroke:#3949ab,color:#111
  classDef note fill:#ffffff,stroke:#111,color:#111,stroke-dasharray:4 3

zzop 의 --domain join 출력 그대로다. 그 출력의 헤더가 스스로 "mermaid 렌더러에 붙여 넣으라"고 적는다.

설정 하나로 레포 열두 개를 조인했다. 크로스레이어 엣지는 둘이 실제로 붙고(grok-build 계열 안에서), 나머지는 버킷으로 분류된다 — grok-build 의 SQLite 테이블 열 개는 이 코퍼스에 제공자가 없고(unprovided), 동적 URL 은 못 푼 채로 남는다. grok-1 은 조용히 빠지는 대신 "조인이 못 보는 트리(공시된 실명)" 노드를 받는다.

dep — import 그래프(mermaid 뷰, --top 40 노드)

nodes 40/4457 · edges 63/10614 · cycles 6 — 40 은 읽히게 하려는 상한이고, 전체는 위의 캔버스다

dep 그래프 — mermaid 소스(아무 mermaid 뷰어에나 붙여 넣으면 된다)
zzop graph --domain dep
%% zzop graph --domain dep — file import graph
%% nodes: drawn 40 / in-scope 4457 / total 4457 | edges: drawn 63 / total 10614 | cycles reported: 6
%% node cap --top 40
flowchart LR
  f0["crates/codegen/xai-acp-lib/src/lib.rs"]
  f1["crates/codegen/xai-grok-pager-pty-harness/src/lib.rs"]
  f2["crates/codegen/xai-grok-pager/src/app/actions.rs"]
  f3["crates/codegen/xai-grok-pager/src/app/agent.rs"]
  f4["crates/codegen/xai-grok-pager/src/app/agent_view/mod.rs"]
  f5["crates/codegen/xai-grok-pager/src/app/app_view.rs"]
  f6["crates/codegen/xai-grok-pager/src/app/mod.rs"]
  f7["crates/codegen/xai-grok-pager/src/lib.rs"]
  f8["crates/codegen/xai-grok-pager/src/scrollback/block.rs"]
  f9["crates/codegen/xai-grok-pager/src/slash/command.rs"]
  f10["crates/codegen/xai-grok-pager/src/slash/commands/mod.rs"]
  f11["crates/codegen/xai-grok-pager/src/views/mod.rs"]
  f12["crates/codegen/xai-grok-pager/tests/pty_e2e/common.rs"]
  f13["crates/codegen/xai-grok-sampling-types/src/lib.rs"]
  f14["crates/codegen/xai-grok-shell/src/agent/config.rs"]
  f15["crates/codegen/xai-grok-shell/src/agent/mod.rs"]
  f16["crates/codegen/xai-grok-shell/src/agent/mvp_agent/mod.rs"]
  f17["crates/codegen/xai-grok-shell/src/auth/mod.rs"]
  f18["crates/codegen/xai-grok-shell/src/extensions/mod.rs"]
  f19["crates/codegen/xai-grok-shell/src/lib.rs"]
  f20["crates/codegen/xai-grok-shell/src/session/acp_session.rs"]
  f21["crates/codegen/xai-grok-shell/src/session/mod.rs"]
  f22["crates/codegen/xai-grok-shell/src/session/persistence.rs"]
  f23["crates/codegen/xai-grok-shell/src/session/storage/mod.rs"]
  f24["crates/codegen/xai-grok-test-support/src/lib.rs"]
  f25["crates/codegen/xai-grok-tools/src/types/output.rs"]
  f26["crates/codegen/xai-grok-tools/src/types/resources.rs"]
  f27["crates/codegen/xai-grok-tools/src/types/tool.rs"]
  f28["crates/common/xai-tool-protocol/src/lib.rs"]
  f29["src/java/com/twitter/search/common/schema/base/ImmutableSchemaInterface.java"]
  f30["src/java/com/twitter/search/common/schema/base/Schema.java"]
  f31["src/java/com/twitter/search/common/schema/earlybird/EarlybirdCluster.java"]
  f32["src/java/com/twitter/search/common/schema/earlybird/EarlybirdFieldConstants.java"]
  f33["src/java/com/twitter/search/core/earlybird/index/EarlybirdIndexSegmentAtomicReader.java"]
  f34{{"src/java/com/twitter/search/earlybird/EarlybirdSearcher.java"}}
  f35["src/java/com/twitter/search/earlybird/common/config/EarlybirdConfig.java"]
  f36["src/java/com/twitter/search/earlybird/factory/EarlybirdWireModule.java"]
  f37["src/java/com/twitter/search/earlybird_root/common/EarlybirdRequestContext.java"]
  f38["src/xai_sdk/__init__.py"]
  f39["src/xai_sdk/proto/__init__.py"]
  f2 --> f3
  f3 --> f0
  f4 --> f2
  f4 --> f3
  f4 --> f5
  f4 --> f7
  f5 --> f0
  f5 --> f2
  f5 --> f3
  f5 --> f4
  f5 --> f6
  f5 --> f7
  f6 --> f2
  f6 --> f3
  f6 --> f4
  f6 --> f5
  f6 --> f7
  f7 --> f6
  f7 --> f11
  f9 --> f2
  f10 --> f9
  f12 --> f1
  f14 --> f13
  f14 --> f17
  f15 --> f14
  f15 --> f16
  f16 --> f0
  f16 --> f13
  f16 --> f14
  f16 --> f17
  f16 --> f21
  f16 --> f22
  f18 --> f21
  f19 --> f15
  f19 --> f17
  f19 --> f18
  f19 --> f21
  f20 --> f0
  f20 --> f13
  f20 --> f17
  f20 --> f21
  f20 --> f22
  f20 --> f23
  f20 --> f25
  f21 --> f20
  f21 --> f22
  f21 --> f23
  f22 --> f0
  f22 --> f13
  f22 --> f23
  f23 --> f13
  f23 --> f22
  f27 --> f25
  f27 --> f26
  f33 --> f30
  f34 --> f29
  f34 --> f30
  f34 --> f31
  f34 --> f32
  f34 --> f35
  f36 --> f31
  f36 --> f32
  f36 --> f35
  zzopNote["PARTIAL VIEW: 40 of 4457 files drawn (4417 dropped by --top 40), 63 of 10614 edges. An edge whose other end was dropped is not drawn. Use zzop facts for the uncapped graph."]
  zzopCycles["6 circular finding(s) — files in a cycle are drawn as hexagons with thick arrows"]

zzop 의 --domain dep 출력 그대로다. 그 출력의 헤더가 스스로 "mermaid 렌더러에 붙여 넣으라"고 적는다.

risk — 허브와 추출 이음새

hubs 12/119 · seams 12/30 — 종류별 상한 12

risk 그래프 — mermaid 소스(아무 mermaid 뷰어에나 붙여 넣으면 된다)
zzop graph --domain risk
%% zzop graph --domain risk — blast-radius hubs and extraction seams
%% hubs: drawn 12 / in-scope 119 / total 119 | seams: drawn 12 / in-scope 30 / total 30
%% per-kind cap --top 12
%% NOT drawn: the 15 structural health scores (12 tree(s) computed them). They are a table of numbers, not a graph; a flowchart of them would be worse than the table. The composite rides `zzop analyze`'s architecture.pain (with painMeasuredWeight/painTotalWeight); the full table rides the direct zzop-facade output and no CLI or MCP reply.
flowchart TD
  s0[["communitynotes::scoring<br/>52 files, 0 boundary edges"]]
  s1[["communitynotes::collaborative-note-generator<br/>6 files, 0 boundary edges"]]
  s2[["communitynotes::template-api-note-writer<br/>11 files, 0 boundary edges"]]
  s3[["grok-1::(root)<br/>4 files, 0 boundary edges"]]
  s4[["grok-build::crates<br/>2462 files, 12 boundary edges"]]
  s5[["grok-build::third_party<br/>66 files, 1 boundary edges"]]
  s6[["grok-build::prod<br/>11 files, 11 boundary edges"]]
  s7[["grok-build-plugin-cc::plugins<br/>14 files, 0 boundary edges"]]
  s8[["plugin-marketplace::scripts<br/>4 files, 0 boundary edges"]]
  s9[["the-algorithm-ml::metrics<br/>4 files, 0 boundary edges"]]
  s10[["the-algorithm-ml::common<br/>16 files, 0 boundary edges"]]
  s11[["the-algorithm-ml::core<br/>16 files, 0 boundary edges"]]
  h0[["communitynotes::scoring/src/scoring/constants.py<br/>blast 42 · 1330 loc"]]
  h1[["communitynotes::scoring/src/scoring/pflip_plus_model.py<br/>blast 23 · 1965 loc"]]
  h2[["communitynotes::scoring/src/scoring/pandas_utils.py<br/>blast 26 · 734 loc"]]
  h3[["communitynotes::scoring/src/scoring/scoring_rules.py<br/>blast 22 · 1764 loc"]]
  h4[["communitynotes::scoring/src/scoring/process_data.py<br/>blast 17 · 1063 loc"]]
  h5[["communitynotes::scoring/src/scoring/matrix_factorization/matrix_factorization.py<br/>blast 18 · 719 loc"]]
  h6[["communitynotes::scoring/src/scoring/explanation_tags.py<br/>blast 23 · 152 loc"]]
  h7[["communitynotes::scoring/src/scoring/note_ratings.py<br/>blast 17 · 902 loc"]]
  h8[["communitynotes::scoring/src/scoring/reputation_matrix_factorization/reputation_matrix_factorization.py<br/>blast 18 · 535 loc"]]
  h9[["communitynotes::scoring/src/scoring/mf_base_scorer.py<br/>blast 15 · 1556 loc"]]
  h10[["communitynotes::scoring/src/scoring/note_status_history.py<br/>blast 18 · 362 loc"]]
  h11[["communitynotes::scoring/src/scoring/enums.py<br/>blast 26 · 54 loc"]]
  s0 --> h0
  s0 --> h1
  s0 --> h2
  s0 --> h3
  s0 --> h4
  s0 --> h5
  s0 --> h6
  s0 --> h7
  s0 --> h8
  s0 --> h9
  s0 --> h10
  s0 --> h11
  zzopNote["PARTIAL VIEW: 12 of 119 hubs and 12 of 30 seams drawn (125 dropped by --top 12). Arrows mean CONTAINMENT, not imports — use --domain dep for import direction."]

zzop 의 --domain risk 출력 그대로다. 그 출력의 헤더가 스스로 "mermaid 렌더러에 붙여 넣으라"고 적는다.

posture — 변경 가능한 공격 표면과 그 가드 상태

mutating routes 6/6 · reported no-auth-evidence 3 — 깃발 모양은 가드가 없다는 뜻이고, 상자는 "가드됐거나 면제"이지 가드됐음이 증명된 것이 아니다 (룰은 자기가 판정할 수 없는 것에 대해 침묵한다)

posture 그래프 — mermaid 소스(아무 mermaid 뷰어에나 붙여 넣으면 된다)
zzop graph --domain posture
%% zzop graph --domain posture — mutating attack surface and its guard status
%% mutating routes: drawn 6 / total 6 | reported unguarded: 3 | per-tree cap --top 20
%% NOT drawn: read routes (a GET is not unguarded, it is a read) and non-http io. Guard status is this run's `mutating-route-no-auth` verdict, never re-derived here.
flowchart LR
  subgraph grok_build
    r0>"POST /control/resize<br/>crates/codegen/ptyctl/src/server.rs"]
    r1>"POST /control/send<br/>crates/codegen/ptyctl/src/server.rs"]
    r2>"POST /control/stop<br/>crates/codegen/ptyctl/src/server.rs"]
    r3["POST /v1/storage<br/>crates/codegen/xai-grok-shell/src/agent/storage_client_tests.rs"]
  end
  subgraph xai_cookbook
    r4["POST /session<br/>voice-examples/agent/web/openai/backend-python/main.py"]
    r5["POST /session<br/>voice-examples/agent/web/xai/backend-python/main.py"]
  end
  zzopLegend["flag shape = reported unguarded (3). box = GUARDED-OR-EXEMPT, not proven guarded: the rule also stays silent on routes it cannot judge (uncovered language, unresolved handler, test file, auth-acquisition path). Absence of a finding is not proof of a guard."]

zzop 의 --domain posture 출력 그대로다. 그 출력의 헤더가 스스로 "mermaid 렌더러에 붙여 넣으라"고 적는다.

깃발이 붙은 라우트 셋은 전부 grok-build 의 ptyctl — 로컬 PTY 제어용 개발 서버다. zzop 이 보고하는 것은 "인증 증거가 없다"이지 "취약하다"가 아니다.

cochange — git 이력에서 같이 바뀌는 파일들

edges 30/275 — 파일을 2~25개 건드린 커밋만 짝을 만들고, 파일마다 가장 강한 상대만 남긴다. 표본이지 레포 전체 합이 아니다

cochange 그래프 — mermaid 소스(아무 mermaid 뷰어에나 붙여 넣으면 된다)
zzop graph --domain cochange
%% zzop graph --domain cochange — files that change together in git history
%% edges: drawn 30 / in-scope 275 / total 275 | cap --top 30
%% A SAMPLE, never a repository total: commits touching fewer than 2 or more than 25 files form no pair at all, and each file keeps only its strongest partners — the tail is dropped, not summed. Read an edge as "among the strongest measured ties", never as "they always change together".
%% Edges are UNDIRECTED and are NOT imports. An import edge is read from source; this one is read from history and says nothing about why. For imports use --domain dep.
flowchart LR
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__README_md["grok-1::README.md"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__checkpoint_py["grok-1::checkpoint.py"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__checkpoints_README_md["grok-1::checkpoints/README.md"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__model_py["grok-1::model.py"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__pyproject_toml["grok-1::pyproject.toml"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__requirements_txt["grok-1::requirements.txt"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__run_py["grok-1::run.py"]
  cc_grok_1___gitignore["grok-1::.gitignore"] --- |1| cc_grok_1__runners_py["grok-1::runners.py"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__README_md["grok-1::README.md"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__checkpoint_py["grok-1::checkpoint.py"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__checkpoints_README_md["grok-1::checkpoints/README.md"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__model_py["grok-1::model.py"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__pyproject_toml["grok-1::pyproject.toml"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__requirements_txt["grok-1::requirements.txt"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__run_py["grok-1::run.py"]
  cc_grok_1__CODE_OF_CONDUCT_md["grok-1::CODE_OF_CONDUCT.md"] --- |1| cc_grok_1__runners_py["grok-1::runners.py"]
  cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"] --- |1| cc_grok_1__README_md["grok-1::README.md"]
  cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"] --- |1| cc_grok_1__checkpoint_py["grok-1::checkpoint.py"]
  cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"] --- |1| cc_grok_1__checkpoints_README_md["grok-1::checkpoints/README.md"]
  cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"] --- |1| cc_grok_1__model_py["grok-1::model.py"]
  cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"] --- |1| cc_grok_1__pyproject_toml["grok-1::pyproject.toml"]
  cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"] --- |1| cc_grok_1__requirements_txt["grok-1::requirements.txt"]
  cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"] --- |1| cc_grok_1__run_py["grok-1::run.py"]
  cc_grok_1__LICENSE_txt["grok-1::LICENSE.txt"] --- |1| cc_grok_1__runners_py["grok-1::runners.py"]
  cc_grok_1__README_md["grok-1::README.md"] --- |1| cc_grok_1__checkpoint_py["grok-1::checkpoint.py"]
  cc_grok_1__README_md["grok-1::README.md"] --- |1| cc_grok_1__checkpoints_README_md["grok-1::checkpoints/README.md"]
  cc_grok_1__README_md["grok-1::README.md"] --- |1| cc_grok_1__model_py["grok-1::model.py"]
  zzopNote["PARTIAL VIEW: 30 of 275 co-change pair(s) drawn (245 dropped by --top 30). Raise --top for the rest — this picture and a direct zzop-facade embedding are the only surfaces that carry these pairs."]

zzop 의 --domain cochange 출력 그대로다. 그 출력의 헤더가 스스로 "mermaid 렌더러에 붙여 넣으라"고 적는다.

재현하기

레포 열두 개를 클론한 뒤 도메인마다 커맨드 하나씩: zzop graph --config <config> --domain join|dep|risk|posture|cochange. 전체 그래프는 cosmograph 레인이다 — zzop graph --config <config> --domain dep --format cosmograph-nodes--format cosmograph-links 가 이 캔버스가 읽는 NDJSON 표 두 개를 낸다. 발견 계수(레포 네 개에 걸쳐 171건)는 zzop cross --config <config> 가 낸 것이고, 그 정직-공시 관점은 개요에 있다.