Docs / user-guide · Edit on GitHub

rgctl User Guide

End-to-end guide for installing rgctl, indexing an in-tree example, and querying a codebase from the command line. Sample outputs target rgctl-tests/ecommerce-java. Runnable examples are backed by scenarios under user-guide/scenarios/ (see change docs-agent-first-diataxis).

Concepts: Introduction. Agents: AGENTS.md. JSON fields: json-api.md.

How this guide is organized

ZoneSectionsRole
Tutorial spine§1–4, §16Install → fixture → discover → recommended workflow
How-to§5–14Flags and feature commands with sample output
Optional UI§15serve / dashboard — nice-to-have, not required for agents
Reference§17–18Command cheat sheet + troubleshooting

Table of contents

  1. Installation
  2. Add rgctl to your PATH
  3. Example project: ecommerce-java
  4. Index with discover
  5. Global CLI flags
  6. Query the graph with GQL
  7. Blast radius (change impact)
  8. Program slicing and taint
  9. Inspect CFG / PDG / dominance
  10. Hybrid CPG (cpg)
  11. Graph metrics
  12. Semantic search
  13. Export graph projections
  14. CI policy check
  15. HTTP server (serve) — optional
  16. Recommended workflow
  17. Command reference
  18. Troubleshooting

1. Installation

Standalone guide: Installation covers prerequisites, CLI and HTTP modes, agent skill setup, upgrading, and troubleshooting.

Option A — GitHub release (recommended)

Pre-built binaries are published on the project Releases page:

https://github.com/sshaaf/rgctl/releases

  1. Open the latest release.

  2. Download the archive for your platform:

    PlatformTypical asset name
    macOS (Apple Silicon)rgctl-*-aarch64-apple-darwin.tar.gz
    macOS (Intel)rgctl-*-x86_64-apple-darwin.tar.gz
    Linux (x86_64)rgctl-*-x86_64-unknown-linux-gnu.tar.gz
    Windowsrgctl-*-x86_64-pc-windows-msvc.zip
  3. Extract the archive. You should get a single rgctl executable (plus rgctl.exe on Windows).

# macOS / Linux example
tar -xzf rgctl-*-aarch64-apple-darwin.tar.gz
./rgctl --version
# Windows example (PowerShell)
Expand-Archive rgctl-*-x86_64-pc-windows-msvc.zip -DestinationPath .
.\rgctl.exe --version

If no release is published yet for your platform, use Option B.

Option B — Build from source

Requires Rust 1.88+ (Edition 2024; rustup.rs).

git clone https://github.com/sshaaf/rgctl.git
cd rgctl
# Optional: code-daemon ONNX weights (~206 MB via Git LFS) if you use
# `semantic index --embedder code-daemon`. Skip for the default vocab embedder.
git lfs pull
cargo build --release --bin rgctl
./target/release/rgctl --version

All nine Tier 1 languages (Rust, Python, JavaScript, TypeScript, Go, Java, C#, C, C++) are always included in the binary.

Install the agent skill

After rgctl is on your PATH, install the bundled rgctl skill into the target repository (the same root you pass to discover via -r / --repo, or the current directory):

rgctl install --skill
# or, from another cwd:
rgctl -r /path/to/repo install --skill

That writes:

  • <repo>/.claude/skills/rgctl/ (Claude Code)
  • <repo>/.cursor/skills/rgctl/ (Cursor)

Limit hosts with --host claude or --host cursor (default is all). Identical files are left unchanged. If a dest file differs, the command exits 1 unless you pass --force. Re-run install --skill --force after upgrading rgctl to refresh the project copy. Manual copy of skills/rgctl/ remains a fallback if you have a git checkout.


2. Add rgctl to your PATH

Pick one approach for your shell.

macOS / Linux — user-local install

mkdir -p ~/.local/bin
cp /path/to/rgctl ~/.local/bin/
chmod +x ~/.local/bin/rgctl

Add to ~/.zshrc or ~/.bashrc:

export PATH="$HOME/.local/bin:$PATH"

Reload and verify:

source ~/.zshrc   # or ~/.bashrc
rgctl --version

macOS / Linux — system-wide (optional)

sudo cp /path/to/rgctl /usr/local/bin/
rgctl --version

Windows

  1. Copy rgctl.exe to a folder such as C:\Tools\rgctl\.
  2. Open Settings → System → About → Advanced system settings → Environment Variables.
  3. Under User variables, edit Path and add C:\Tools\rgctl.
  4. Open a new terminal:
rgctl --version

Per-project usage (no PATH change)

Pass the full path or use a repo-local alias:

alias rgctl='/path/to/rgctl'

3. Example project: ecommerce-java

This guide uses the in-tree Spring Boot fixture shipped with rgctl:

rgctl-tests/ecommerce-java

It implements the same e-commerce domain as the other ecommerce-* fixtures (cart, orders, products, auth), plus a CoolStore-compatible dual API under /services/* (additive next to /api/*). No separate clone is required when you have the rgctl repo.

# From the rgctl repository root
export REPO="$PWD/rgctl-tests/ecommerce-java"
cd "$REPO"

Layout (simplified):

ecommerce-java/
├── pom.xml
└── src/main/java/com/example/ecommerce/
    ├── controller/     # /api/* — CartController, OrderController, ProductController, …
    ├── service/        # CartService, OrderService, ProductService, …
    ├── entity/         # Cart, Order, Product, User, …
    ├── repository/     # Spring Data JPA repos
    ├── security/       # JWT filter / token provider
    └── coolstore/      # /services/* — CoolStore cart pricing + orders (in-memory)
        ├── rest/       # ProductEndpoint, CartEndpoint, OrderEndpoint
        ├── service/    # ShoppingCartService, PromoService, ShippingService, …
        └── model/      # ShoppingCart, ShoppingCartItem, CatalogProduct, …

Dual REST surface (same contract on every ecommerce-* language):

SurfaceRole
/api/*JWT e-commerce API (auth, categories, cart ownership, reviews, …)
/services/*CoolStore-style products / cart / checkout / orders (cartId session carts)

ShoppingCartService.priceShoppingCart mutates cart totals (promo + shipping) — the Layer F target for cpg mutations --type ShoppingCart. Full route table: rgctl-tests/README.md.

Sibling fixtures (ecommerce-python, ecommerce-rust, ecommerce-c, …) share both REST shapes.

All commands below assume REPO points at ecommerce-java, or that you run from inside that directory and use . instead of "$REPO".

Sample outputs in this guide were captured on a laptop with a release build; absolute paths are shortened to …/ecommerce-java/… for readability. Counts may differ slightly across versions.


4. Index with discover

discover scans source files, builds the knowledge graph, runs analytics (complexity, communities, centrality, blast-radius scoring), and writes artifacts under {repo}/.rgctl/.

Choosing the discover target (common pitfall):

CommandIndexes
cd repo && rgctl discover .Recommended — current directory
rgctl -r /path/to/repo discoverRepo at /path/to/repo
rgctl discover /path/to/repoSame (absolute path positional)
rgctl -r /path/to/repo discover .Shell cwd only-r is ignored when . is present

Full table: Discovering and indexing.

Built-in registry includes markdown (rgctl-lang-markdown): .md and .mdx are indexed by default (headings, links, code blocks, frontmatter). See markdown-context.md. Use -l markdown or -l markdown,java to limit languages.

Full pipeline (--full)

rgctl discover PATH --full prints an execution plan, runs a basic discover (queryable snapshot), reports that the initial discover is complete, then continues in the same process with --with-cfg --with-dashboard --with-harmonic, then semantic index. Other terminals can gql after stage 1. Does not imply taint or secret scanning.

rgctl discover . --full

Status is written to .rgctl/pipeline_status.json. A second --full on unchanged sources skips fresh stages.

Fast index (default)

cd "$REPO"
rgctl discover . -l java -e target

Example output:

==> Analyzing: …/ecommerce-java/.
[✓] Indexed 51 files -> 518 nodes, 1122 edges (0.0s)
[✓] Detected 443 communities (modularity: 0.47)
[✓] Analyzed 187 functions (avg complexity: 1.0, 0 high, 0 medium)
[*] Top hotspot: findAll (PageRank: 0.0177)
[!] Found 48 circular dependencies
[✓] Analysis complete
[✓] Saved to .rgctl/ (0.1 MB total)
[✓] Completed in 0.0s (peak memory: 21 MB)

[i] Next steps:
   rgctl gql "MATCH (n:Function) RETURN n"  # Query the graph
   rgctl slice <file> --line <N> --variable <VAR>
   rgctl serve --open   # Dashboard + query API at http://127.0.0.1:8080

Typical runtime on this fixture: well under a second.

CI / automation — structured metrics on stdout:

rgctl -f json discover . -l java -e target | jq .
<!-- ug-scenario:04-discover-json -->

Example:

{
  "command": "discover",
  "metrics": {
    "duration_ms": "…",
    "edges_generated": 3293,
    "files_discovered": 65,
    "files_indexed": 65,
    "files_skipped": 0,
    "nodes_generated": 1088
  },
  "schema_version": 2
}
<!-- /ug-scenario:04-discover-json -->

Language and path filters

# Java only, skip Maven output
rgctl discover . -l java -e target

# Multiple languages (polyglot monorepo)
rgctl discover . -l java,typescript -e target,node_modules,dist

Default pipeline (always on)

Bare discover (no --with-*) always runs: index/extract → topology → community → complexity → PageRank/betweenness → dependency cycles → blast engine → persist analysis + snapshot.

Harmonic, dashboard, migration export, security, CFG/PDG, and discover-time taint are opt-in via the flags below.

Deeper analysis (opt-in)

FlagWhat it adds
--with-securitySecret scanning
--with-cfgPer-function CFG, dominators, PDG (archive under .rgctl/analysis/)
--with-taintDiscover-time taint into archive (implies CFG/PDG pass)
--with-harmonicHarmonic centrality (migration ranking)
--with-dashboardStatic dashboard bundle under .rgctl/dashboard/
--export-migration-hintsMigration roadmap JSON (alias: --export-migration-plan)
--with-kantraKonveyor Kantra rule evaluation + rules graph index (see below)
# CFG so inspect / slice have rich PDG context
rgctl discover . -l java -e target --with-cfg

# Full walkthrough set used for the samples below
rgctl discover . -l java -e target \
  --with-cfg --with-dashboard --with-harmonic --export-migration-hints

Example lines from that richer run:

[!] Deep analysis enabled (--with-cfg / --with-taint).
✓ Control flow analysis:
  CFG/PDG/Dominance: 178 functions analyzed
  Skipped: 9 functions (unsupported language or parse error)
[✓] Migration plan (Hybrid Default): 9 steps → …/ecommerce-java/./.rgctl/migration_plan.json
[✓] Dashboard: …/ecommerce-java/./.rgctl/dashboard/index.html

Use --with-cfg when you need inspect / slice overlays; add --with-taint for discover-time taint flows. On large monorepos (100k+ functions) expect minutes to hours.

Kantra migration rules (--with-kantra)

Evaluate Konveyor Kantra rules natively against the indexed graph and source cache — no Kantra CLI, LSP, or containers. Release binaries embed the Konveyor stable/java catalog (~2.6k rules); discover --with-kantra uses it by default.

# Default: embedded catalog + eval + index rules into graph.snapshot.bin
rgctl discover . -l java -e target --with-kantra

# Migration target filter (konveyor.io/target=<NAME> labels)
rgctl discover . -l java --with-kantra --kantra-target quarkus

# CI / fixtures: override with a local ruleset directory
rgctl discover . --with-kantra --kantra-rules tests/fixtures/kantra-rules

# Dev: local konveyor/rulesets checkout instead of embedded blob
rgctl discover . --with-kantra --kantra-catalog /path/to/rulesets/stable/java

# Index rules into graph only (skip eval)
rgctl discover . --with-kantra --kantra-index-only
FlagPurpose
--with-kantraRun Kantra eval and index KantraRule / KantraRuleset nodes into the session graph
--kantra-target NAMEEvaluate only rules labeled konveyor.io/target=NAME
--kantra-rules DIRSingle ruleset directory (ruleset.yaml + *.yaml); overrides embedded catalog
--kantra-catalog ROOTRulesets tree (e.g. stable/java); overrides embedded catalog
--kantra-index-onlySkip eval; still index catalog rules into the graph

Artifacts: .rgctl/kantra_findings.json (violations + skipped rules). Rule nodes are queryable via GQL after index:

rgctl gql "MATCH (r:KantraRule) RETURN r LIMIT 10"
rgctl gql 'MATCH (r:KantraRule) WHERE r.`konveyor.io/target` = '\''quarkus'\'' RETURN r'

Konveyor labels (konveyor.io/target, konveyor.io/source, …) are stored as node properties; use backtick-quoted property names in GQL WHERE clauses.

Source builds: full embedded catalog requires the git submodule — git submodule update --init crates/rgctl-kantra/assets/rulesets or ./scripts/init-kantra-rulesets.sh. Without it, the build falls back to tests/fixtures/kantra-rules/. See crates/rgctl-kantra/README.md.

Further reading: Kantra integration exploration · Architecture options (embedded catalog + rules graph) · JSON schema

Does not require --with-cfg. Unsupported rule providers and invalid regex patterns are skipped with reasons in skipped_rules. After full eval, VIOLATES edges link each KantraRule to matched code nodes (queryable via GQL).

Dashboard: discover --with-kantra --with-dashboard exports kantra_index.json and per-file violation shards. Open Migration Rules in the dashboard (category + Konveyor target filters, syntax-highlighted snippets). See Dashboard user guide — Migration Rules.

Verbose logging and stage profiling

rgctl discover . -v

With -v, discover emits a [profile] discover summary line (wall time, peak RSS, node count) and per-stage timings.

Extraction internals also emit populate_graph timing buckets in profile logs. These buckets are intentionally distinct (symbol_processing_secs, config_key_secs, relation_resolution_secs, config_usage_secs) and are measured independently to avoid double-counting when one bucket is empty.

RUST_LOG=info,profile=info rgctl discover . --with-cfg -v -l java -e target 2>&1 \
  | tee discover-profile.log
grep '\[profile\]' discover-profile.log

Example profile lines (ecommerce-java, --with-cfg):

[profile] discover summary wall_secs=0.14 index_secs=0.01 post_index_secs=0.09 \
  peak_rss_mb=27.0 functions=187 nodes=518 cfg=true security=false
[profile] stage stage="cfg_total" secs=0.030 pct_wall=21.0
[profile] stage stage="save_dashboard" secs=0.028 pct_wall=19.6
[profile] stage stage="index_extract" secs=0.012 pct_wall=8.1

Harmonic centrality is off by default — pass --with-harmonic when you need it for migration ranking. On kernel-scale graphs it adds ~30s wall and multi‑GB peak RSS.

See analysis-architecture.md and internal/profile.md for large-graph adaptive gating and cold-profile runs.

Legacy JSON graph (optional)

By default, rgctl writes a binary snapshot (graph.snapshot.bin). Legacy graph.db / graph.json are only written when requested:

rgctl discover . --write-json-graph

What discover creates

After a successful run, the layout below appears under {repo}/.rgctl/:

{repo}/.rgctl/
├── graph.snapshot.bin          # Columnar mmap graph (primary cache for queries)
├── content_store.bin           # Large markdown bodies / files (body_ref / blob_ref; Obsidian + doc semantic)
├── blast_engine.snapshot.bin   # Pre-built blast-radius engine
├── macro_call_index.db         # Blast-radius lookup cache (SQLite; not the graph)
├── macro_call_index.bin        # Same index in bincode (companion to .db)
├── analysis_results.bin        # Columnar analysis properties
├── kantra_findings.json        # With --with-kantra (violations + skipped rules)
├── file_hashes.json            # Incremental file tracker
├── migration_plan.json         # With --export-migration-hints
├── analysis/                   # Per-function CFG/PDG/taint (with --with-cfg / --with-taint)
│   └── cfg_pdg.archive.bin
└── dashboard/                  # Only with --with-dashboard
    ├── index.html
    ├── manifest.json
    ├── migration_plan.json
    └── graph_payload.bin

Query commands read graph.snapshot.bin when present. You do not need graph.db for normal CLI use.

Point every subsequent command at the same repo root you indexed:

export REPO="$PWD"   # after cd into the repo
# or pass -r on each command:
rgctl -r "$REPO" gql 'MATCH (n:Function) RETURN n LIMIT 5'

5. Global CLI flags

These apply to every subcommand:

FlagPurpose
-r, --repo PATHRepository root (default: current directory)
-d, --db PATHLegacy graph JSON path (default: .rgctl/graph.db)
-f, --format FORMATOutput: text, json, graphviz, mermaid
-o, --output FILEWrite command output to a file instead of stdout

Examples:

# JSON for scripting
rgctl -r "$REPO" -f json gql 'MATCH (n:Class) RETURN n LIMIT 10'

# Mermaid diagram to a file
rgctl -r "$REPO" -f mermaid -o checkout-cfg.mmd inspect checkout cfg

6. Query the graph with GQL

gql runs the graph query language against the indexed graph. Run discover first.

Inventory macros

rgctl -r "$REPO" gql --macro-name all_functions unused

Text mode prints one function name per line (count varies with fixture size). JSON is better for scripts:

rgctl -r "$REPO" -f json gql --macro-name all_functions unused | jq '.count'
<!-- ug-scenario:06-gql-all-functions -->
317
<!-- /ug-scenario:06-gql-all-functions -->

Exact name match

rgctl -r "$REPO" gql \
  "MATCH (n:Function) WHERE n.name = 'clearCart' RETURN n"
clearCart
clearCart

(There are two clearCart methods — service and controller.)

JSON shows file paths:

rgctl -r "$REPO" -f json gql \
  "MATCH (n:Function) WHERE n.name = 'clearCart' RETURN n" | jq '.rows'
[
  [
    {
      "binding": "n",
      "file": "…/service/CartService.java",
      "node": "clearCart",
      "type": "Function"
    }
  ],
  [
    {
      "binding": "n",
      "file": "…/controller/CartController.java",
      "node": "clearCart",
      "type": "Function"
    }
  ]
]

Classes

rgctl -r "$REPO" -f json gql \
  "MATCH (n:Class) WHERE n.name = 'CartService' RETURN n" | jq '.rows[0]'
[
  {
    "binding": "n",
    "file": "…/service/CartService.java",
    "node": "CartService",
    "type": "Class"
  }
]

Call relationships

Who calls clearCart?

rgctl -r "$REPO" gql \
  "MATCH (a:Function)-[:CALLS]->(b:Function) WHERE b.name = 'clearCart' RETURN a,b"
checkout -> clearCart
clearCart -> clearCart

JSON (trimmed):

{
  "count": 2,
  "rows": [
    [
      { "binding": "a", "node": "checkout", "file": "…/OrderService.java", "type": "Function" },
      { "binding": "b", "node": "clearCart", "file": "…/CartService.java", "type": "Function" }
    ]
  ],
  "schema_version": 1
}

Common node / edge types

  • Nodes: Function, Class, Interface, Module, File, Import, ConfigKey, …
  • Edges: CALLS, IMPORTS, CONTAINS, DEPENDS_ON, IMPLEMENTS, …

Named communities (analysis overlay)

discover runs label-propagation community detection and stores assignments in .rgctl/analysis_results.binnot as edges in the topology graph. gql joins that sidecar so you can list and filter communities:

Community detection uses behavioral edges (Calls, Uses, References) by default.
On mixed code + markdown repos, doc REFERENCES participate in the same community pass as code edges; this is expected behavior.

# Macro: list communities (id, heuristic label, member_count)
rgctl -r "$REPO" -f json gql --macro-name all_communities unused | jq '.rows[:3]'
[
  [
    {
      "binding": "c",
      "node": "ecommerce.service::checkout",
      "type": "Community",
      "label": "ecommerce.service::checkout",
      "community_id": 385,
      "member_count": 19,
      "file": null
    }
  ]
]
# Members of one community (use an id from the list above)
rgctl -r "$REPO" -f json gql \
  "MATCH (f:Function) WHERE f.community_id = '385' RETURN f LIMIT 10" | jq '.count'

# CLI helpers (same labels; --write refreshes analysis_results.bin)
rgctl -r "$REPO" communities list
rgctl -r "$REPO" communities label --write

Labels are heuristic (package path, top PageRank symbol, token majority, infrastructure hubs). They are for orientation — not ground-truth domain names. See community query & naming plan.

Virtual type :Community is query-only; there is no MEMBER_OF edge in the snapshot.


7. Blast radius (change impact)

blast-radius answers: “What breaks upstream if I change this symbol?”

Bare names are often ambiguous. Prefer FQN (Class::method):

rgctl -r "$REPO" blast-radius 'CartService::clearCart'
Blast radius for 'CartService::clearCart'
  Score: 25.1/100
  Direct callers: 1
  Impact zone: 1
  Callers: OrderService.checkout
  Impact: OrderService.checkout

Ambiguous bare name shows remediation:

rgctl -r "$REPO" blast-radius clearCart
Error: Symbol 'clearCart' is ambiguous. Found 2 matches.
UUID                                   | Class Context  | Source File Path
…                                      | CartService    | …/CartService.java
…                                      | CartController | …/CartController.java

Remediation: Refine your search query using a fully qualified namespace syntax:
  rgctl blast-radius "ClassName::clearCart"
  rgctl blast-radius "path/to/file.java::clearCart"

Symbol forms

FormExample
Bare namecheckout (fails if ambiguous)
FQNCartService::clearCart
UUIDnode id from GQL / blast JSON

Disambiguate with filters:

rgctl -r "$REPO" blast-radius clearCart --class CartService
rgctl -r "$REPO" blast-radius clearCart \
  --file src/main/java/com/example/ecommerce/service/CartService.java

Limit caller depth

rgctl -r "$REPO" blast-radius 'CartService::clearCart' --depth 1
rgctl -r "$REPO" blast-radius 'CartService::clearCart' --depth 5

Omit --depth for full transitive upstream closure.

JSON output

rgctl -r "$REPO" -f json blast-radius 'CartService::clearCart' \
  | jq '{score: .metrics.score, callers: .topology.direct_callers}'
<!-- ug-scenario:07-blast-clearCart -->
{
  "score": 25.05,
  "callers": [
    {
      "file_path": "…/OrderService.java",
      "fqn": "com.example.ecommerce.service.OrderService.checkout",
      "id": "…"
    }
  ]
}
<!-- /ug-scenario:07-blast-clearCart -->

Schema: json-api.md (blast-radius + field catalogs).

Statement-level slice hand-offs (slow)

rgctl -r "$REPO" blast-radius 'CartService::clearCart' --with-slices

Requires discover --with-cfg for rich PDG context.


8. Program slicing and taint

slice performs line-level backward or forward slicing on a source file. Paths may be absolute, cwd-relative, or relative to --repo. Run discover --with-cfg first so PDG data is available.

Backward slice

“What code influences this variable at this line?” — in OrderService.checkout, cart is assigned on line 52:

rgctl -r "$REPO" slice \
  src/main/java/com/example/ecommerce/service/OrderService.java \
  --line 52 \
  --variable cart \
  --function checkout
Backward slice for src/main/java/com/example/ecommerce/service/OrderService.java:52 (variable: cart)
Reduction: 92.3%
  52

A denser example from CartService.addItem (line 53, local item):

rgctl -r "$REPO" slice \
  src/main/java/com/example/ecommerce/service/CartService.java \
  --line 53 \
  --variable item \
  --function addItem
Backward slice for src/main/java/com/example/ecommerce/service/CartService.java:53 (variable: item)
Reduction: 92.9%
  53

Forward slice

rgctl -r "$REPO" slice \
  src/main/java/com/example/ecommerce/service/CartService.java \
  --line 38 \
  --variable cart \
  --function addItem \
  --direction forward

Taint trace

rgctl -r "$REPO" slice \
  src/main/java/com/example/ecommerce/service/OrderService.java \
  --line 83 \
  --variable cartService \
  --function checkout \
  --taint

View modes

--viewDescription
textSummary (default)
cfgCFG overlay — use with -f mermaid or -f graphviz
pdgPDG overlay
rgctl -r "$REPO" -f mermaid slice \
  src/main/java/com/example/ecommerce/service/CartService.java \
  --line 53 --variable item --function addItem --view cfg

--function names

--function must be the method/function name in the source file (as parsed by tree-sitter), not the enclosing class name:

rgctl -r "$REPO" gql \
  "MATCH (n:Function) WHERE n.name = 'checkout' RETURN n"

9. Inspect CFG / PDG / dominance

inspect dumps semantic layers for an indexed function symbol (no --class flag — use a unique symbol or GQL to pick the right function). Run discover --with-cfg first.

rgctl -r "$REPO" inspect checkout cfg
CFG for checkout: 5 blocks, 5 edges
rgctl -r "$REPO" -f json inspect checkout cfg | jq '{layer, blocks: (.nodes|length), edges: (.edges|length)}'
{
  "layer": "cfg",
  "blocks": 5,
  "edges": 5
}

Mermaid CFG:

rgctl -r "$REPO" -f mermaid inspect checkout cfg
flowchart TD
  462c1054-… --> 14712608-…
  462c1054-… --> ae5a5a76-…
  14712608-… --> 897883b6-…
  ae5a5a76-… --> 897883b6-…
  897883b6-… --> 4165ce10-…

Other layers:

# Prune unreachable blocks
rgctl -r "$REPO" inspect checkout cfg --prune

# Program dependence graph (data edges)
rgctl -r "$REPO" inspect checkout pdg --edge-layer data
# → PDG for checkout: 13 nodes, 22 data deps, 0 control deps

rgctl -r "$REPO" inspect checkout pdg --def-use
rgctl -r "$REPO" inspect checkout dom --frontiers

10. Hybrid CPG (cpg)

The cpg façade bridges the repo call graph (L_repo) with the per-function CFG/PDG archive (L_proc) built by discover --with-cfg. Use it for typed field mutations, data flows, and Joern-style handoffs without stitching several CLI tools yourself.

Requires a prior discover … --with-cfg (the ecommerce walkthrough already uses that flag).

Status and CALL neighborhood

rgctl -r "$REPO" cpg status
# → CPG L_proc: ready (… functions) at …/cfg_pdg.archive.bin
# → CPG field writes: N indexed (cpg mutations)

rgctl -r "$REPO" cpg function priceShoppingCart
rgctl -r "$REPO" cpg calls 'ShoppingCartService::priceShoppingCart'

Field mutations (CoolStore ShoppingCart)

Find non-constructor writes to a type — useful before converting a mutable DTO/cart model to an immutable record, or to prove pricing still mutates totals:

rgctl -r "$REPO" cpg mutations --type ShoppingCart --exclude-ctors

Example (paths shortened):

Mutations of ShoppingCart [excl. ctors] (7 hits):
  …/coolstore/model/ShoppingCart.java:61  this.cartTotal = cartTotal
  …/coolstore/model/ShoppingCart.java:45  this.cartItemTotal = cartItemTotal
  …

Pair with blast-radius on the CoolStore pricing entrypoint:

rgctl -r "$REPO" blast-radius 'ShoppingCartService::priceShoppingCart'
# → Callers include CartEndpoint.add / delete / checkout and checkOutShoppingCart

Dashboard: after discover --with-cfg --with-dashboard, the Dataflow tab includes a Field mutations (CPG) panel (same filters). Click a hit to open that function’s PDG and highlight the write line. See Dashboard user guide.

JSON for agents:

rgctl -r "$REPO" -f json cpg mutations --type ShoppingCart --exclude-ctors

Empty result means no typed non-ctor writes were recovered (receivers without a resolved type are omitted unless --include-unresolved). On C fixtures, query the struct typedef name (e.g. shopping_cart_t). See agent-recipes.md Recipe 11 and hybrid-cpg-plan.md.

Flows, AST, export

# Forward flows from a variable at a line (wraps slice; optional --with-alias)
rgctl -r "$REPO" -f json cpg flows \
  src/main/java/com/example/ecommerce/coolstore/service/ShoppingCartService.java \
  --line 75 --variable sc --function priceShoppingCart --direction forward

# Optional: discover --with-ast-skeleton then:
rgctl -r "$REPO" -f json cpg ast priceShoppingCart

rgctl -r "$REPO" cpg export --format graphson --output /tmp/ecommerce-cpg.json \
  --path-contains coolstore/

11. Graph metrics

metrics reports network analytics on the indexed call graph. Prefer JSON for scripting (text mode prints debug-style structs).

rgctl -r "$REPO" -f json metrics --communities | jq .
{
  "communities": {
    "assignments": 518,
    "count": 442,
    "modularity": 0.49
  },
  "schema_version": 1
}

That summary is counts only. For named communities and membership, use GQL / communities list (§6) or .rgctl/dashboard/communities.json after --with-dashboard.

rgctl -r "$REPO" -f json metrics --pagerank | jq '.pagerank | {iterations, converged, top: .top[:3]}'
{
  "iterations": 20,
  "converged": false,
  "top": [
    { "node": "…uuid…", "pagerank": 0.0027 },
    { "node": "…uuid…", "pagerank": 0.0015 },
    { "node": "…uuid…", "pagerank": 0.0015 }
  ]
}
rgctl -r "$REPO" metrics --betweenness
rgctl -r "$REPO" -f json metrics --pagerank --iterations 50 | jq .

12. Semantic search

Semantic search is opt-in — it does not run during discover. Build a separate Hamming index, then query by natural language or keywords.

Default index scope: :Function symbols. Doc headings: semantic index --scope docs (or --scope all for functions + docs). See markdown-context.md.

Prerequisites: discover completed. Default embedder is vocab (compiled token table, no ONNX). Quality extra: --embedder code-daemon (needs git lfs pull for bundled ONNX). CI smoke: --embedder hash.

# Build semantic index (default: vocab, 256-d, declaration metadata only)
rgctl -r "$REPO" semantic index

# Incremental rebuild — reuse rows when body hash unchanged
rgctl -r "$REPO" semantic index --incremental

# Query (JSON for agents). Late fusion is ON by default.
rgctl -r "$REPO" -f json semantic query "shopping cart checkout" --limit 10
rgctl -r "$REPO" -f json semantic query "OrderService" --keyword-and
# Pure Hamming (disable fusion):
rgctl -r "$REPO" -f json semantic query "OrderService" --no-fusion --limit 10

# Community-scoped search — pool member embeddings (needs discover analysis + semantic index)
rgctl -r "$REPO" -f json semantic query "shopping cart" --scope community --limit 5

# Doc section search (markdown headings; needs discover -l markdown)
rgctl -r "$REPO" semantic index --scope docs --embedder hash
rgctl -r "$REPO" -f json semantic query "checkout flow" --scope docs --limit 10

# Hash embedder (no ONNX) — e.g. CI
rgctl -r "$REPO" semantic index --embedder hash

# Vocab is the default; optional call-graph diffusion
rgctl -r "$REPO" semantic index --diffuse \
  --diffuse-alpha 0.25 --diffuse-iters 2

# Re-read function bodies into the vector (off by default; fusion still uses discover token-blooms)
rgctl -r "$REPO" semantic index --embed-bodies

# Distill our token list through a teacher (rebuild after copying to assets/vocab_matrix.bin)
rgctl -r "$REPO" semantic distill --matrix crates/rgctl-analysis/assets/vocab_matrix.bin --embedder code-daemon

# Neural code retriever (ONNX)
rgctl -r "$REPO" semantic index --embedder code-daemon

Passing --diffuse recomputes dense vectors and mixes call-graph neighbors before sign quantization (even when --incremental would otherwise reuse bits). Query does not re-diffuse — restart is not required for CLI query; for the dashboard, restart serve after rebuilding the index.

Doc semantic index: --scope docs on semantic index embeds :Module sections (kind=heading and kind=code_block). Query --scope docs does not filter hits — only semantic index --scope community changes query behavior. Build a doc-scoped index before querying doc sections. Large bodies use content_store.bin when body_ref is set. CLI success text may still say Indexed N functions (entry count, not always functions).

FlagPurpose
semantic index --scope function|docs|allWhich symbols to embed (default: functions only)
semantic query --scope function|community|docs|allcommunity = pooled community search; other scopes do not filter hits (index content determines results)
--no-fusionDisable late fusion (default is fusion on: blast, PageRank, name, token-bloom, community, package, callees)
--keyword-andEvery query token must match metadata or body sketch
--candidate-pool <N>Hamming pool size before fusion [default: 256]
--expand neighbors|blast|gql|allHybrid expansion after top hits
--embedder hash|vocab|onnx|code-daemonEmbedding backend [default: vocab]
--embed-bodiesAppend identifier tokens from function source (off by default)
--dimensions <N>Float width before quantize; multiple of 8 [default: 256]
semantic distill --matrix <PATH>Write RBVK from our token list via a teacher (code-daemon default; not vocab)
--diffuse / --no-diffuseJacobi call-graph mix on dense floats before quantize (index only; off by default)
--diffuse-alpha / --diffuse-itersDiffusion blend weight and iterations [defaults: 0.25, 2]
--diffuse-bidirectionalInclude callers as well as callees

Dashboard: rgctl serve --openSearch tab uses the same index via /api/semantic/*. The UI does not choose the embedder — build the index with CLI first, then restart serve. Status shows model_id (e.g. vocab-accumulate-v1).

Perf note (linux-scale): time queries with a release binary (cargo build --release). Debug builds can be ~100× slower on Hamming scan. Index load of .rgctl/semantic_index.bin is bincode into owned strings (~tens of seconds at ~1.8M functions); query itself is ~few ms in release.

Design → Semantic search design · timing tests → cargo test --test semantic_query_timing -- --nocapture


13. Export graph projections

export writes the graph or a filter-selected subgraph to a file or directory. The --query flag uses filter syntax, not GQL MATCH (JSON/graph formats honor the filter; Obsidian/OKF use --query all):

--export-formatOutput--query
jsonGraph snapshot JSONFilter or all
graphmlGraphML XMLFilter or all
graphvizDOTFilter or all
mermaidMermaid flowchartFilter or all
obsidianObsidian vault directoryall (heading modules)
okfOKF JSON entity bundleall
QueryMeaning
allEntire graph
name:clearCartNodes with exact name
type:FunctionAll functions
functionsShortcut for function nodes
rgctl -r "$REPO" export \
  --export-format mermaid \
  --export-output cart-clear.mmd \
  --query "name:clearCart"
Exported 2 nodes, 1 edges -> cart-clear.mmd
# Full graph as JSON / GraphML / DOT
rgctl -r "$REPO" export --export-format json --export-output ecommerce-graph.json --query all
rgctl -r "$REPO" export --export-format graphml --export-output ecommerce.graphml --query all
rgctl -r "$REPO" export --export-format graphviz --export-output calls.dot --query all

Markdown → Obsidian vault

For documentation repos (or -l markdown discover), export heading sections as an Obsidian vault. Requires prior discover (creates .rgctl/ and content_store.bin for large bodies).

export REPO=/path/to/docs-repo
rgctl -r "$REPO" discover -l markdown
rgctl -r "$REPO" export \
  --export-format obsidian \
  --export-output "$REPO/vault" \
  --query all

Open $REPO/vault in Obsidian (Open folder as vault). Each note is one heading section; YAML qualified_name maps back to GQL. Re-run discover + export after doc changes.

Other doc export formats: --export-format okf (JSON entity bundle). Full walkthrough: markdown-context.md.

For GQL pattern matching, use rgctl gql — or rgctl serve + HTTP API.


14. CI policy check

check evaluates blast-radius policy rules against functions changed in the current git working tree (or all functions if git is unavailable).

Example policy files: docs/examples/policy-strict.json. Format: policy-format.md.

rgctl -r "$REPO" check --policy-file policy.json

Exit code 1 when violations are found — suitable for CI pipelines.

The fixture also ships a shared policy at rgctl-tests/rgctl-policy.json.


15. HTTP server (serve) — optional

serve binds HTTP immediately and, unless --no-pipeline, starts the same staged full pipeline as discover --full. The dashboard at / shows a preparing page until the bundle exists. Prefer CLI -f json for agents and CI.

# Starts indexing if needed; preparing page until dashboard exists
rgctl -r "$REPO" serve --port 8080

# Old fail-fast (require existing artifacts)
rgctl -r "$REPO" serve --no-pipeline --query-only
EndpointPurpose
/Dashboard UI or preparing page
GET /api/statusFull-pipeline status JSON
POST /api/queryGQL / macros (JSON body; 503 until graph ready)
GET /api/semantic/statusSemantic index availability
POST /api/semantic/querySemantic search (JSON body)
/api/healthHealth check
curl -sS -X POST http://127.0.0.1:8080/api/query \
  -H 'Content-Type: application/json' \
  -d '{"macro":"all_functions"}' | jq '.count'

Full reference: http-api.md. CoolStore walkthrough: HTTP Server and Dashboard.


16. Recommended workflow

# 1. Point at the in-tree fixture
cd /path/to/rgctl
export REPO="$PWD/rgctl-tests/ecommerce-java"
cd "$REPO"

# 2. Index (add CFG + dashboard + Kantra for the rest of this walkthrough)
rgctl discover . -l java -e target \
  --with-cfg --with-dashboard --with-harmonic --with-kantra --export-migration-hints

# 3. Explore structure
rgctl -r "$REPO" -f json gql --macro-name all_functions unused | jq '.count'
rgctl -r "$REPO" -f json gql --macro-name all_communities unused | jq '.rows[:5]'
rgctl -r "$REPO" communities list | head -15
rgctl -r "$REPO" gql \
  "MATCH (a:Function)-[:CALLS]->(b:Function) WHERE b.name = 'clearCart' RETURN a,b"

# 4. Change-impact before editing
rgctl -r "$REPO" blast-radius 'CartService::clearCart'
rgctl -r "$REPO" -f json blast-radius 'CartService::clearCart' | jq '.metrics'

# 5. CoolStore dual API + hybrid CPG (field mutations)
rgctl -r "$REPO" cpg status
rgctl -r "$REPO" cpg mutations --type ShoppingCart --exclude-ctors
rgctl -r "$REPO" blast-radius 'ShoppingCartService::priceShoppingCart'

# 5b. Konveyor Kantra migration rules (optional target filter)
rgctl -r "$REPO" -f json gql "MATCH (r:KantraRule) RETURN r LIMIT 5" | jq '.count'
rgctl -r "$REPO" -f json gql \
  'MATCH (r:KantraRule)-[:VIOLATES]->(n) RETURN r, n LIMIT 10' | jq '.count'
jq '{violations: (.violations|length), evaluated_rules, target_filter}' \
  "$REPO/.rgctl/kantra_findings.json"

# 6. Architectural hotspots
rgctl -r "$REPO" -f json metrics --communities | jq .
rgctl -r "$REPO" -f json metrics --pagerank | jq '.pagerank.top[:5]'

# 7. Deep dive on checkout
rgctl -r "$REPO" inspect checkout cfg
rgctl -r "$REPO" slice \
  src/main/java/com/example/ecommerce/service/CartService.java \
  --line 53 --variable item --function addItem

# 8. Export / dashboard
rgctl -r "$REPO" export --export-format mermaid \
  --export-output clearCart.mmd --query 'name:clearCart'
rgctl -r "$REPO" serve --open

Migration hints (with --export-migration-hints) land under .rgctl/migration_plan.json and .rgctl/dashboard/migration_plan.json — package-level steps such as com.example.ecommerce.service, …repository, …controller, and CoolStore …coolstore.*.


17. Command reference

CommandPurpose
discoverIndex repo, build .rgctl/ artifacts (--full = staged CFG/dashboard/harmonic + semantic)
gqlGraph query language (incl. virtual :Community)
communitiesList / refresh heuristic community labels
blast-radiusUpstream call-graph impact for a symbol
sliceLine-level program slice or taint trace
inspectCFG / PDG / dominance for a function
cpgHybrid CPG: status, mutations, flows, calls, export (needs --with-cfg)
metricsPageRank, betweenness, communities summary
exportSerialize graph (json, graphml, dot, mermaid, obsidian vault, okf)
checkCI policy gateway
installCopy the bundled agent skill into .claude/skills/ and .cursor/skills/
semanticOpt-in semantic index + query (--scope community, docs, all)
serveHTTP dashboard + /api/query + /api/status (auto full pipeline); --no-pipeline fail-fast

discover flags

FlagDescription
-l, --languagesComma-separated filter (java, typescript, rust, …)
-e, --excludeComma-separated path exclude patterns
-v, --verboseDebug logging + stage profile lines
--with-securitySecret scanning
--with-cfgCFG / PDG (not taint); alias --cfg
--with-taintDiscover-time taint (implies CFG pass)
--with-dfg-loopsTag loop-carried DataDependency edges in PDG (with --with-cfg)
--with-ast-skeletonBuild AST skeleton archive for cpg ast
--with-harmonicHarmonic centrality (default off; needed for migration ranking)
--with-dashboardStatic dashboard bundle (default off)
--fullStaged pipeline: basic discover, then CFG+dashboard+harmonic, then semantic index
--export-migration-hintsMigration roadmap JSON (alias --export-migration-plan)
--migration-presetPreset for migration hints (hybrid, foundational, …)
--migration-orderscheduled (topological) or priority
--write-json-graphAlso write legacy graph.db / graph.json
--with-kantraKonveyor Kantra eval + rules graph index (embedded catalog by default)
--kantra-target NAMEFilter eval to konveyor.io/target=NAME
--kantra-rules DIROverride embedded catalog with one ruleset directory
--kantra-catalog ROOTOverride embedded catalog with a rulesets tree
--kantra-index-onlyIndex rules into graph; skip eval

There is no umbrella --all flag — combine --with-cfg --with-security --with-taint explicitly when you want the former deep pass.


18. Troubleshooting

Indexed the wrong repository

If you ran rgctl -r /path/to/repo discover . from another directory, artifacts reflect shell cwd, not -r. Fix:

cd /path/to/repo && rgctl discover .
# or: rgctl -r /path/to/repo discover    # no trailing .

Graph not found / run discover first

rgctl discover . -l java -e target
# or
rgctl -r "$REPO" gql 'MATCH (n:Function) RETURN n LIMIT 1'

Symbol not found / ambiguous (blast-radius, inspect)

List exact names, then use FQN:

rgctl -r "$REPO" gql "MATCH (n:Function) WHERE n.name = 'clearCart' RETURN n"
rgctl -r "$REPO" blast-radius 'CartService::clearCart'
rgctl -r "$REPO" blast-radius clearCart --class CartService

inspect takes a function name (checkout, addItem), not a class name (CartService).

Slice parse / PDG errors

Ensure you ran discover --with-cfg, then pass the method name and a variable that exists on that line:

rgctl -r "$REPO" slice \
  src/main/java/com/example/ecommerce/service/CartService.java \
  --line 53 --variable item --function addItem --language java

Empty cpg mutations

Confirm cpg status shows a field-write index, then match the resolved type name (Java/C#/…: ShoppingCart; C: shopping_cart_t). Setters count as mutation sites; unresolved receivers are omitted unless --include-unresolved. Re-run discover --with-cfg after adding CoolStore sources.

Slow discover

Start with the default mode. Add --with-cfg or --with-taint only when you need inspect, slice overlays, or taint. Keep --with-harmonic / --with-dashboard off unless you need migration ranking or the static UI.

On very large repos (500k+ graph nodes), discover automatically:

  • Caps PageRank iterations and relaxes convergence tolerance
  • Caps HyperBall harmonic rounds (when --with-harmonic) and parallelizes propagation
  • Skips per-function rows in function_metrics.json (community/metagraph view instead)
  • Uses on-demand blast reachability for flat call graphs (no eager multi-hundred-GB bitsets)

Profile a cold run (delete in-repo artifacts first):

rm -rf .rgctl
rgctl discover . -v   # from repo root
# or:
RUST_LOG=info,profile=info rgctl discover . -v 2>&1 | grep '\[profile\]'

Further reading