| name | repo-graph |
| description | Build or refresh the Hybrid Platforms portfolio repository graph. Reads hybrid-platforms-inputs/{openshift,operator-catalog,services} CSV inventories (ansible/ excluded by default), cross-references every repo against analysis-results/findings/ coverage (audit/triage/threat-model + severity), and emits repo-graph.{json,dot,gexf,html} + repo-graph-stats.md into analysis-results/graph/. Use when asked to "build the repo graph", "update the portfolio graph", "map repos to findings", "which repos have no coverage", or "what ships kube-rbac-proxy". |
| argument-hint | [--inputs DIR] [--findings DIR] [--out DIR] [--exclude SEGMENT]... [--date YYYY-MM-DD] |
| allowed-tools | ["Bash(python3:*)","Bash(ls:*)","Bash(jq:*)","Bash(open:*)","Read","Write"] |
repo-graph
Build a directed graph of the entire Hybrid Platforms source-code portfolio
and overlay security-findings coverage on it. One command, five artefacts.
What it produces
Written to --out (default ../analysis-results/graph/):
| File | Purpose |
|---|
repo-graph.json | Full-fidelity {nodes,edges,stats}. Canonical machine-readable output. |
repo-graph.dot | GraphViz source. Render with dot -Tsvg -Ksfdp (large — expect minutes). |
repo-graph.gexf | Gephi import for interactive layout / community detection. |
repo-graph.html | Self-contained vis-network page: pan/zoom, text filter, segment-focus dropdown, double-click-to-open. Product-version and category nodes are collapsed into tooltips so the ~10k-node graph stays interactive. |
repo-graph-stats.md | Node/edge counts, coverage %, top-20 hub repos by degree. |
Node types
segment → release / product → product-version → category → repo → findings
plus owner-team → repo (dashed).
Repo nodes are coloured by max triaged severity across all linked findings
dirs (CRITICAL red · HIGH orange · MEDIUM yellow · LOW green · none
grey) and carry attrs.tp_total, attrs.findings_dirs,
attrs.findings ∈ {audit,triage,tm,none,…}.
Edge relations
| rel | meaning |
|---|
contains | hierarchy: segment→release/product, product→version, release/version→category |
ships | inventory row: this release/version/category/product ships this repo |
owned-by | owners.csv: this Owner Team owns this repo |
has-findings | this repo has an analysis-results/findings/<bucket>/<slug>/ directory |
Procedure
$ARGUMENTS are passed straight through to build_repo_graph.py. From the
ai-security-harness/ checkout:
-
Resolve paths. If the user supplied --inputs / --findings / --out,
honour them; otherwise use defaults relative to the harness checkout
(../hybrid-platforms-inputs, ../analysis-results/findings,
../analysis-results/graph).
-
Run the builder:
python3 harnessing/repo-graph/build_repo_graph.py $ARGUMENTS
The script indexes every findings/*/*/name-security-audit.json for
metadata.repository (preferring the sibling *-findings-current.json
cumulative report from the track-findings skill when present — findings
nodes then carry dispositions, resolved_findings, open_findings,
and false_positives attributes, and stats.md reports ledger coverage),
ingests all inventory CSVs from the non-excluded segments, links repos
to findings (exact URL match, then a guarded name-only fallback that
refuses dirs whose recorded URL points at a different repo), and writes
all five outputs.
-
Report the summary. Read repo-graph-stats.md back to the user and call
out:
- node/edge totals and coverage %
- the top-3 hub repos
repos_without_findings count — these are gap targets for
secure-code-audit.
-
If the user asked to "open" or "view" the graph, run
open <out>/repo-graph.html.
Answering questions from the graph
Once repo-graph.json exists, common queries:
# repos with no findings coverage
jq -r '.nodes[]|select(.type=="repo" and .attrs.findings=="none")|.label' repo-graph.json
# every product/release that ships a given repo
jq -r --arg r github.com/openshift/kube-rbac-proxy \
'.edges[]|select(.rel=="ships" and .to=="repo:"+$r)|.from' repo-graph.json
# repos owned by a team, sorted by TP
jq -r --arg t "OpenShift Storage" \
'[.edges[]|select(.rel=="owned-by" and .from=="owner-team:"+$t)|.to] as $r
| .nodes[]|select(.id as $i|$r|index($i))
| "\(.attrs.tp_total // 0)\t\(.label)"' repo-graph.json | sort -rn
Excluding segments
--exclude is repeatable; default is ansible. To also drop the operator
catalog: --exclude ansible --exclude operator-catalog.