<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shaaf Syed</title>
    <description>The latest articles on DEV Community by Shaaf Syed (@sshaaf).</description>
    <link>https://dev.to/sshaaf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F371656%2F62a7bfdc-3b24-4c74-8f1b-bbc5cd073836.png</url>
      <title>DEV Community: Shaaf Syed</title>
      <link>https://dev.to/sshaaf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sshaaf"/>
    <language>en</language>
    <item>
      <title>Enforcing Architecture with rgctl Policy Checks</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Wed, 09 Sep 2026 14:37:39 +0000</pubDate>
      <link>https://dev.to/sshaaf/enforcing-architecture-with-rgctl-policy-checks-2j34</link>
      <guid>https://dev.to/sshaaf/enforcing-architecture-with-rgctl-policy-checks-2j34</guid>
      <description>&lt;h1&gt;
  
  
  Enforcing Architecture with rgctl Policy Checks
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;rgctl&lt;/a&gt; (Reachability Graph Control) indexes a codebase into a compact knowledge graph -- functions, classes, calls, inheritance, communities -- and serves deterministic, structured answers to questions about architecture, impact, and data flow. You run &lt;code&gt;rgctl discover .&lt;/code&gt; once, and every subsequent query (&lt;code&gt;blast-radius&lt;/code&gt;, &lt;code&gt;metrics&lt;/code&gt;, &lt;code&gt;gql&lt;/code&gt;) returns sub-millisecond results from the persisted graph. Agents and humans get the same exact facts. No re-reading source files, no confident guesses, no context window gymnastics.&lt;/p&gt;

&lt;p&gt;This post is not about querying the graph. It is about using it to &lt;strong&gt;enforce rules&lt;/strong&gt; -- automatically, in CI, on every pull request. We will use a real Java EE monolith as the worked example: the &lt;a href="https://github.com/sshaaf/coolstore-weblogic" rel="noopener noreferrer"&gt;CoolStore WebLogic&lt;/a&gt; application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The example project: CoolStore WebLogic
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sshaaf/coolstore-weblogic" rel="noopener noreferrer"&gt;CoolStore&lt;/a&gt; is a Java EE e-commerce monolith originally built for WebLogic. It has a REST layer (&lt;code&gt;CartEndpoint&lt;/code&gt;, &lt;code&gt;OrderEndpoint&lt;/code&gt;, &lt;code&gt;ProductEndpoint&lt;/code&gt;), a service layer (&lt;code&gt;ShoppingCartService&lt;/code&gt;, &lt;code&gt;CatalogService&lt;/code&gt;, &lt;code&gt;PromoService&lt;/code&gt;, &lt;code&gt;OrderService&lt;/code&gt;, &lt;code&gt;ShippingService&lt;/code&gt;), JPA model classes, and a JavaScript frontend with bundled vendor libraries (lodash, d3, jQuery DataTables, Angular). It is small enough to follow along, but structurally interesting -- the vendor JavaScript creates deep call graphs that dwarf the Java application code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-architecture.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-architecture.svg" alt="CoolStore WebLogic: application layers -- Java EE monolith with bundled JavaScript vendor libraries"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clone it and index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sshaaf/coolstore-weblogic.git
&lt;span class="nb"&gt;cd &lt;/span&gt;coolstore-weblogic
rgctl discover &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; rgctl discover
&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Analyzing: coolstore-weblogic
&lt;span class="gp"&gt;[✓] Loaded 1227 files from snapshot -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;17417 nodes, 52920 edges &lt;span class="o"&gt;(&lt;/span&gt;0.0s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;[✓] Analyzed 7526 functions (avg complexity: 1.0, 0 high, 0 medium)
[✓] Detected 13565 communities (modularity: 0.29)
[*] Top hotspot: replace (PageRank: 0.0502)
[!] Found 186 circular dependencies
[✓] Analysis complete
[✓] Saved to .rgctl/ (2.9 MB total)
[✓] Completed in 0.6s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;17,417 nodes, 52,920 edges, indexed in under a second. The graph is now on disk at &lt;code&gt;.rgctl/&lt;/code&gt; and ready for policy checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: architecture rules that live in people's heads
&lt;/h2&gt;

&lt;p&gt;Every team has architectural constraints. "The payments module must not depend on legacy." "No single function should affect more than 50 downstream consumers." "High-centrality bridge functions need extra review." These rules exist as tribal knowledge, communicated in onboarding docs and code review comments. They are enforced manually, inconsistently, and after the fact.&lt;/p&gt;

&lt;p&gt;Manual enforcement has three failure modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Drift.&lt;/strong&gt; The rule is forgotten. A new dependency slips in. Nobody notices until it causes a production incident three months later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambiguity.&lt;/strong&gt; "Too much coupling" means different things to different reviewers. Without a number, every review is a negotiation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale.&lt;/strong&gt; On a codebase with 7,500 functions, no human can evaluate the blast radius of every changed function in a pull request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;rgctl's &lt;code&gt;check&lt;/code&gt; command solves this by turning architecture rules into a JSON policy file that a CI pipeline can evaluate automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy file format
&lt;/h2&gt;

&lt;p&gt;A policy file is a JSON object with up to four fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"centrality_alert_threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"forbidden_crossings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"legacy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"node_domains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"550e8400-e29b-41d4-a716-446655440000"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"legacy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"6ba7b810-9dad-11d1-80b4-00c04fd430c8"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every field is optional. Omitted fields default to unlimited (no constraint).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_impact_nodes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;unlimited&lt;/td&gt;
&lt;td&gt;Fails if any function's blast-radius impact zone exceeds this count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;centrality_alert_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;td&gt;unlimited&lt;/td&gt;
&lt;td&gt;Fails if any reached node has betweenness centrality above this value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forbidden_crossings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[[string, string], ...]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fails if a blast-radius path crosses between the named domain pair&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;node_domains&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ "uuid": "domain" }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maps node UUIDs to domain labels used by &lt;code&gt;forbidden_crossings&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The simplest possible policy is one field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says: no function in the codebase may have a blast radius larger than 25 nodes. If any function exceeds that threshold, the check fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Violation types
&lt;/h2&gt;

&lt;p&gt;When a policy rule is broken, rgctl reports a structured violation. There are four types:&lt;/p&gt;

&lt;h3&gt;
  
  
  ScaleFailure
&lt;/h3&gt;

&lt;p&gt;Triggered when a function's impact zone exceeds &lt;code&gt;max_impact_nodes&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scale failure: impact zone size 648 exceeds max 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most common violation. It catches functions that are deeply embedded in the call graph -- utility functions like &lt;code&gt;indexOf&lt;/code&gt; or &lt;code&gt;baseIsEqual&lt;/code&gt; that fan out to hundreds of downstream consumers. A change to these functions has outsized blast radius, and the policy forces the team to acknowledge that.&lt;/p&gt;

&lt;h3&gt;
  
  
  CascadeHazard
&lt;/h3&gt;

&lt;p&gt;Triggered when a node in the blast-radius impact zone has betweenness centrality above &lt;code&gt;centrality_alert_threshold&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cascade hazard: node 6ba7b810... betweenness 0.8523 exceeds threshold 0.8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Betweenness centrality measures how often a node sits on the shortest path between other nodes. High-betweenness nodes are bridge functions -- they connect otherwise separate parts of the graph. Changing a function that reaches a bridge node means the blast radius crosses architectural boundaries. The &lt;code&gt;CascadeHazard&lt;/code&gt; violation flags this.&lt;/p&gt;

&lt;h3&gt;
  
  
  DomainIsolation
&lt;/h3&gt;

&lt;p&gt;Triggered when a blast-radius path crosses a &lt;code&gt;forbidden_crossings&lt;/code&gt; boundary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;domain isolation failure: path from 'legacy' to 'payments' via node 550e8400...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires setting up &lt;code&gt;node_domains&lt;/code&gt; to assign graph nodes to logical domains, and &lt;code&gt;forbidden_crossings&lt;/code&gt; to declare which domain pairs must not be connected. The crossing check is bidirectional -- &lt;code&gt;["legacy", "payments"]&lt;/code&gt; blocks both &lt;code&gt;legacy -&amp;gt; payments&lt;/code&gt; and &lt;code&gt;payments -&amp;gt; legacy&lt;/code&gt; paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  SanitizationBypass
&lt;/h3&gt;

&lt;p&gt;Triggered by the CPG (Code Property Graph) analysis when a sanitizer on a taint path does not dominate the sink block in the control-flow graph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sanitization bypass: sanitizer 550e8400... does not dominate sink at line 42 (path len 3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most advanced violation type. It requires &lt;code&gt;discover --with-cfg&lt;/code&gt; to build the control-flow graph and PDG (Program Dependence Graph), and catches cases where a security sanitizer exists on a data-flow path but can be bypassed through an alternate control-flow route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the first policy check on CoolStore
&lt;/h2&gt;

&lt;p&gt;Create a policy file. We will start with &lt;code&gt;max_impact_nodes: 15&lt;/code&gt; and &lt;code&gt;centrality_alert_threshold: 0.8&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"centrality_alert_threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this as &lt;code&gt;policy.json&lt;/code&gt; in the repository root and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json check &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output (truncated):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"policy.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"passed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 114 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"baseIsEqual"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 649 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"baseIndexOf"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 78 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"merge"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 818 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"isIterateeCall"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 900 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"isLength"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 648 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indexOf"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 899 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"replace"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 18 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getInventory"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 16 exceeds max 15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"_fnInitComplete"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The check exits with code &lt;strong&gt;1&lt;/strong&gt;. The full output lists over 100 violations, but the pattern is clear: the worst offenders are lodash and jQuery DataTables internals (&lt;code&gt;isLength&lt;/code&gt; at 900, &lt;code&gt;replace&lt;/code&gt; at 899, &lt;code&gt;isIterateeCall&lt;/code&gt; at 818, &lt;code&gt;indexOf&lt;/code&gt; at 648). The Java application function &lt;code&gt;getInventory&lt;/code&gt; appears too -- it has a blast radius of 18 nodes because of its callers in &lt;code&gt;CatalogService&lt;/code&gt; and the downstream &lt;code&gt;CatalogItemEntity&lt;/code&gt; model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the exit code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl check &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy.json
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Exit code: &lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Policy violations: 113
Exit code: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 1 means the policy failed. Any CI system can use this directly as a gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-function blast radius with policy
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;check&lt;/code&gt; command evaluates every function in the graph. When you want to inspect a specific function, use &lt;code&gt;blast-radius --policy-file&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-blast-comparison.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-blast-comparison.svg" alt="Blast radius: pass vs fail -- priceShoppingCart (7 nodes) vs indexOf (648 nodes) at max_impact_nodes: 15"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A function that passes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;priceShoppingCart&lt;/code&gt; is the core pricing method in &lt;code&gt;ShoppingCartService&lt;/code&gt;. Let's check it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius priceShoppingCart &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gatekeeping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"policy_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.35&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"priceShoppingCart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"canonical_fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ShoppingCartService::priceShoppingCart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/main/java/com/redhat/coolstore/service/ShoppingCartService.java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public void priceShoppingCart(ShoppingCart sc) {"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"topology"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.add"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.dedupeCartItems"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.set"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.service.ShoppingCartService.checkOutShoppingCart"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.delete"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.add"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.dedupeCartItems"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.delete"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.set"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.service.ShoppingCartService.checkOutShoppingCart"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.checkout"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"anonymous"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;policy_status: "PASS"&lt;/code&gt;&lt;/strong&gt;. The impact zone is 7 nodes -- well under the 15-node limit. Five direct callers, all in &lt;code&gt;CartEndpoint&lt;/code&gt; and &lt;code&gt;ShoppingCartService&lt;/code&gt; itself. Changing &lt;code&gt;priceShoppingCart&lt;/code&gt; affects a contained, well-defined set of functions. This is what healthy coupling looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  A function that violates
&lt;/h3&gt;

&lt;p&gt;Now check &lt;code&gt;indexOf&lt;/code&gt; from the bundled lodash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius indexOf &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gatekeeping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"policy_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VIOLATED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scale_failure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;648&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;232&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;648&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;72.4&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indexOf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/main/webapp/bower_components/lodash/lodash.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;policy_status: "VIOLATED"&lt;/code&gt;&lt;/strong&gt;. 232 direct callers, 648 nodes in the impact zone. Changing &lt;code&gt;indexOf&lt;/code&gt; could affect 648 functions across the entire application. The policy rightfully flags this -- nobody should modify a vendored lodash function without understanding that scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tight policy on Java service code
&lt;/h3&gt;

&lt;p&gt;Let's use a tighter policy (&lt;code&gt;max_impact_nodes: 8&lt;/code&gt;) and check &lt;code&gt;getShoppingCart&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius getShoppingCart &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy-tight.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gatekeeping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"policy_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VIOLATED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scale_failure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.5&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getShoppingCart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"canonical_fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ShoppingCartService::getShoppingCart"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a limit of 8, &lt;code&gt;getShoppingCart&lt;/code&gt; violates -- its impact zone is 10. Six direct callers (&lt;code&gt;CartEndpoint.add&lt;/code&gt;, &lt;code&gt;CartEndpoint.set&lt;/code&gt;, &lt;code&gt;CartEndpoint.delete&lt;/code&gt;, &lt;code&gt;CartEndpoint.dedupeCartItems&lt;/code&gt;, &lt;code&gt;CartEndpoint.getCart&lt;/code&gt;, &lt;code&gt;ShoppingCartService.checkOutShoppingCart&lt;/code&gt;) plus their downstream consumers push it over. Raise the threshold to 10 and it passes. This is the kind of tuning that policy files are designed for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Java application violations at moderate thresholds
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;max_impact_nodes: 10&lt;/code&gt;, the Java application code starts surfacing alongside the vendor JavaScript:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symbol&lt;/th&gt;
&lt;th&gt;Impact zone&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getShoppingCartItemList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ShoppingCart.java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getProductByItemId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ProductService.java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toProduct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Transformers.java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getCartItemTotal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ShoppingCart.java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getShippingTotal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ShoppingCart.java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getInventory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CatalogItemEntity.java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getCatalogItemById&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CatalogService.java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are the Java functions that fan out the most. &lt;code&gt;getInventory&lt;/code&gt; has the largest blast radius at 18 nodes -- it is used by &lt;code&gt;CatalogItemEntity&lt;/code&gt; which feeds into &lt;code&gt;CatalogService&lt;/code&gt;, &lt;code&gt;ProductService&lt;/code&gt;, and ultimately the REST endpoints. During a migration from WebLogic to, say, Quarkus, these are the functions you would want to refactor first -- they touch the most downstream code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissive vs strict policies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Permissive: baseline for a monolith
&lt;/h3&gt;

&lt;p&gt;Start with a high threshold to establish the pipeline and see what you are dealing with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"centrality_alert_threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json check &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy-permissive.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"policy-permissive.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"passed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"baseIndexOf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 649 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"binaryIndex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 659 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"isIterateeCall"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 818 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"join"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 607 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"isLength"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 900 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"slice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 697 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"isIndex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 856 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"replace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 899 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"baseSlice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 883 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indexOf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 648 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"indexOfNaN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 659 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"binaryIndexBy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 660 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"isObjectLike"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Graph error: scale failure: impact zone size 745 exceeds max 500"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even at 500, 13 functions still violate. All of them are lodash internals. The Java application code passes entirely. This tells you the vendor JavaScript is the structural risk, not the Java services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strict: zero tolerance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;max_impact_nodes&lt;/code&gt; of 0 means any function with any downstream consumer triggers a failure. Useful for smoke-testing the pipeline or for isolated modules where every function is expected to be a leaf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain isolation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-domain-isolation.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-domain-isolation.svg" alt="Domain isolation: forbidden crossings -- policy blocks blast-radius paths between service and rest layers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You are migrating CoolStore from WebLogic. The REST layer and the service layer must not grow new cross-layer dependencies. You want CI to catch any function whose blast radius crosses from &lt;code&gt;service&lt;/code&gt; into &lt;code&gt;rest&lt;/code&gt; (or vice versa).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Find the node UUIDs for functions in each layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"MATCH (n:Function) WHERE n.file_path CONTAINS 'service/' RETURN n"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each node in the response includes an &lt;code&gt;id&lt;/code&gt; field (UUID). Collect the UUIDs for the service-layer functions and the REST-layer functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Build the policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"forbidden_crossings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rest"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"node_domains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"60a84e0c-7366-4e65-8c41-98b4411c7d36"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"26135ad3-6fa3-4fab-bca6-32607ddbff72"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"b311845f-2bb1-43c5-99b5-90d2e7d6f60b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fd95d5e1-a959-4d7c-92d4-bd20de91dc45"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"004895d9-7ebd-46ed-b92f-84ca01ccfed2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"73fb42ed-45dd-46af-86e9-f1306995916c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rest"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Run the check. Any blast-radius path that crosses from a &lt;code&gt;service&lt;/code&gt; node to a &lt;code&gt;rest&lt;/code&gt; node (or vice versa) triggers a &lt;code&gt;DomainIsolation&lt;/code&gt; violation.&lt;/p&gt;

&lt;p&gt;The crossing check is bidirectional. Declaring &lt;code&gt;["service", "rest"]&lt;/code&gt; blocks both directions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git-aware scoping (local development)
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;check&lt;/code&gt; command has a local optimization: it runs &lt;code&gt;git diff --name-only HEAD&lt;/code&gt; to detect &lt;strong&gt;uncommitted changes in the working tree&lt;/strong&gt;. If you have modified files that are not yet committed, &lt;code&gt;check&lt;/code&gt; evaluates only the functions defined in those files instead of the full graph. This makes the feedback loop fast during local development -- edit a file, run the check, see if your change introduces a policy violation.&lt;/p&gt;

&lt;p&gt;If the diff is empty (no uncommitted changes) or git is unavailable, the check falls back to evaluating every function in the graph. This is what happens in CI: a GitHub Actions checkout produces a clean working tree with no uncommitted changes, so &lt;code&gt;git diff --name-only HEAD&lt;/code&gt; returns nothing and the full graph is evaluated. For CoolStore's 7,500 functions this is still fast (sub-second), but on very large codebases you may want to be aware of this.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions integration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-ci-pipeline.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-ci-pipeline.svg" alt="GitHub Actions: architecture policy gate -- every pull request is checked against policy.json before merge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a complete GitHub Actions workflow that runs rgctl policy checks on every pull request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Architecture Policy Check&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;policy-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install rgctl&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -fsSL https://github.com/sshaaf/rgctl/releases/latest/download/rgctl-linux-x86_64.tar.gz \&lt;/span&gt;
            &lt;span class="s"&gt;| tar xz -C /usr/local/bin&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Discover codebase&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rgctl discover .&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run policy check&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rgctl -f json check --policy-file policy.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Update 09-09-2026: Today, rgctl does not checkout full git history. evaluation is on a clean tree. This means that &lt;code&gt;check&lt;/code&gt; evaluates every function in the graph, not just the ones modified in the PR. Temporal updates and diffs are planned for a future release.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The workflow does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Checks out the PR branch.&lt;/strong&gt; The checkout is a clean tree, so &lt;code&gt;check&lt;/code&gt; evaluates every function in the graph (the git-aware local optimization does not apply in CI -- see above).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discovers the codebase&lt;/strong&gt; to build the knowledge graph. This step runs once and persists the graph to &lt;code&gt;.rgctl/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs the policy check&lt;/strong&gt; against &lt;code&gt;policy.json&lt;/code&gt; at the repository root. If any violation is found, the step fails with exit code 1 and the pull request is blocked.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Adding violation details to PR comments
&lt;/h3&gt;

&lt;p&gt;For better developer experience, pipe the JSON output into a PR comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run policy check&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;policy&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;rgctl -f json check --policy-file policy.json &amp;gt; policy-result.json&lt;/span&gt;
          &lt;span class="s"&gt;echo "passed=$(jq -r .passed policy-result.json)" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Comment on PR&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.policy.outputs.passed == 'false'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;const fs = require('fs');&lt;/span&gt;
            &lt;span class="s"&gt;const result = JSON.parse(fs.readFileSync('policy-result.json', 'utf8'));&lt;/span&gt;
            &lt;span class="s"&gt;const violations = result.violations&lt;/span&gt;
              &lt;span class="s"&gt;.map(v =&amp;gt; `- **${v.symbol}**: ${v.error || v.violation}`)&lt;/span&gt;
              &lt;span class="s"&gt;.join('\n');&lt;/span&gt;
            &lt;span class="s"&gt;const body = `## Policy Check Failed\n\n${result.violations.length} violation(s) found:\n\n${violations}`;&lt;/span&gt;
            &lt;span class="s"&gt;github.rest.issues.createComment({&lt;/span&gt;
              &lt;span class="s"&gt;issue_number: context.issue.number,&lt;/span&gt;
              &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
              &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
              &lt;span class="s"&gt;body&lt;/span&gt;
            &lt;span class="s"&gt;});&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fail if violations&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.policy.outputs.passed == 'false'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exit &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This posts a formatted comment listing every violation, so the developer can see exactly which functions broke the policy without digging into the CI logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching the graph
&lt;/h3&gt;

&lt;p&gt;For large repositories, the &lt;code&gt;discover&lt;/code&gt; step can take minutes. Cache the &lt;code&gt;.rgctl/&lt;/code&gt; directory to skip re-indexing when the source has not changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache rgctl graph&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.rgctl&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rgctl-${{ hashFiles('**/*.java', '**/*.ts', '**/*.py') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rgctl-&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Discover codebase&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rgctl discover .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the cache hits, &lt;code&gt;discover&lt;/code&gt; detects the existing graph and skips the indexing step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tightening policies over time
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-tightening.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-policy-tightening.svg" alt="Tightening policies over time -- CoolStore violations at each threshold"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A practical adoption path for CoolStore (or any monolith):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Week 1:&lt;/strong&gt; Deploy with a permissive policy (&lt;code&gt;max_impact_nodes: 500&lt;/code&gt;). On CoolStore, this catches 13 lodash internals and lets all Java application code pass. This is your baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 2-4:&lt;/strong&gt; Lower the threshold (&lt;code&gt;max_impact_nodes: 50&lt;/code&gt;). The vendor JavaScript dominates the violations. Consider excluding &lt;code&gt;bower_components/&lt;/code&gt; from indexing if vendor code is not your concern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month 2:&lt;/strong&gt; Add &lt;code&gt;centrality_alert_threshold: 0.8&lt;/code&gt;. Start catching bridge functions that connect otherwise separate subsystems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month 3+:&lt;/strong&gt; Tighten to &lt;code&gt;max_impact_nodes: 15&lt;/code&gt;. Now Java application functions like &lt;code&gt;getInventory&lt;/code&gt; (18 nodes) and &lt;code&gt;getCatalogItemById&lt;/code&gt; (15 nodes) start appearing. These are your refactoring targets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ongoing:&lt;/strong&gt; Add &lt;code&gt;forbidden_crossings&lt;/code&gt; for module boundaries you want to enforce. Assign &lt;code&gt;node_domains&lt;/code&gt; to the key functions in each domain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step produces measurable progress. The violation count in CI is your metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Define rules&lt;/td&gt;
&lt;td&gt;Write a &lt;code&gt;policy.json&lt;/code&gt; with thresholds and domain boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check locally&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rgctl -f json check --policy-file policy.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check one function&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rgctl -f json blast-radius priceShoppingCart --policy-file policy.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI gate&lt;/td&gt;
&lt;td&gt;Add to GitHub Actions; exit code 1 blocks the PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start permissive&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{"max_impact_nodes": 500}&lt;/code&gt; -- catches only the worst offenders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get strict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"max_impact_nodes": 15, "centrality_alert_threshold": 0.8}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enforce boundaries&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;forbidden_crossings&lt;/code&gt; and &lt;code&gt;node_domains&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Architecture rules should not live in people's heads. They should live in a JSON file, evaluated on every pull request, with a clear pass/fail signal. That is what rgctl policy checks give you.&lt;/p&gt;

&lt;p&gt;The CoolStore WebLogic source is at &lt;a href="https://github.com/sshaaf/coolstore-weblogic" rel="noopener noreferrer"&gt;github.com/sshaaf/coolstore-weblogic&lt;/a&gt;. Clone it, run &lt;code&gt;rgctl discover .&lt;/code&gt;, write a &lt;code&gt;policy.json&lt;/code&gt;, and see what breaks.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>rgctl</category>
      <category>ci</category>
      <category>architecture</category>
    </item>
    <item>
      <title>I Created a Code Graph of Magento 2 for AI Agents</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Mon, 07 Sep 2026 15:54:44 +0000</pubDate>
      <link>https://dev.to/sshaaf/i-created-a-code-graph-of-magento-2-for-ai-agents-5g80</link>
      <guid>https://dev.to/sshaaf/i-created-a-code-graph-of-magento-2-for-ai-agents-5g80</guid>
      <description>&lt;h1&gt;
  
  
  I Created a Code Graph of Magento 2 for AI Agents
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Reachability Graph Control (&lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;rgctl&lt;/a&gt;) indexes a repository once into a compact graph — functions, classes, calls, inheritance, communities — then serves deterministic JSON queries. Agents get structural facts without loading 25,000 PHP files into context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wanted to know whether &lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;rgctl&lt;/a&gt; could give a coding agent the same kind of map I wish I had on day one of a Magento engagement: &lt;strong&gt;where modules live&lt;/strong&gt;, &lt;strong&gt;what calls what&lt;/strong&gt;, &lt;strong&gt;which neighborhoods cluster together&lt;/strong&gt;, and &lt;strong&gt;what breaks if I touch a service class&lt;/strong&gt; — without pretending the framework's dependency injection container is fully visible to static analysis.&lt;/p&gt;

&lt;p&gt;This post is use-case driven. If you pair with Cursor, Claude Code, or a custom agent on &lt;strong&gt;large PHP monoliths&lt;/strong&gt; (Magento, Symfony, Laravel at scale), the workflow below is what I actually run. Under the hood I'll cover the new &lt;strong&gt;PHP Tier 1 plugin&lt;/strong&gt; (tree-sitter, not regex), &lt;strong&gt;shallow vs deep extraction&lt;/strong&gt;, and where the graph is honest about what it cannot see.&lt;/p&gt;

&lt;p&gt;The corpus is upstream &lt;a href="https://github.com/magento/magento2" rel="noopener noreferrer"&gt;Magento Open Source&lt;/a&gt; — cloned locally, indexed with rgctl v0.4.10, not a toy fixture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Magento, why agents
&lt;/h2&gt;

&lt;p&gt;Magento 2 is a stress test for static tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~25,000 PHP files&lt;/strong&gt; in &lt;code&gt;app/&lt;/code&gt;, &lt;code&gt;lib/&lt;/code&gt;, and &lt;code&gt;setup/&lt;/code&gt; (excluding &lt;code&gt;vendor/&lt;/code&gt; and &lt;code&gt;generated/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PSR-4 namespaces&lt;/strong&gt;, traits, interfaces, generated code paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symfony-style DI&lt;/strong&gt; — constructor injection, &lt;code&gt;di.xml&lt;/code&gt; preferences, &lt;code&gt;ObjectManager&lt;/code&gt; at the edges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module boundaries&lt;/strong&gt; that do not match folder names in your head&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A human opens &lt;code&gt;module.xml&lt;/code&gt;, greps for &lt;code&gt;preference&lt;/code&gt;, reads &lt;code&gt;di.xml&lt;/code&gt;, and builds intuition over weeks. An agent has a context window and &lt;code&gt;grep&lt;/code&gt;. On a tree this size, "find all usages of &lt;code&gt;OrderRepository&lt;/code&gt;" often means either reading the wrong ten files or stopping at the first plausible hit.&lt;/p&gt;

&lt;p&gt;rgctl's bet is the same one that worked on Kubernetes and Java monoliths in earlier posts: &lt;strong&gt;index once, query many times&lt;/strong&gt;, return &lt;strong&gt;small JSON facts&lt;/strong&gt; the agent can chain across turns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Shallow discover — the structural map
&lt;/h2&gt;

&lt;p&gt;Fetch the corpus (optional — rgctl ships a fetch script for profile repos):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./scripts/fetch-profile-repos.sh
&lt;span class="c"&gt;# → example/magento2/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cold discover on maintainer hardware (release build, empty &lt;code&gt;.rgctl/&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/example/magento2"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; .rgctl
rgctl discover app lib setup &lt;span class="nt"&gt;-l&lt;/span&gt; php &lt;span class="nt"&gt;-e&lt;/span&gt; vendor,generated &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reference numbers from v0.4.10:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wall time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~12 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP files&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25,499&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graph nodes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~266k&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~100k&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak RSS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~2 GB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is the &lt;strong&gt;shallow&lt;/strong&gt; pass: symbols, relations, communities, blast-radius indexes — no CFG, no taint. For an agent's first turn ("what does this codebase look like?"), this is usually enough.&lt;/p&gt;

&lt;p&gt;Artifacts land in &lt;code&gt;{repo}/.rgctl/&lt;/code&gt; — especially &lt;code&gt;graph.snapshot.bin&lt;/code&gt; and dashboard manifests. Agents should treat that directory as read-only cache; add &lt;code&gt;.rgctl/&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood: tree-sitter, not regex
&lt;/h2&gt;

&lt;p&gt;PHP is &lt;strong&gt;Tier 1&lt;/strong&gt; in rgctl — the same class as Java, Go, and Rust. That means a custom &lt;code&gt;LanguagePlugin&lt;/code&gt; in &lt;code&gt;crates/rgctl-lang-php/&lt;/code&gt;, backed by &lt;strong&gt;tree-sitter-php&lt;/strong&gt;, not the generic Tier 2 regex indexer.&lt;/p&gt;

&lt;p&gt;Why that matters for Magento:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What you get on &lt;code&gt;namespace&lt;/code&gt;, traits, &lt;code&gt;match&lt;/code&gt;, attributes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Regex / line parsers&lt;/td&gt;
&lt;td&gt;Fragile; misses nested structures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree-sitter AST&lt;/td&gt;
&lt;td&gt;Grammar-accurate nodes; same pipeline as other Tier 1 languages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The plugin walks the AST and emits &lt;strong&gt;symbols&lt;/strong&gt; (classes, interfaces, traits, enums, methods, functions, arrow functions) and &lt;strong&gt;relations&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Relation&lt;/th&gt;
&lt;th&gt;Example in Magento-style code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Calls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$this-&amp;gt;orderRepository-&amp;gt;save($order)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Extends&lt;/code&gt; / &lt;code&gt;Implements&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;class OrderRepository implements OrderRepositoryInterface&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Uses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;use HasDataChanges;&lt;/code&gt; (trait composition)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Import&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;use Magento\Sales\Api\OrderRepositoryInterface;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Instantiates&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new OrderFactory()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Import-aware static calls:&lt;/strong&gt; &lt;code&gt;OrderRepositoryInterface::class&lt;/code&gt; and &lt;code&gt;SomeService::getInstance()&lt;/code&gt; get &lt;code&gt;to_qualified_hint&lt;/code&gt; from the file's &lt;code&gt;use&lt;/code&gt; map — so resolution can tie a call site to a namespaced target when the name is unique in the index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern PHP surface&lt;/strong&gt; (relevant to extensions and newer core code):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attributes stored in &lt;code&gt;metadata.attributes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Anonymous classes as &lt;code&gt;$Anonymous{line}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Promoted constructor properties on &lt;code&gt;fields[]&lt;/code&gt; (DI-style &lt;code&gt;private readonly OrderRepositoryInterface $orderRepository&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;metadata.unresolved&lt;/code&gt; on dynamic calls (&lt;code&gt;$this-&amp;gt;$method()&lt;/code&gt;, variable callees) — the graph keeps the edge but flags that the target is not statically known&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optional: &lt;code&gt;RGCTL_PHP_ONLY=1&lt;/code&gt; switches to the PHP-only tree-sitter grammar (no inline HTML) for pure &lt;code&gt;.php&lt;/code&gt; trees.&lt;/p&gt;

&lt;h3&gt;
  
  
  What shallow extraction does &lt;em&gt;not&lt;/em&gt; claim
&lt;/h3&gt;

&lt;p&gt;rgctl is not a PHPStan or Psalm competitor. There is no full type checker inside the plugin — only &lt;strong&gt;bound resolution&lt;/strong&gt; from declarations, parameters, fields, and imports. That design choice is intentional: stay fast on whole-repo discover, stay honest when types are interfaces wired at runtime.&lt;/p&gt;

&lt;p&gt;On Magento, the symptom is a &lt;strong&gt;sparse call graph&lt;/strong&gt;: many SCCs, lots of interface-typed receivers, factory and &lt;code&gt;ObjectManager&lt;/code&gt; indirection. The graph still tells you &lt;strong&gt;that&lt;/strong&gt; a method invokes something; it often cannot name the single concrete callee. More on that in Honest limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: Deep discover — CFG, taint, field mutations
&lt;/h2&gt;

&lt;p&gt;When an agent needs &lt;strong&gt;control flow&lt;/strong&gt;, &lt;strong&gt;data-flow-ish facts&lt;/strong&gt;, or &lt;strong&gt;security patterns&lt;/strong&gt;, run deep discover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl discover app lib setup &lt;span class="nt"&gt;-l&lt;/span&gt; php &lt;span class="nt"&gt;-e&lt;/span&gt; vendor,generated &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--with-cfg&lt;/span&gt; &lt;span class="nt"&gt;--with-security&lt;/span&gt; &lt;span class="nt"&gt;--with-taint&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reference cold numbers (same hardware):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wall time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~33 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CFG functions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~100k&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field writes indexed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~58k&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak RSS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~5.7 GB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Deep pass adds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Agent use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CFG / PDG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;inspect&lt;/code&gt;, &lt;code&gt;slice&lt;/code&gt;, &lt;code&gt;cpg flows&lt;/code&gt; — "what happens between line 40 and the &lt;code&gt;save()&lt;/code&gt; call?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Taint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Discover-time patterns: &lt;code&gt;$_FILES&lt;/code&gt;, &lt;code&gt;filter_input&lt;/code&gt;, PDO &lt;code&gt;prepare&lt;/code&gt; / SQL sinks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Field mutations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cpg mutations --type Order&lt;/code&gt; — cart/DTO safety, state change neighborhoods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security scan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Config-like files when &lt;code&gt;--with-security&lt;/code&gt; is on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Magento's core, &lt;strong&gt;taint hit count was very low&lt;/strong&gt; (two flows in our cold run) — framework wrappers and indirection mean discover-time heuristics miss most real sinks. That is a finding, not a failure: agents should not treat "zero taint" as "safe"; they should treat it as "pattern library did not match this framework."&lt;/p&gt;

&lt;p&gt;Warm re-run on the same tree dropped to &lt;strong&gt;~12 s&lt;/strong&gt; wall — cache reuse in &lt;code&gt;.rgctl/&lt;/code&gt; is real for iterative agent sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits: DI and sparse calls
&lt;/h2&gt;

&lt;p&gt;Magento's DI is the elephant in the room. Constructor injection through interfaces is correct at runtime and invisible to naive static resolution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;OrderRepositoryInterface&lt;/span&gt; &lt;span class="nv"&gt;$orderRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rgctl records the &lt;strong&gt;parameter type&lt;/strong&gt; on the constructor and the &lt;strong&gt;field&lt;/strong&gt; on the class. A call like &lt;code&gt;$this-&amp;gt;orderRepository-&amp;gt;get($id)&lt;/code&gt; may not resolve to a unique &lt;code&gt;Calls&lt;/code&gt; edge to &lt;code&gt;Magento\Sales\Model\OrderRepository::get&lt;/code&gt; — because the declared type is the interface, and the binding lives in &lt;code&gt;etc/di.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We track follow-up work in &lt;a href="https://github.com/sshaaf/rgctl/issues/75" rel="noopener noreferrer"&gt;issue #75&lt;/a&gt;: CHA-lite candidate edges, config parsers for &lt;code&gt;di.xml&lt;/code&gt;, and &lt;code&gt;metadata.candidates&lt;/code&gt; on unresolved calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For agents today:&lt;/strong&gt; combine structural queries with module artifacts you already trust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Graph: who implements this interface in-repo?&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"MATCH (c:Class)-[:IMPLEMENTS]-&amp;gt;(i:Interface)
   WHERE i.qualified_name CONTAINS 'OrderRepository'
   RETURN c.qualified_name LIMIT 20"&lt;/span&gt;

&lt;span class="c"&gt;# Source: preferences in DI config (agent reads file or rgrep)&lt;/span&gt;
rgrep &lt;span class="s1"&gt;'OrderRepositoryInterface'&lt;/span&gt; app/code/&lt;span class="k"&gt;**&lt;/span&gt;/etc/di.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph narrows the search space; it does not replace reading &lt;code&gt;di.xml&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent workflow: copy-paste recipes
&lt;/h2&gt;

&lt;p&gt;Install the agent skill once per machine (writes &lt;code&gt;AGENTS.md&lt;/code&gt; recipes into Cursor / Claude config):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--skill&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/magento2
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; discover app lib setup &lt;span class="nt"&gt;-l&lt;/span&gt; php &lt;span class="nt"&gt;-e&lt;/span&gt; vendor,generated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All queries below assume &lt;strong&gt;&lt;code&gt;-f json&lt;/code&gt;&lt;/strong&gt; and parse &lt;code&gt;schema_version&lt;/code&gt; + payload from stdout — never scrape stderr.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Inventory before editing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# How many service classes in a module path?&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"MATCH (n:Class) WHERE n.file_path CONTAINS 'Magento/Sales'
   RETURN n.qualified_name LIMIT 50"&lt;/span&gt;

&lt;span class="c"&gt;# Functions named like plugins (Magento convention)&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"MATCH (f:Function) WHERE f.name LIKE '*Plugin*'
   RETURN f.name, f.file_path LIMIT 30"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Blast radius before a refactor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius save &lt;span class="nt"&gt;--class&lt;/span&gt; OrderRepository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the symbol is ambiguous, rgctl returns remediation text — agents must retry with &lt;code&gt;--class&lt;/code&gt; or &lt;code&gt;path/to/file.php::method&lt;/code&gt;, not pick a random row.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;gatekeeping.policy_status&lt;/code&gt; with a policy file when you want a hard stop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Community = module neighborhood
&lt;/h3&gt;

&lt;p&gt;Community detection clusters functions that call each other densely — often aligned with module boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="nt"&gt;--macro-name&lt;/span&gt; all_communities unused
rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"MATCH (f:Function) WHERE f.community_id = '42' RETURN f LIMIT 30"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent planning "extract this feature to a microservice" can ask for the community around a REST entrypoint instead of listing every file under &lt;code&gt;Controller/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Deep slice on a suspicious method
&lt;/h3&gt;

&lt;p&gt;After &lt;code&gt;--with-cfg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json cpg slice &lt;span class="nt"&gt;--function&lt;/span&gt; execute &lt;span class="nt"&gt;--file&lt;/span&gt; app/code/Magento/Sales/Model/OrderRepository.php
rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json cpg flows app/code/.../OrderRepository.php &lt;span class="nt"&gt;--line&lt;/span&gt; 120 &lt;span class="nt"&gt;--variable&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="nt"&gt;--function&lt;/span&gt; save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Semantic search (optional second index)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl semantic index &lt;span class="nt"&gt;--embedder&lt;/span&gt; &lt;span class="nb"&gt;hash
&lt;/span&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json semantic query &lt;span class="s2"&gt;"cancel order workflow"&lt;/span&gt; &lt;span class="nt"&gt;--limit&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Semantic complements the graph; it does not replace &lt;code&gt;Calls&lt;/code&gt; edges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent rules that actually help
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Run &lt;code&gt;discover&lt;/code&gt; once per branch / after large pulls&lt;/td&gt;
&lt;td&gt;Stale graph → wrong blast radius&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prefer &lt;code&gt;gql&lt;/code&gt; / &lt;code&gt;blast-radius&lt;/code&gt; over reading random &lt;code&gt;Model/&lt;/code&gt; files&lt;/td&gt;
&lt;td&gt;Token budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treat &lt;code&gt;metadata.unresolved&lt;/code&gt; calls as "unknown callee"&lt;/td&gt;
&lt;td&gt;Do not invent targets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On DI-heavy classes, follow &lt;code&gt;IMPLEMENTS&lt;/code&gt; + &lt;code&gt;di.xml&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Graph + config, not graph alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use &lt;code&gt;-f json&lt;/code&gt; only&lt;/td&gt;
&lt;td&gt;Stable machine output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;See &lt;a href="https://shaaf.dev/rgctl/docs/agent-recipes.md" rel="noopener noreferrer"&gt;agent-recipes.md&lt;/a&gt; and &lt;a href="https://github.com/sshaaf/rgctl/blob/main/AGENTS.md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; for the full command matrix.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the graph is enough vs when to use the IDE
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Start with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"What module neighborhood is this controller in?"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gql&lt;/code&gt; + communities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"If I change &lt;code&gt;save()&lt;/code&gt;, what breaks?"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blast-radius&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Does this file call &lt;code&gt;curl_exec&lt;/code&gt; anywhere?"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cpg&lt;/code&gt; / &lt;code&gt;slice&lt;/code&gt; after &lt;code&gt;--with-cfg&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"What is the runtime DI binding?"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;di.xml&lt;/code&gt;, &lt;code&gt;setup:di:compile&lt;/code&gt; output, debugger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Will this PHPStan level pass?"&lt;/td&gt;
&lt;td&gt;PHPStan — different tool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/sshaaf/rgctl/releases/tag/v0.4.10" rel="noopener noreferrer"&gt;rgctl v0.4.10&lt;/a&gt; — PHP Tier 1 in the default binary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small fixture:&lt;/strong&gt; &lt;code&gt;rgctl-tests/ecommerce-php/&lt;/code&gt; — MVC + taint + field-write golden&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large fixture:&lt;/strong&gt; &lt;code&gt;./scripts/fetch-profile-repos.sh&lt;/code&gt; → &lt;code&gt;example/magento2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://shaaf.dev/rgctl/docs/languages/" rel="noopener noreferrer"&gt;languages&lt;/a&gt;, &lt;a href="https://shaaf.dev/rgctl/docs/tier-1-language-support/" rel="noopener noreferrer"&gt;tier-1 PHP parity&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl discover &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; php &lt;span class="nt"&gt;--with-cfg&lt;/span&gt; &lt;span class="nt"&gt;--with-taint&lt;/span&gt;
rgctl &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--skill&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are building agent playbooks for commerce migrations (Magento → something else, or major version jumps), the combination of &lt;strong&gt;fast whole-repo index&lt;/strong&gt;, &lt;strong&gt;honest unresolved edges&lt;/strong&gt;, and &lt;strong&gt;JSON query recipes&lt;/strong&gt; is the piece I wanted documented — not a fake complete call graph that would lie about DI.&lt;/p&gt;

&lt;p&gt;Next up on the graph side: &lt;a href="https://github.com/sshaaf/rgctl/issues/75" rel="noopener noreferrer"&gt;DI-aware call resolution&lt;/a&gt; — &lt;code&gt;di.xml&lt;/code&gt; preferences and interface fan-out without inventing unique targets. Until then, agents that know the limits are more trustworthy than agents that guess.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>php</category>
      <category>magento</category>
      <category>rgctl</category>
    </item>
    <item>
      <title>Blast Radius for Agent-Assisted Development on Kubernetes</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Wed, 02 Sep 2026 20:52:38 +0000</pubDate>
      <link>https://dev.to/sshaaf/blast-radius-for-agent-assisted-development-on-kubernetes-161</link>
      <guid>https://dev.to/sshaaf/blast-radius-for-agent-assisted-development-on-kubernetes-161</guid>
      <description>&lt;h1&gt;
  
  
  Blast Radius for Agent-Assisted Development on Kubernetes
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Reachability Graph Control (rgctl) - AI coding agents default to reading files sequentially. That burns context, misses structure, and produces confident wrong answers about impact and dependencies. rgctl indexes the whole repository once into a rich graph with pre-computed reachability, then serves compact, deterministic query results — so agents (and humans) get the right slice of the codebase without loading it into the prompt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you refactor &lt;code&gt;RESTClientFor&lt;/code&gt; in Kubernetes by hand, you probably do not open a&lt;br&gt;
call-graph tool first. not that I am suggesting you should.. but lets take it as a good example when it comes to impact on the codebase, its dependencies etc. Usually you have an IDE find-references, a mental map of&lt;br&gt;
&lt;code&gt;client-go&lt;/code&gt;, teammates who have been burned before, and CI that will catch what&lt;br&gt;
you missed. For most edits, that workflow is fine.&lt;/p&gt;

&lt;p&gt;The landscape changes when you decide to hand the same task to a coding agent. The agent&lt;br&gt;
has a context window, grep, and no tenure on the team and importantly the LLM has its own limits, depending on which one you use. &lt;br&gt;
On a tree with &lt;strong&gt;181,000 functions&lt;/strong&gt;, asking it to "refactor &lt;code&gt;newPodWorkers&lt;/code&gt; safely" often&lt;br&gt;
produces one of three outcomes: it reads the wrong files, stops at direct&lt;br&gt;
callers and misses transitive impact, or burns most of the turn budget&lt;br&gt;
exploring &lt;code&gt;pkg/kubelet/&lt;/code&gt; without ever reaching &lt;code&gt;cmd/kubelet/main&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This post is for brave souls who work or plan to work &lt;strong&gt;with&lt;/strong&gt; agents on large codebases. It&lt;br&gt;
explains why &lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;rgctl&lt;/a&gt; blast radius belongs in&lt;br&gt;
that workflow, what you should expect from the output, and where your existing&lt;br&gt;
tools are still the right choice. The examples use the upstream&lt;br&gt;
&lt;a href="https://github.com/kubernetes/kubernetes" rel="noopener noreferrer"&gt;kubernetes/kubernetes&lt;/a&gt; repository —&lt;br&gt;
cloned locally and indexed with rgctl, not a trimmed or synthetic corpus.&lt;/p&gt;
&lt;h2&gt;
  
  
  How blast radius is calculated in rgctl
&lt;/h2&gt;

&lt;p&gt;Blast radius answers one question: &lt;strong&gt;if I change this function, what upstream&lt;br&gt;
code could be affected?&lt;/strong&gt; It walks the call graph &lt;strong&gt;backwards&lt;/strong&gt; along &lt;code&gt;CALLS&lt;/code&gt;&lt;br&gt;
edges — from callee to caller, caller's caller, and so on — and returns three&lt;br&gt;
things:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct callers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Functions that call the target directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Impact zone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full transitive closure of upstream callers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Score (0–100)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A capped impact rating derived from both counts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The score is not PageRank (even though rgctl can also calculate it) and its not guestaming it either. rgctl combines two components:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Formula&lt;/th&gt;
&lt;th&gt;Cap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct callers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;direct_count × 25&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40 points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transitive impact&lt;/td&gt;
&lt;td&gt;&lt;code&gt;impact_count × 0.05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;60 points&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;score = min(direct_component + transitive_component, 100)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A symbol with no upstream callers in the static graph scores &lt;strong&gt;0&lt;/strong&gt;. A shared&lt;br&gt;
helper with dozens of direct callers and a large impact zone lands in the&lt;br&gt;
&lt;strong&gt;40–50&lt;/strong&gt; range; scores above &lt;strong&gt;50&lt;/strong&gt; flag architectural hotspots where both&lt;br&gt;
fan-in dimensions are high.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why agents get sub-second answers on Kubernetes
&lt;/h3&gt;

&lt;p&gt;Lets take a look at how this is made possible, or as my agent friends always say "lets delve into it.."&lt;br&gt;
Agents work in multi-turn sessions. A live breadth-first search over 1.7M&lt;br&gt;
edges on every "who calls X?" would make that unusable. rgctl does the expensive&lt;br&gt;
work once, at &lt;code&gt;discover&lt;/code&gt; time, and serves lookups from pre-built snapshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Build the call graph:&lt;/strong&gt; &lt;code&gt;discover&lt;/code&gt; parses source, extracts function&lt;br&gt;
nodes and &lt;code&gt;CALLS&lt;/code&gt; edges, and writes &lt;code&gt;graph.snapshot.bin&lt;/code&gt; plus a dedicated&lt;br&gt;
&lt;code&gt;blast_engine.snapshot.bin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fblast-radius-step1-call-graph.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fblast-radius-step1-call-graph.svg" alt="Step 1: build the call graph" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Collapse cycles:&lt;/strong&gt; Mutual recursion is common in real codebases&lt;br&gt;
(7,097 circular call dependencies in Kubernetes). rgctl runs Kosaraju's&lt;br&gt;
algorithm on the &lt;code&gt;CALLS&lt;/code&gt; subgraph and condenses each strongly connected&lt;br&gt;
component (SCC) into a single node, producing a directed acyclic graph (DAG).&lt;br&gt;
Cycles no longer break reachability analysis.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fblast-radius-step2-scc-collapse.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fblast-radius-step2-scc-collapse.svg" alt="Step 2: collapse cycles with SCC condensation" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Precompute reverse reachability.&lt;/strong&gt; On the condensed DAG, rgctl&lt;br&gt;
propagates reachability in reverse topological order, storing the result as&lt;br&gt;
dense bitsets — one per SCC. A query becomes a bitset read: &lt;strong&gt;O(1) lookup&lt;/strong&gt;&lt;br&gt;
on the condensed graph, not a per-request graph walk.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fblast-radius-step3-reachability.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fblast-radius-step3-reachability.svg" alt="Step 3: precompute reverse reachability" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Serve through tiers:&lt;/strong&gt; At query time, rgctl tries the fastest path&lt;br&gt;
first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;T0&lt;/strong&gt; — macro call lookup cache hit (&lt;code&gt;macro_call_index.db&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T1&lt;/strong&gt; — mmap blast engine with precomputed SCC reachability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T3&lt;/strong&gt; — full graph hydrate (only when you need &lt;code&gt;--with-slices&lt;/code&gt; or
&lt;code&gt;--policy-file&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fblast-radius-query-pipeline.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fblast-radius-query-pipeline.svg" alt="rgctl blast-radius query pipeline" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Kubernetes that means a &lt;strong&gt;~13 s&lt;/strong&gt; one-time index, then &lt;strong&gt;~1 s&lt;/strong&gt; per symbol&lt;br&gt;
query — no daemon, no remote service. Re-run &lt;code&gt;discover&lt;/code&gt; after large merges so&lt;br&gt;
the graph stays current. The full breakdown is in the&lt;br&gt;
&lt;a href="https://shaaf.dev/rgctl/docs/design/blast-radius-design" rel="noopener noreferrer"&gt;design doc&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The gap agents cannot close on their own
&lt;/h2&gt;

&lt;p&gt;A typical agent turn looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:   "Change how newPodWorkers handles static pods — keep behavior, improve readability."
Agent: [reads pod_workers.go] [greps "newPodWorkers"] [edits file] [maybe runs tests]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before any line changes, the agent has to answer a structural question: &lt;em&gt;who&lt;br&gt;
depends on this symbol, transitively?&lt;/em&gt; The usual approaches all fall short at&lt;br&gt;
Kubernetes scale:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the agent tries&lt;/th&gt;
&lt;th&gt;What it gets&lt;/th&gt;
&lt;th&gt;What it misses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read one file&lt;/td&gt;
&lt;td&gt;Local implementation&lt;/td&gt;
&lt;td&gt;Callers in other packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;grep newPodWorkers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text matches&lt;/td&gt;
&lt;td&gt;Upstream paths two or three hops away&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rely on training data&lt;/td&gt;
&lt;td&gt;Plausible Kubernetes architecture&lt;/td&gt;
&lt;td&gt;This checkout's actual call graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read 20 caller files&lt;/td&gt;
&lt;td&gt;More context, fewer tokens left for the edit&lt;/td&gt;
&lt;td&gt;No guaranteed closure; easy to stop early&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;How many times we have seen an agent confidently edit&lt;br&gt;
a helper and skip the integration test package that actually exercises it. Blast radius closes that gap with a single subprocess (point &lt;code&gt;rgctl -r&lt;/code&gt; at your&lt;br&gt;
local clone of &lt;a href="https://github.com/kubernetes/kubernetes" rel="noopener noreferrer"&gt;kubernetes/kubernetes&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; /path/to/kubernetes &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius &amp;lt;Symbol&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is a few hundred tokens of structured JSON instead of tens of&lt;br&gt;
thousands of lines of source the agent would otherwise have to read and still&lt;br&gt;
fail to synthesize correctly. You can run the same command yourself in a&lt;br&gt;
terminal — the point is not to replace your IDE, but to give the agent (and&lt;br&gt;
you, when reviewing its plan) a shared, verifiable fact base.&lt;/p&gt;
&lt;h2&gt;
  
  
  The loop that actually works
&lt;/h2&gt;

&lt;p&gt;rgctl is built around a loop that matches how Cursor, Claude Code, and similar&lt;br&gt;
tools operate today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Your prompt        →  natural language ("what breaks if I change X?")
2. Subprocess         →  rgctl -f json blast-radius &amp;lt;Symbol&amp;gt;
3. Structured facts   →  parse schema_version + payload
4. Reasoning          →  risk summary, test plan, edit scope
5. Edit / check       →  re-query if the graph may be stale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract lives in &lt;a href="https://github.com/sshaaf/rgctl/blob/main/AGENTS.md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt;&lt;br&gt;
and installs as a project skill via &lt;code&gt;rgctl install --skill&lt;/code&gt;. Agents parse&lt;br&gt;
&lt;strong&gt;stdout JSON only&lt;/strong&gt; — not stderr, not the dashboard unless you ask for a UI.&lt;/p&gt;

&lt;p&gt;When you review an agent's plan, ask whether it grounded impact analysis in&lt;br&gt;
something like step 2, or whether it inferred callers from grep and memory.&lt;br&gt;
That distinction is usually visible in the quality of the proposed test plan.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Kubernetes is a good stress test
&lt;/h2&gt;

&lt;p&gt;All numbers below come from indexing a fresh clone of&lt;br&gt;
&lt;a href="https://github.com/kubernetes/kubernetes" rel="noopener noreferrer"&gt;kubernetes/kubernetes&lt;/a&gt; — the same&lt;br&gt;
tree you get from GitHub, not a fixture or subset. When working inside the&lt;br&gt;
rgctl repo, &lt;code&gt;./scripts/fetch-profile-repos.sh&lt;/code&gt; clones it to &lt;code&gt;example/kubernetes&lt;/code&gt;;&lt;br&gt;
otherwise clone it anywhere and pass that path to &lt;code&gt;-r&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source files indexed&lt;/td&gt;
&lt;td&gt;26,141&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functions analyzed&lt;/td&gt;
&lt;td&gt;181,680&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graph edges&lt;/td&gt;
&lt;td&gt;1,789,412&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functions named &lt;code&gt;Run&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;513&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold &lt;code&gt;discover&lt;/code&gt; time&lt;/td&gt;
&lt;td&gt;~13 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical &lt;code&gt;blast-radius&lt;/code&gt; query&lt;/td&gt;
&lt;td&gt;~1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;No one should have to read 26k Go files to answer "what is the upstream impact of this symbol?". &lt;br&gt;
IMHO it just doesnt make sense, whether a programmer or the helper agent.&lt;br&gt;
Index once:&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/kubernetes/kubernetes.git
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; kubernetes discover &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;✓] Loaded 26141 files -&amp;gt; 740986 nodes, 1789412 edges
&lt;span class="o"&gt;[&lt;/span&gt;✓] Analyzed 181680 functions
&lt;span class="o"&gt;[&lt;/span&gt;✓] Completed &lt;span class="k"&gt;in &lt;/span&gt;13.3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;discover&lt;/code&gt; writes artifacts to &lt;code&gt;kubernetes/.rgctl/&lt;/code&gt; (or wherever you cloned),&lt;br&gt;
including &lt;code&gt;blast_engine.snapshot.bin&lt;/code&gt; and a macro call lookup cache. Queries&lt;br&gt;
are mmap lookups, not live graph walks over 1.7M edges.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example: changing &lt;code&gt;RESTClientFor&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Suppose you ask your agent to add a default timeout to &lt;code&gt;RESTClientFor&lt;/code&gt; in&lt;br&gt;
&lt;code&gt;client-go/rest/config.go&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without blast radius, a competent agent will open the file, grep for&lt;br&gt;
&lt;code&gt;RESTClientFor(&lt;/code&gt;, find a handful of call sites, edit, and suggest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./staging/src/k8s.io/client-go/rest/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not wrong. It is incomplete.&lt;/p&gt;

&lt;p&gt;With blast radius, one query returns the full upstream picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; kubernetes &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius RESTClientFor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"canonical_fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RESTClientFor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kubernetes/staging/src/k8s.io/client-go/rest/config.go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"func RESTClientFor(config *Config) (*RESTClient, error) {"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;44.75&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"topology"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"factoryImpl.RESTClient"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NewForConfig"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Framework.BeforeEach"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NewKubectlCommand"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".../cmd/kubectl/kubectl.go"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this you — and the agent — can derive a concrete plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Score 44.8&lt;/strong&gt; — not a leaf function; treat as a shared infrastructure change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;38 direct callers&lt;/strong&gt; — client factories, kubectl wiring, integration and e2e setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact zone reaches &lt;code&gt;cmd/kubectl/main&lt;/code&gt;&lt;/strong&gt; — unit tests under &lt;code&gt;rest/&lt;/code&gt; are necessary
but not sufficient.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might reach the same conclusion with IDE find-references and ten minutes of&lt;br&gt;
clicking. The agent will not do that reliably unless you give it a tool that&lt;br&gt;
returns the closure in one shot.&lt;/p&gt;
&lt;h3&gt;
  
  
  Context cost
&lt;/h3&gt;

&lt;p&gt;This matters if you care about agent turn quality, not just correctness:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Approx. context cost&lt;/th&gt;
&lt;th&gt;Caller coverage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read &lt;code&gt;config.go&lt;/code&gt; + five caller files&lt;/td&gt;
&lt;td&gt;15k–40k tokens&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rgctl -f json blast-radius RESTClientFor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5k–2k tokens&lt;/td&gt;
&lt;td&gt;Full static closure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The agent keeps context for the actual edit and for reasoning about risk, rather&lt;br&gt;
than spending it reconstructing a call graph from grep output.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example: refactoring &lt;code&gt;newPodWorkers&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A subtler case. You ask the agent to extract pod worker configuration into a&lt;br&gt;
struct in &lt;code&gt;pkg/kubelet/pod_workers.go&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Grep finds &lt;strong&gt;four&lt;/strong&gt; direct call sites. That sounds low-risk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; kubernetes blast-radius newPodWorkers &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--file&lt;/span&gt; pkg/kubelet/pod_workers.go
Blast radius &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'newPodWorkers'&lt;/span&gt;
  Score: 41.5/100
  Direct callers: 4
  Impact zone: 30
  Callers: TestFakePodWorkers, createPodWorkersWithLogger, NewMainKubelet,
           TestVolumeAttachLimitExceededCleanup
  Impact: ... NewMainKubelet, createAndInitKubelet, RunKubelet, startKubelet,
          Run, run, NewKubeletCommand, main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four direct callers, but &lt;strong&gt;30&lt;/strong&gt; in the impact zone — the chain runs through&lt;br&gt;
kubelet construction all the way to &lt;code&gt;cmd/kubelet/main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An agent that stops at grep will under-scope the test plan. One that reads blast&lt;br&gt;
radius should propose &lt;code&gt;pkg/kubelet&lt;/code&gt; tests &lt;strong&gt;and&lt;/strong&gt; the &lt;code&gt;cmd/kubelet&lt;/code&gt; startup path,&lt;br&gt;
and mention &lt;code&gt;RunKubelet&lt;/code&gt; in the change summary.&lt;/p&gt;

&lt;p&gt;You might get there by tracing callers in your IDE or by running&lt;br&gt;
&lt;code&gt;make test WHAT=pkg/kubelet&lt;/code&gt; and seeing what breaks. Blast radius is how you&lt;br&gt;
front-load that knowledge before the agent edits, not after CI fails.&lt;/p&gt;
&lt;h3&gt;
  
  
  Use &lt;code&gt;--depth&lt;/code&gt; to control noise
&lt;/h3&gt;

&lt;p&gt;Kubernetes impact zones include a lot of test harness symbols. When you want a&lt;br&gt;
local picture rather than the full release surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; kubernetes blast-radius newPodWorkers &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--file&lt;/span&gt; pkg/kubelet/pod_workers.go &lt;span class="nt"&gt;--depth&lt;/span&gt; 2
  Impact zone: 8   &lt;span class="c"&gt;# down from 30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full closure for "what could this break in production?" Bounded depth for&lt;br&gt;
"what is immediately upstream?" Same command, one flag.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example: the score-zero trap
&lt;/h2&gt;

&lt;p&gt;You ask: "Kubelet.Run looks unused — can we remove it?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; kubernetes blast-radius Kubelet::Run &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--file&lt;/span&gt; pkg/kubelet/kubelet.go
  Score: 0.0/100
  Direct callers: 0
  Impact zone: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A score of zero here does &lt;strong&gt;not&lt;/strong&gt; mean safe to delete. &lt;code&gt;Kubelet.Run&lt;/code&gt; is the&lt;br&gt;
kubelet's main loop; it is started from &lt;code&gt;cmd/kubelet&lt;/code&gt;. For this symbol the gap&lt;br&gt;
is primarily &lt;strong&gt;interface dispatch&lt;/strong&gt; — fast static analysis (without heavy&lt;br&gt;
pointer analysis) struggles to link a caller that holds an interface type to the&lt;br&gt;
concrete &lt;code&gt;Kubelet&lt;/code&gt; implementation. Goroutine spawns can produce the same blind&lt;br&gt;
spot: the graph may not record them as &lt;code&gt;CALLS&lt;/code&gt; edges either.&lt;/p&gt;

&lt;p&gt;You would not delete it. An agent might, if it treats the score as ground truth.&lt;/p&gt;

&lt;p&gt;This is the most important caveat when relying on blast radius in agent&lt;br&gt;
workflows: &lt;strong&gt;the output is a risk signal for reasoning, not permission to&lt;br&gt;
merge.&lt;/strong&gt; When score is zero on a symbol you know is hot, disambiguate with&lt;br&gt;
&lt;code&gt;--class&lt;/code&gt; or &lt;code&gt;--file&lt;/code&gt;, then grep for call sites the graph cannot see. The&lt;br&gt;
&lt;a href="https://github.com/sshaaf/rgctl/blob/main/skills/rgctl/SKILL.md" rel="noopener noreferrer"&gt;rgctl skill&lt;/a&gt;&lt;br&gt;
encodes this failure mode so agents do not treat "no static callers" as "unused."&lt;/p&gt;
&lt;h2&gt;
  
  
  What you get in the JSON
&lt;/h2&gt;

&lt;p&gt;Whether you or your agent runs the query, these are the fields worth paying&lt;br&gt;
attention to:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;target.canonical_fqn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disambiguated symbol — not just a name that matches 513 &lt;code&gt;Run&lt;/code&gt; functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;target.file_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anchor for edits and citations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metrics.score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quick risk tier: 0 / ~25–50 / 50+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metrics.direct_callers_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Immediate fan-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metrics.impact_zone_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transitive fan-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;topology.direct_callers[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;First-hop checklist for tests and review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;topology.impact_zone[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full upstream checklist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;topology.scc_component_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hint when the symbol sits in a cycle-heavy neighborhood&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gatekeeping.policy_status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;VIOLATED&lt;/code&gt; when a policy file rejects the change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Text output is fine for a quick terminal check. Agent workflows and CI gates&lt;br&gt;
should use &lt;code&gt;-f json&lt;/code&gt; per the &lt;a href="https://shaaf.dev/rgctl/docs/json-api" rel="noopener noreferrer"&gt;JSON API&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Disambiguation on a real tree
&lt;/h2&gt;

&lt;p&gt;Kubernetes has &lt;strong&gt;513&lt;/strong&gt; functions named &lt;code&gt;Run&lt;/code&gt;. A bare symbol query fails loudly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; kubernetes blast-radius Run
Error: Symbol &lt;span class="s1"&gt;'Run'&lt;/span&gt; is ambiguous. Found 513 matches.
Remediation: rgctl blast-radius &lt;span class="s2"&gt;"ClassName::Run"&lt;/span&gt;
              rgctl blast-radius &lt;span class="s2"&gt;"path/to/file.go::Run"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You click the right reference in your IDE. An agent needs the remediation path&lt;br&gt;
in the error output — and a rule that says never pick a random row from the&lt;br&gt;
disambiguation table. In practice: retry with &lt;code&gt;ClassName::symbol&lt;/code&gt;, &lt;code&gt;--file&lt;/code&gt;, or&lt;br&gt;
&lt;code&gt;--class&lt;/code&gt; before editing.&lt;/p&gt;

&lt;p&gt;If you are setting up agent rules or reviewing agent output, check whether&lt;br&gt;
disambiguation happened before the blast-radius numbers were quoted.&lt;/p&gt;
&lt;h2&gt;
  
  
  Policy gates in agent-driven PRs
&lt;/h2&gt;

&lt;p&gt;You can wire blast radius into automated guardrails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; kubernetes &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius RESTClientFor &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy.json
&lt;span class="c"&gt;# gatekeeping.policy_status: "VIOLATED", exit code 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an agent proposing a large refactor, &lt;code&gt;VIOLATED&lt;/code&gt; should mean stop and report&lt;br&gt;
— not silently proceed. The &lt;code&gt;check&lt;/code&gt; command runs the same rules across all&lt;br&gt;
touched symbols in one pass, which fits agent-opened PRs where you want a hard&lt;br&gt;
ceiling on impact zone size.&lt;/p&gt;

&lt;p&gt;You might override that judgment on a case-by-case basis. Agents should not&lt;br&gt;
override it unless you explicitly say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  When your IDE is still the better tool
&lt;/h2&gt;

&lt;p&gt;Blast radius is not the right first move for every task — even in agent-assisted&lt;br&gt;
work:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your question&lt;/th&gt;
&lt;th&gt;Often better&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What calls this in the file I have open?&lt;/td&gt;
&lt;td&gt;IDE find references&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Will CI pass?&lt;/td&gt;
&lt;td&gt;Run the tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is this a public API?&lt;/td&gt;
&lt;td&gt;Module boundaries, docs, review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who owns this package?&lt;/td&gt;
&lt;td&gt;CODEOWNERS, team knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What changed in this branch?&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git diff&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Blast radius earns its place when you need &lt;strong&gt;transitive upstream impact as a&lt;br&gt;
bounded fact&lt;/strong&gt; — before an agent edits a shared helper, before you approve its&lt;br&gt;
test plan, or before a high-fan-in change lands in a PR. For local edits where&lt;br&gt;
you already know the blast zone, skip it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static analysis limits (both of you should know these)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go interface dispatch&lt;/strong&gt; — methods like &lt;code&gt;SyncPod&lt;/code&gt; may show zero callers; the
graph does not always resolve interface-typed receivers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflection and plugins&lt;/strong&gt; — invisible to static analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test-heavy impact zones&lt;/strong&gt; — Kubernetes lists many &lt;code&gt;Test*&lt;/code&gt; symbols; use
&lt;code&gt;--depth&lt;/code&gt; or path filters when you care about production paths only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents inventing callers&lt;/strong&gt; — if a caller is not in &lt;code&gt;topology&lt;/code&gt;, it was not
in the query result; treat that as a hallucination risk&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get the tool:&lt;/strong&gt; Grab &lt;code&gt;rgctl&lt;/code&gt; from &lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test it on your own codebase:&lt;/strong&gt; Navigate to your project root and run &lt;code&gt;rgctl discover . --export-migration-hints&lt;/code&gt; to index your application.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/kubernetes"&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; discover &lt;span class="nb"&gt;.&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius RESTClientFor
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius newPodWorkers &lt;span class="nt"&gt;--file&lt;/span&gt; pkg/kubelet/pod_workers.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are already in the rgctl repo, &lt;code&gt;./scripts/fetch-profile-repos.sh&lt;/code&gt; clones&lt;br&gt;
kubernetes/kubernetes into &lt;code&gt;example/kubernetes&lt;/code&gt; — use that path for &lt;code&gt;-r&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;A useful sanity-check prompt for your agent:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use rgctl to find the blast radius of RESTClientFor, and tell me what tests&lt;br&gt;
we should run before changing it based on the upstream callers.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The explicit tool invocation matters — agents sometimes skip subprocesses unless&lt;br&gt;
the prompt names them. A good answer runs &lt;code&gt;rgctl -f json blast-radius&lt;/code&gt; and cites&lt;br&gt;
&lt;code&gt;metrics&lt;/code&gt; and &lt;code&gt;topology&lt;/code&gt;. A weak one greps the tree and guesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://shaaf.dev/rgctl/docs/guides/blast-radius-analysis/" rel="noopener noreferrer"&gt;Blast Radius Analysis guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shaaf.dev/rgctl/docs/design/blast-radius-design" rel="noopener noreferrer"&gt;Blast radius design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sshaaf/rgctl/blob/main/AGENTS.md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; — agent contract and high-value commands&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shaaf.dev/rgctl/docs/json-api" rel="noopener noreferrer"&gt;JSON API&lt;/a&gt; — schema v2 field reference&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>go</category>
      <category>kubernetes</category>
      <category>rgctl</category>
    </item>
    <item>
      <title>Decomposing a Monolith into Microservices with Call Graph Analysis</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Wed, 02 Sep 2026 20:52:35 +0000</pubDate>
      <link>https://dev.to/sshaaf/decomposing-a-monolith-into-microservices-with-call-graph-analysis-3oii</link>
      <guid>https://dev.to/sshaaf/decomposing-a-monolith-into-microservices-with-call-graph-analysis-3oii</guid>
      <description>&lt;h1&gt;
  
  
  Decomposing a Monolith into Microservices with Call Graph Analysis
&lt;/h1&gt;

&lt;p&gt;Splitting a monolith into microservices is a problem of finding boundaries. The&lt;br&gt;
wrong boundaries produce distributed monoliths -- microservices that cannot be&lt;br&gt;
deployed independently because they call each other synchronously for every&lt;br&gt;
operation. The right boundaries follow natural seams in the code: clusters of&lt;br&gt;
functions that are tightly coupled internally but loosely connected to the rest&lt;br&gt;
of the system. This post picks up the CoolStore monolith from&lt;br&gt;
&lt;a href="https://shaaf.dev/post/migrating-coolstore-monolith-to-quarkus-with-rgctl/" rel="noopener noreferrer"&gt;migrating it to Quarkus with rgctl&lt;/a&gt;&lt;br&gt;
and applies the same graph analysis to the next question: where to cut it into&lt;br&gt;
services.&lt;/p&gt;

&lt;p&gt;This post walks through a concrete decomposition of a Java EE monolith using&lt;br&gt;
static call-graph analysis. I use &lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;rgctl&lt;/a&gt;&lt;br&gt;
to build the function-level call graph, detect community structure, measure&lt;br&gt;
coupling with blast radius, and identify the cuts that produce viable&lt;br&gt;
microservice boundaries. The goal is not to argue that you should decompose&lt;br&gt;
every monolith -- it is to show how graph analysis replaces intuition with&lt;br&gt;
structural evidence when you decide to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;before we move forward, yes you can use the same functionality in tandem with an code agent as well. This is possible by using the skill provided by rgctl. The following &lt;a href="https://shaaf.dev/rgctl/docs/guides/agent-skill/" rel="noopener noreferrer"&gt;guide explains how the skill works&lt;/a&gt; and how to install it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The Monolith
&lt;/h2&gt;

&lt;p&gt;The CoolStore is a Java EE 7 e-commerce application originally deployed on&lt;br&gt;
WebLogic. It has 25 classes across 5 packages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-monolith-packages.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-monolith-packages.svg" alt="CoolStore monolith package layout"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application handles four distinct business operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Catalog browsing&lt;/strong&gt; -- list products, look up inventory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopping cart&lt;/strong&gt; -- add/remove items, price calculation, shipping, promotions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order processing&lt;/strong&gt; -- checkout publishes to JMS, MDBs persist the order&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory management&lt;/strong&gt; -- deduct stock on order, low-stock alerts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the monolith, these operations share a single database, a single JMS topic,&lt;br&gt;
and freely inject each other's EJBs. The question is: which of these can become&lt;br&gt;
independent services, and where are the coupling points that make that hard?&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Build the Call Graph
&lt;/h2&gt;

&lt;p&gt;All commands below assume the rgctl repository root, with the CoolStore example&lt;br&gt;
at &lt;code&gt;example/coolstore-weblogic&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic discover &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--with-cfg&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--with-harmonic&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--with-security&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--export-migration-hints&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; rgctl discover
&lt;span class="go"&gt;[!] Deep analysis enabled (--with-cfg / --with-taint).
&lt;/span&gt;&lt;span class="gp"&gt;[✓] Loaded 1227 files from snapshot -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;17417 nodes, 52920 edges
&lt;span class="go"&gt;[✓] Analyzed 7526 functions (avg complexity: 1.0)
[✓] Detected 13565 communities (modularity: 0.29)
[!] Found 186 circular dependencies

✓ Security analysis:
  Potential secrets found: 5

✓ Control flow analysis:
  Field writes indexed: 3299
  CFG/PDG/Dominance: 6585 functions analyzed
  Skipped: 941 functions (unsupported language or parse error)

[✓] rgctl discover finished in 16.4s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;186 circular dependencies. In a monolith, this is typical -- EJBs inject each&lt;br&gt;
other, services call back into the REST layer, model objects are passed across&lt;br&gt;
every boundary. But for microservice decomposition, every circular dependency&lt;br&gt;
is a potential distributed coupling problem. I need to find which cycles can&lt;br&gt;
be broken and which are load-bearing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Detect Community Structure
&lt;/h2&gt;

&lt;p&gt;rgctl uses label-propagation community detection on the call graph. This&lt;br&gt;
algorithm finds clusters of functions that call each other densely but have&lt;br&gt;
sparse connections to the outside. These clusters are candidates for&lt;br&gt;
microservice boundaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="nt"&gt;--macro-name&lt;/span&gt; all_communities unused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering to communities with 2+ members that contain Java application code&lt;br&gt;
(community IDs are assigned at &lt;code&gt;discover&lt;/code&gt; time; labels are the stable handle&lt;br&gt;
when comparing runs on the same snapshot):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Community&lt;/th&gt;
&lt;th&gt;Members&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Dominant Classes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8064&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;coolstore.model::&lt;/code&gt;&lt;br&gt;&lt;code&gt;APPLICATION_JSON&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CartEndpoint, ShoppingCartService, ShoppingCart, ShippingService, PromoService&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4266&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;coolstore.model::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getCartItemTotal&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ShoppingCartService, ShippingService, PromoService, Transformers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11973&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;coolstore.model::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getProductByItemId&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ProductEndpoint, ProductService, Transformers, Product, CatalogItemEntity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11250&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;coolstore.service::&lt;/code&gt;&lt;br&gt;&lt;code&gt;CatalogItemEntity&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CatalogService, InventoryEntity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4058&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;coolstore.service::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getPercentOff&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;PromoService, Promotion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14643&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;coolstore.rest::Order&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OrderEndpoint, OrderService&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's look at what the community detection is telling me by examining the&lt;br&gt;
members of each cluster.&lt;/p&gt;
&lt;h3&gt;
  
  
  Community 8064 (30 members): Cart Operations
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"MATCH (f:Function) WHERE f.community_id = '8064' RETURN f LIMIT 50"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CartEndpoint.add             CartEndpoint.delete
CartEndpoint.set             CartEndpoint.dedupeCartItems
CartEndpoint.getCart         CartEndpoint.checkout
ShoppingCartService.getShoppingCart   ShoppingCartService.getProduct
ShoppingCart.addShoppingCartItem      ShoppingCart.removeShoppingCartItem
ShoppingCart.getShoppingCartItemList  ShoppingCart.setShoppingCartItemList
ShoppingCartItem.setQuantity         ShoppingCartItem.setPrice
ShoppingCartItem.setProduct          ShoppingCartItem.getQuantity
ShoppingCartItem.toOrderItem         Product.getPrice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the cart CRUD cluster -- adding, removing, and listing items. The&lt;br&gt;
algorithm grouped the REST endpoint, the service, and the relevant model&lt;br&gt;
methods into one community because they form a tight call cycle.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pricing and Shipping (community 4266)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ShoppingCartService.priceShoppingCart   ShoppingCartService.initShoppingCartForPricing
ShoppingCartService.lookupShippingServiceRemote
ShippingService.calculateShipping      ShippingService.calculateShippingInsurance
ShippingService.getPercentOfTotal
PromoService.applyShippingPromotions
ShoppingCart.getCartItemTotal          ShoppingCart.setCartItemTotal
ShoppingCart.setShippingTotal          ShoppingCart.getShippingTotal
ShoppingCart.setCartTotal              ShoppingCart.setCartItemPromoSavings
ShoppingCartItem.getPromoSavings
ShoppingCart.setShippingPromoSavings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the pricing engine -- computing totals, shipping costs, and&lt;br&gt;
applying shipping promotions. It pulls in &lt;code&gt;ShippingService&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;PromoService.applyShippingPromotions&lt;/code&gt; because &lt;code&gt;priceShoppingCart&lt;/code&gt; calls both&lt;br&gt;
directly.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cart Serialization and Item Promotions
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ShoppingCartOrderProcessor.process
Transformers.shoppingCartToJson        Transformers.$lambda$0
Transformers.$lambda$1
PromoService.applyCartItemPromotions   PromoService.getPromotions
Promotion.getPercentOff                Promotion.getItemId
ShoppingCartItem.setPromoSavings
ShoppingCart.getCartItemPromoSavings    ShoppingCart.getCartTotal
ShoppingCart.getShippingPromoSavings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This cluster links cart serialization (for JMS publishing) with item-level&lt;br&gt;
promotions. The connection is through &lt;code&gt;ShoppingCartOrderProcessor.process&lt;/code&gt;,&lt;br&gt;
which serializes the cart, and &lt;code&gt;PromoService.applyCartItemPromotions&lt;/code&gt;, which&lt;br&gt;
reads and writes cart item prices.&lt;/p&gt;
&lt;h3&gt;
  
  
  Order Fulfillment and Inventory (community 11250 + order MDB cluster)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderServiceMDB.onMessage             OrderServiceMDB.$lambda$0
InventoryNotificationMDB.onMessage    InventoryNotificationMDB.$lambda$0
OrderService.save
CatalogService.getCatalogItemById     CatalogService.updateInventoryItems
CatalogItemEntity.getInventory
InventoryEntity.getQuantity           InventoryEntity.setQuantity
Order.getItemList                     OrderItem.getProductId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the order fulfillment pipeline -- the two MDBs that listen on the JMS&lt;br&gt;
topic, persist the order, and update inventory. &lt;code&gt;CatalogService&lt;/code&gt; appears here&lt;br&gt;
because both MDBs use it: &lt;code&gt;OrderServiceMDB&lt;/code&gt; calls &lt;code&gt;updateInventoryItems&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;InventoryNotificationMDB&lt;/code&gt; calls &lt;code&gt;getCatalogItemById&lt;/code&gt; to check stock levels.&lt;/p&gt;
&lt;h3&gt;
  
  
  Product Catalog (community 11973)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ProductEndpoint.listAll               ProductEndpoint.getProduct
ProductService.getProducts            ProductService.getProductByItemId
ProductService.$lambda$0
CatalogService.getCatalogItems
Transformers.toProduct
Product.setItemId  Product.setPrice  Product.setLocation
Product.setQuantity  Product.setLink  Product.setDesc  Product.setName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Product listing and lookup. &lt;code&gt;CatalogService.getCatalogItems&lt;/code&gt; provides the JPA&lt;br&gt;
query, &lt;code&gt;Transformers.toProduct&lt;/code&gt; maps entities to DTOs, and &lt;code&gt;ProductEndpoint&lt;/code&gt;&lt;br&gt;
exposes the REST API.&lt;/p&gt;
&lt;h3&gt;
  
  
  Order Queries (community 14643)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderEndpoint.listAll      OrderEndpoint.getOrder
OrderService.getOrders     OrderService.getOrderById
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Simple read path for orders. Just an endpoint and a service with JPA queries.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Map the Proposed Microservice Boundaries
&lt;/h2&gt;

&lt;p&gt;The communities suggest four natural services, but with complications. Let me&lt;br&gt;
map them out and then use blast radius to quantify the coupling at each boundary.&lt;/p&gt;
&lt;h3&gt;
  
  
  Proposed services
&lt;/h3&gt;

&lt;p&gt;Based on the community structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-proposed-services.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-proposed-services.svg" alt="Proposed microservice boundaries from community detection"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The communities group naturally into three services. But look at the problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;CatalogService&lt;/code&gt; appears in two communities&lt;/strong&gt; -- it is called by the&lt;br&gt;
product listing path (community 11973) and by the order fulfillment path&lt;br&gt;
(community 11250 -- MDBs call &lt;code&gt;CatalogService&lt;/code&gt; for inventory operations).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Transformers&lt;/code&gt; is split across three services&lt;/strong&gt; -- &lt;code&gt;toProduct&lt;/code&gt; belongs&lt;br&gt;
with catalog, &lt;code&gt;shoppingCartToJson&lt;/code&gt; with cart, &lt;code&gt;jsonToOrder&lt;/code&gt; with orders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;PromoService&lt;/code&gt; spans two communities&lt;/strong&gt; -- &lt;code&gt;applyCartItemPromotions&lt;/code&gt;&lt;br&gt;
is in community 4058 (item promotions) and &lt;code&gt;applyShippingPromotions&lt;/code&gt; is&lt;br&gt;
in community 4266 (pricing and shipping).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;ShoppingCartService.getProduct&lt;/code&gt;&lt;/strong&gt; calls &lt;code&gt;ProductService.getProductByItemId&lt;/code&gt;&lt;br&gt;
-- a synchronous cross-service call.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are not trivial issues. They are the structural coupling that makes&lt;br&gt;
monolith decomposition hard. Let's use blast radius to quantify them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: Quantify Coupling with Blast Radius
&lt;/h2&gt;

&lt;p&gt;Blast radius tells you: if I change this function, how many upstream callers&lt;br&gt;
break? For microservice decomposition, I care specifically about functions that&lt;br&gt;
are called across the proposed service boundaries -- these become the API&lt;br&gt;
contracts between services.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cross-boundary calls
&lt;/h3&gt;

&lt;p&gt;Query the call graph for edges between Java classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"MATCH (a:Function)-[:CALLS]-&amp;gt;(b:Function) RETURN a,b LIMIT 5000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering to cross-class calls between Java files, the significant&lt;br&gt;
inter-service edges are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source (proposed service)&lt;/th&gt;
&lt;th&gt;Target (proposed service)&lt;/th&gt;
&lt;th&gt;Call&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCartService.getProduct&lt;/code&gt;&lt;br&gt;→ &lt;code&gt;ProductService.getProductByItemId&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCartOrderProcessor.process&lt;/code&gt;&lt;br&gt;→ &lt;code&gt;Transformers.shoppingCartToJson&lt;/code&gt; (shared)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order&lt;/td&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OrderServiceMDB.onMessage&lt;/code&gt;&lt;br&gt;→ &lt;code&gt;CatalogService.updateInventoryItems&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order&lt;/td&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;InventoryNotificationMDB.onMessage&lt;/code&gt;&lt;br&gt;→ &lt;code&gt;CatalogService.getCatalogItemById&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order&lt;/td&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Transformers.jsonToOrder&lt;/code&gt; (shared code, both need it)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Blast radius at the boundaries
&lt;/h3&gt;

&lt;p&gt;Now let's check the impact of the key boundary functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius getShoppingCart
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius priceShoppingCart
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius getCatalogItems
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius getProducts
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius toProduct
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius save &lt;span class="nt"&gt;--class&lt;/span&gt; OrderService
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius onMessage &lt;span class="nt"&gt;--class&lt;/span&gt; OrderServiceMDB
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius calculateShipping &lt;span class="nt"&gt;--file&lt;/span&gt; ShippingService.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Direct Callers&lt;/th&gt;
&lt;th&gt;Impact Zone&lt;/th&gt;
&lt;th&gt;Proposed Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCartService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getShoppingCart&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;40.5&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCartService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;priceShoppingCart&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;40.4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ProductService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getProducts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;40.1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Transformers::&lt;/code&gt;&lt;br&gt;&lt;code&gt;toProduct&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;40.7&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Transformers::&lt;/code&gt;&lt;br&gt;&lt;code&gt;jsonToOrder&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;40.1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Transformers::&lt;/code&gt;&lt;br&gt;&lt;code&gt;shoppingCartToJson&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PromoService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;applyCartItemPromotions&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CatalogService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getCatalogItems&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CatalogService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;updateInventoryItems&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OrderService::save&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;OrderServiceMDB::&lt;/code&gt;&lt;br&gt;&lt;code&gt;onMessage&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ShippingService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;calculateShipping&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Transformers::toProduct&lt;/code&gt; has the highest blast radius at 40.7 with 14 nodes in&lt;br&gt;
the impact zone. This is a utility function that maps JPA entities to DTOs. It&lt;br&gt;
is called by both &lt;code&gt;ProductService.getProducts&lt;/code&gt; (catalog browsing) and&lt;br&gt;
&lt;code&gt;ProductService.getProductByItemId&lt;/code&gt; (called from the cart service). If you&lt;br&gt;
change this function, 14 upstream functions are affected -- across two proposed&lt;br&gt;
service boundaries.&lt;/p&gt;
&lt;h3&gt;
  
  
  What the ShoppingCart mutations tell me
&lt;/h3&gt;

&lt;p&gt;I can also use CPG field mutation analysis to understand the data coupling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json cpg mutations &lt;span class="nt"&gt;--type&lt;/span&gt; ShoppingCart &lt;span class="nt"&gt;--exclude-ctors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mutations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setShoppingCartItemList"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"member"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shoppingCartItemList"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ThisField"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setCartItemTotal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;"member"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cartItemTotal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ThisField"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setShippingTotal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;"member"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shippingTotal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ThisField"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setCartTotal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nl"&gt;"member"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cartTotal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ThisField"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setCartItemPromoSavings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"member"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cartItemPromoSavings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ThisField"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setShippingPromoSavings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"member"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shippingPromoSavings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ThisField"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six fields are mutated on &lt;code&gt;ShoppingCart&lt;/code&gt; by functions spread across community&lt;br&gt;
8064 (cart CRUD) and community 4266 (pricing and shipping) and community 4058 (item promotions). The cart object&lt;br&gt;
is a shared mutable structure that both &lt;code&gt;ShoppingCartService&lt;/code&gt;, &lt;code&gt;PromoService&lt;/code&gt;,&lt;br&gt;
and &lt;code&gt;ShippingService&lt;/code&gt; write to. In a monolith, this works because they all&lt;br&gt;
operate on the same in-memory object. In microservices, you need to decide who&lt;br&gt;
owns this state.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Make the Cuts
&lt;/h2&gt;

&lt;p&gt;With the structural data in hand, here are the decomposition decisions and the&lt;br&gt;
reasoning behind each.&lt;/p&gt;
&lt;h3&gt;
  
  
  Service 1: Catalog Service
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope&lt;/strong&gt;: Product listing, inventory queries, entity-to-DTO mapping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ProductEndpoint&lt;/code&gt; (REST API)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProductService&lt;/code&gt; (business logic)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CatalogService&lt;/code&gt; (JPA queries) -- but only the read path:
&lt;code&gt;getCatalogItems&lt;/code&gt;, &lt;code&gt;getCatalogItemById&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CatalogItemEntity&lt;/code&gt;, &lt;code&gt;InventoryEntity&lt;/code&gt; (JPA entities)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Product&lt;/code&gt; (DTO)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Transformers.toProduct&lt;/code&gt; (mapping logic -- inlined into this service)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why these boundaries work&lt;/strong&gt;: Communities 11973 and 11250 form a clean cluster&lt;br&gt;
with only one outbound dependency: &lt;code&gt;ProductService&lt;/code&gt; is called by&lt;br&gt;
&lt;code&gt;ShoppingCartService.getProduct&lt;/code&gt; in the cart service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API contract&lt;/strong&gt;: Expose a REST endpoint that the cart service calls instead of&lt;br&gt;
injecting &lt;code&gt;ProductService&lt;/code&gt; directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Catalog Service: src/main/java/com/redhat/coolstore/catalog/ProductResource.java&lt;/span&gt;
&lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/products"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductResource&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;ProductService&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@GET&lt;/span&gt;
    &lt;span class="nd"&gt;@Produces&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;listAll&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProducts&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GET&lt;/span&gt;
    &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{itemId}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Produces&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;getByItemId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"itemId"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;itemId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductByItemId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itemId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Blast radius validation&lt;/strong&gt;: &lt;code&gt;getCatalogItems&lt;/code&gt; has a score of 25.1 with only 1&lt;br&gt;
direct caller (&lt;code&gt;ProductService.getProducts&lt;/code&gt;). This call stays within the&lt;br&gt;
service. The cross-service call from &lt;code&gt;ShoppingCartService.getProduct&lt;/code&gt; -&amp;gt;&lt;br&gt;
&lt;code&gt;ProductService.getProductByItemId&lt;/code&gt; becomes a REST client call, which is a clean&lt;br&gt;
boundary.&lt;/p&gt;
&lt;h3&gt;
  
  
  Service 2: Cart Service
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope&lt;/strong&gt;: Shopping cart lifecycle, pricing, shipping, promotions, checkout&lt;br&gt;
trigger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CartEndpoint&lt;/code&gt; (REST API)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShoppingCartService&lt;/code&gt; (cart operations + pricing)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShippingService&lt;/code&gt; (shipping cost calculation)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PromoService&lt;/code&gt; (promotion rules)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShoppingCartOrderProcessor&lt;/code&gt; (publishes checkout event)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ShoppingCart&lt;/code&gt;, &lt;code&gt;ShoppingCartItem&lt;/code&gt;, &lt;code&gt;Promotion&lt;/code&gt; (model objects)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why these boundaries work&lt;/strong&gt;: Communities 8064 and 4266 (plus community 4058 for item&lt;br&gt;
promotions) forms the cart operations cluster. Despite label-propagation&lt;br&gt;
sometimes splitting cart, pricing, and promotions into adjacent communities,&lt;br&gt;
they share a tight internal coupling: &lt;code&gt;priceShoppingCart&lt;/code&gt; calls both&lt;br&gt;
&lt;code&gt;ShippingService&lt;/code&gt; and &lt;code&gt;PromoService&lt;/code&gt;, and &lt;code&gt;checkOutShoppingCart&lt;/code&gt; calls&lt;br&gt;
&lt;code&gt;ShoppingCartOrderProcessor&lt;/code&gt;. These are all synchronous, performance-sensitive&lt;br&gt;
calls that belong together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CatalogService coupling&lt;/strong&gt;: &lt;code&gt;ShoppingCartService.getProduct&lt;/code&gt; calls&lt;br&gt;
&lt;code&gt;ProductService.getProductByItemId&lt;/code&gt;, which calls&lt;br&gt;
&lt;code&gt;CatalogService.getCatalogItemById&lt;/code&gt;. This is the only synchronous cross-service&lt;br&gt;
dependency from cart to catalog. Replace the EJB injection with a REST client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cart Service: replaces the direct ProductService injection&lt;/span&gt;
&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogClient&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nd"&gt;@RestClient&lt;/span&gt;
    &lt;span class="nc"&gt;CatalogApi&lt;/span&gt; &lt;span class="n"&gt;catalogApi&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;getProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;itemId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;catalogApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getByItemId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itemId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@RegisterRestClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"catalog-api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/products"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;CatalogApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GET&lt;/span&gt;
    &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{itemId}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;getByItemId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"itemId"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;itemId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# application.properties
&lt;/span&gt;&lt;span class="py"&gt;quarkus.rest-client.catalog-api.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://catalog-service:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Checkout becomes an event&lt;/strong&gt;: &lt;code&gt;ShoppingCartOrderProcessor.process&lt;/code&gt; currently&lt;br&gt;
publishes to a JMS topic. In the microservices version, this becomes a message&lt;br&gt;
to a shared broker (Kafka or AMQP). The cart service is the producer; the order&lt;br&gt;
service is the consumer. This is already an asynchronous boundary in the&lt;br&gt;
monolith -- JMS was doing the right thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cart Service: checkout event publisher&lt;/span&gt;
&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCartOrderProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nd"&gt;@Channel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders-out"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Emitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ordersEmitter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ordersEmitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Transformers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shoppingCartToJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Blast radius validation&lt;/strong&gt;: &lt;code&gt;getShoppingCart&lt;/code&gt; has a score of 40.5, but all 6&lt;br&gt;
direct callers are within the cart service (&lt;code&gt;CartEndpoint.add&lt;/code&gt;, &lt;code&gt;.delete&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;.set&lt;/code&gt;, &lt;code&gt;.getCart&lt;/code&gt;, &lt;code&gt;.dedupeCartItems&lt;/code&gt;, and &lt;code&gt;ShoppingCartService.checkOutShoppingCart&lt;/code&gt;).&lt;br&gt;
The blast radius stays internal. The only outbound edge is the checkout event,&lt;br&gt;
which is asynchronous.&lt;/p&gt;
&lt;h3&gt;
  
  
  Service 3: Order Service
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope&lt;/strong&gt;: Order persistence, order queries, inventory deduction, low-stock&lt;br&gt;
alerts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OrderEndpoint&lt;/code&gt; (REST API for order queries)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OrderService&lt;/code&gt; (JPA persistence for orders)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OrderServiceMDB&lt;/code&gt; -&amp;gt; becomes &lt;code&gt;OrderEventConsumer&lt;/code&gt; (consumes checkout events)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;InventoryNotificationMDB&lt;/code&gt; -&amp;gt; becomes &lt;code&gt;InventoryAlertConsumer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Order&lt;/code&gt;, &lt;code&gt;OrderItem&lt;/code&gt; (JPA entities)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why these boundaries work&lt;/strong&gt;: The order MDB cluster and communities 11250 and 14643 form the&lt;br&gt;
order processing cluster. The MDBs have blast radius 0.0 -- they are pure&lt;br&gt;
consumers with no upstream callers. This makes them ideal microservice entry&lt;br&gt;
points: they receive events and operate independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CatalogService coupling problem&lt;/strong&gt;: This is the hardest cut.&lt;br&gt;
&lt;code&gt;OrderServiceMDB.onMessage&lt;/code&gt; calls &lt;code&gt;CatalogService.updateInventoryItems&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;InventoryNotificationMDB.onMessage&lt;/code&gt; calls &lt;code&gt;CatalogService.getCatalogItemById&lt;/code&gt;.&lt;br&gt;
Both need access to inventory data that lives in the catalog service's&lt;br&gt;
database.&lt;/p&gt;

&lt;p&gt;There are two options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: REST call to catalog service&lt;/strong&gt; (simple, synchronous)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderEventConsumer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nd"&gt;@RestClient&lt;/span&gt;
    &lt;span class="nc"&gt;CatalogApi&lt;/span&gt; &lt;span class="n"&gt;catalogApi&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Incoming&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders-in"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderStr&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Transformers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonToOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderStr&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItemList&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;catalogApi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateInventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQuantity&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option B: Publish inventory events&lt;/strong&gt; (asynchronous, better decoupling)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderEventConsumer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nd"&gt;@Channel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inventory-updates"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Emitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventoryEmitter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Incoming&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders-in"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderStr&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Transformers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonToOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderStr&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Emit inventory deduction events instead of direct call&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItemList&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;inventoryEmitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createObjectBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"productId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductId&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"quantity"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQuantity&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Option B, the catalog service would consume &lt;code&gt;inventory-updates&lt;/code&gt; and handle&lt;br&gt;
deductions internally. This eliminates the synchronous dependency entirely.&lt;/p&gt;

&lt;p&gt;Blast radius confirms this is safe: &lt;code&gt;OrderServiceMDB::onMessage&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;InventoryNotificationMDB::onMessage&lt;/code&gt; both have scores of 0.0. Changing their&lt;br&gt;
implementation (from JMS to Reactive Messaging, from direct call to event) has&lt;br&gt;
zero impact on upstream callers.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Shared Code Problem: Transformers
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Transformers&lt;/code&gt; is the class that appears in all three services. It has three&lt;br&gt;
static methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Blast Radius&lt;/th&gt;
&lt;th&gt;Used By&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toProduct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40.7&lt;/td&gt;
&lt;td&gt;Catalog (entity-to-DTO mapping)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shoppingCartToJson&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;25.2&lt;/td&gt;
&lt;td&gt;Cart (checkout serialization)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jsonToOrder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40.1&lt;/td&gt;
&lt;td&gt;Order (event deserialization)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are pure functions with no state and no injected dependencies. The correct&lt;br&gt;
decomposition is to split them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;toProduct&lt;/code&gt;&lt;/strong&gt; moves into the catalog service. It depends on
&lt;code&gt;CatalogItemEntity&lt;/code&gt; and &lt;code&gt;Product&lt;/code&gt;, both of which are catalog-owned models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;shoppingCartToJson&lt;/code&gt;&lt;/strong&gt; moves into the cart service. It depends on
&lt;code&gt;ShoppingCart&lt;/code&gt; and &lt;code&gt;ShoppingCartItem&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;jsonToOrder&lt;/code&gt;&lt;/strong&gt; moves into the order service. It depends on &lt;code&gt;Order&lt;/code&gt; and
&lt;code&gt;OrderItem&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each method only references model classes owned by its target service. The blast&lt;br&gt;
radius scores confirm this is the right split: each method's callers are entirely&lt;br&gt;
within its target service.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Shared Model Problem
&lt;/h3&gt;

&lt;p&gt;In the monolith, &lt;code&gt;ShoppingCart&lt;/code&gt;, &lt;code&gt;Order&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt;, and their related classes&lt;br&gt;
are all in one &lt;code&gt;model&lt;/code&gt; package. In the decomposed version, each service owns its&lt;br&gt;
domain model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Owned Models&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CatalogItemEntity&lt;/code&gt;, &lt;code&gt;InventoryEntity&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCart&lt;/code&gt;, &lt;code&gt;ShoppingCartItem&lt;/code&gt;, &lt;code&gt;Promotion&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Order&lt;/code&gt;, &lt;code&gt;OrderItem&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The boundary is clean except for one thing: &lt;code&gt;Product&lt;/code&gt; is used by both the&lt;br&gt;
catalog service (as a DTO returned by the API) and the cart service (as the&lt;br&gt;
product embedded in a &lt;code&gt;ShoppingCartItem&lt;/code&gt;). The solution is standard: the catalog&lt;br&gt;
service defines and owns the &lt;code&gt;Product&lt;/code&gt; type. The cart service either depends on&lt;br&gt;
a shared &lt;code&gt;catalog-api&lt;/code&gt; module or defines its own &lt;code&gt;ProductRef&lt;/code&gt; DTO with only the&lt;br&gt;
fields it needs (&lt;code&gt;itemId&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6: The Resulting Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-microservices-architecture.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fshaaf.dev%2Fimages%2F2026%2F09%2Fcoolstore-microservices-architecture.svg" alt="CoolStore microservices architecture"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Synchronous dependencies
&lt;/h3&gt;

&lt;p&gt;Only one: &lt;strong&gt;Cart -&amp;gt; Catalog&lt;/strong&gt; for product lookups. This is a single REST call&lt;br&gt;
(&lt;code&gt;GET /api/products/{itemId}&lt;/code&gt;) used in &lt;code&gt;ShoppingCartService.getProduct&lt;/code&gt; when&lt;br&gt;
adding items to the cart. It can be cached.&lt;/p&gt;
&lt;h3&gt;
  
  
  Asynchronous dependencies
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cart -&amp;gt; Order&lt;/strong&gt;: checkout event (&lt;code&gt;orders-out&lt;/code&gt; -&amp;gt; &lt;code&gt;orders-in&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order -&amp;gt; Catalog&lt;/strong&gt;: inventory deduction event (&lt;code&gt;inventory-updates&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both are fire-and-forget with eventual consistency.&lt;/p&gt;
&lt;h3&gt;
  
  
  Database ownership
&lt;/h3&gt;

&lt;p&gt;Each service owns its schema:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Tables&lt;/th&gt;
&lt;th&gt;Former JPA entities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Catalog&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CATALOG_ITEM_ENTITY&lt;/code&gt;, &lt;code&gt;INVENTORY&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CatalogItemEntity&lt;/code&gt;, &lt;code&gt;InventoryEntity&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cart&lt;/td&gt;
&lt;td&gt;(in-memory or Redis)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCart&lt;/code&gt;, &lt;code&gt;ShoppingCartItem&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ORDERS&lt;/code&gt;, &lt;code&gt;ORDER_ITEM&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Order&lt;/code&gt;, &lt;code&gt;OrderItem&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Step 7: CI Guardrails for Boundary Enforcement
&lt;/h2&gt;

&lt;p&gt;After the decomposition, enforce the boundaries with a policy file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"forbidden_crossings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"cart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"catalog-internals"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"catalog-internals"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cart-internals"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"centrality_alert_threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json check &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents re-introduction of direct cross-service calls. If a developer&lt;br&gt;
adds a direct import of &lt;code&gt;CatalogService&lt;/code&gt; in the order service, the policy check&lt;br&gt;
fails in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Graph Told Me That Intuition Would Not
&lt;/h2&gt;

&lt;p&gt;A developer familiar with the CoolStore would likely suggest similar service&lt;br&gt;
boundaries based on domain knowledge. The graph analysis adds three things that&lt;br&gt;
intuition cannot:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It quantifies the cuts.&lt;/strong&gt; &lt;code&gt;Transformers::toProduct&lt;/code&gt; has a blast radius of&lt;br&gt;
40.7 with 14 nodes in the impact zone spanning two proposed services. That is&lt;br&gt;
a number you can use in a design review. "This function affects 14 callers&lt;br&gt;
across 2 services" is more actionable than "Transformers is used in a lot of&lt;br&gt;
places."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It finds hidden coupling.&lt;/strong&gt; The order fulfillment cluster groups &lt;code&gt;OrderServiceMDB&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;InventoryNotificationMDB&lt;/code&gt;, and &lt;code&gt;OrderService.save&lt;/code&gt; by call density. Community&lt;br&gt;
11250 groups the &lt;code&gt;CatalogService&lt;/code&gt; inventory methods they call. This is&lt;br&gt;
not obvious from the package structure -- &lt;code&gt;CatalogService&lt;/code&gt; is in the &lt;code&gt;service&lt;/code&gt;&lt;br&gt;
package alongside cart-related EJBs, but functionally it is tightly coupled to&lt;br&gt;
the order fulfillment pipeline through inventory operations. The community&lt;br&gt;
detection surfaced this relationship from the call graph, not from naming&lt;br&gt;
conventions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It validates the cuts are safe.&lt;/strong&gt; Both MDBs have blast radius 0.0. This&lt;br&gt;
means converting them from JMS &lt;code&gt;@MessageDriven&lt;/code&gt; beans to Reactive Messaging&lt;br&gt;
&lt;code&gt;@Incoming&lt;/code&gt; consumers has zero static impact on the rest of the codebase. The&lt;br&gt;
graph proves the refactoring is isolated, which is exactly what you want for a&lt;br&gt;
high-risk change like switching messaging infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Not to Decompose
&lt;/h2&gt;

&lt;p&gt;The analysis also tells you when decomposition is a bad idea. The ShoppingCart&lt;br&gt;
mutations analysis showed 6 fields mutated by functions spread across 3&lt;br&gt;
communities. If you tried to split &lt;code&gt;PromoService&lt;/code&gt; and &lt;code&gt;ShippingService&lt;/code&gt; into&lt;br&gt;
separate services from the cart, every pricing operation would require&lt;br&gt;
synchronous round-trips to apply promotions, calculate shipping, and update&lt;br&gt;
totals. The graph coupling is too dense.&lt;/p&gt;

&lt;p&gt;This is why communities 8064 and 4266 -- despite being separate label-propagation&lt;br&gt;
clusters -- end up in one cart service. The community detection correctly&lt;br&gt;
identifies distinct functional clusters at finer resolution, but the shared&lt;br&gt;
mutable state (the &lt;code&gt;ShoppingCart&lt;/code&gt; object) makes them inseparable at deployment&lt;br&gt;
time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;Stop migrating by spreadsheet and tribal knowledge. If you are staring down a monolith and wondering where the hidden dependencies are, let the call graph tell you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get the tool:&lt;/strong&gt; Grab &lt;code&gt;rgctl&lt;/code&gt; from &lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test it on your own codebase:&lt;/strong&gt; Navigate to your project root and run &lt;code&gt;rgctl discover . --export-migration-hints&lt;/code&gt; to index your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop guessing:&lt;/strong&gt; Run &lt;code&gt;rgctl blast-radius&lt;/code&gt; on your most heavily-used service and see exactly what you will break before you write a single line of code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;code&gt;rgctl&lt;/code&gt; saves you from a broken build or a doomed migration sprint, star the repository. Drop into the GitHub Discussions to share your custom agent recipes—or just to show off your most horrifying circular dependency graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build the graph with full analysis (from the rgctl repo root)&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic discover &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--with-cfg&lt;/span&gt; &lt;span class="nt"&gt;--with-harmonic&lt;/span&gt; &lt;span class="nt"&gt;--export-migration-hints&lt;/span&gt;

&lt;span class="c"&gt;# List communities (natural code clusters)&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="nt"&gt;--macro-name&lt;/span&gt; all_communities unused

&lt;span class="c"&gt;# Inspect community members&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"MATCH (f:Function) WHERE f.community_id = '&amp;lt;id&amp;gt;' RETURN f LIMIT 25"&lt;/span&gt;

&lt;span class="c"&gt;# Map call edges between functions&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"MATCH (a:Function)-[:CALLS]-&amp;gt;(b:Function) RETURN a,b LIMIT 5000"&lt;/span&gt;

&lt;span class="c"&gt;# Check blast radius at a boundary&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius &amp;lt;method&amp;gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius &amp;lt;method&amp;gt; &lt;span class="nt"&gt;--class&lt;/span&gt; &amp;lt;ClassName&amp;gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius &amp;lt;method&amp;gt; &lt;span class="nt"&gt;--file&lt;/span&gt; &amp;lt;ClassName&amp;gt;.java
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic blast-radius &amp;lt;method&amp;gt;  &lt;span class="c"&gt;# human-readable output&lt;/span&gt;

&lt;span class="c"&gt;# Analyze field mutations for shared-state coupling&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json cpg mutations &lt;span class="nt"&gt;--type&lt;/span&gt; &amp;lt;ClassName&amp;gt; &lt;span class="nt"&gt;--exclude-ctors&lt;/span&gt;

&lt;span class="c"&gt;# Enforce service boundaries in CI&lt;/span&gt;
rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json check &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rust</category>
      <category>java</category>
      <category>microservices</category>
      <category>rgctl</category>
    </item>
    <item>
      <title>Application Migration using A Graph-Driven Approach with rgctl</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Mon, 31 Aug 2026 19:23:30 +0000</pubDate>
      <link>https://dev.to/sshaaf/migrating-an-application-to-quarkus-a-graph-driven-approach-with-rgctl-fn0</link>
      <guid>https://dev.to/sshaaf/migrating-an-application-to-quarkus-a-graph-driven-approach-with-rgctl-fn0</guid>
      <description>&lt;h1&gt;
  
  
  Application Migration using A Graph-Driven Approach with rgctl
&lt;/h1&gt;

&lt;p&gt;Migrating an application takes a lot of time and energy, which itself is on a collision course&lt;br&gt;
with complexity in codebases. Leaving aside the discussion on people and process. In this blog post&lt;br&gt;
I would like to highlight the journey of migration and using a tool like &lt;strong&gt;Reachability Graph Control&lt;/strong&gt; - &lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;rgctl&lt;/a&gt; which understands the codebase. Although I have taken an example of Java, the tool supports &lt;a href="https://shaaf.dev/rgctl/docs/languages/" rel="noopener noreferrer"&gt;multiple languages&lt;/a&gt; like C, C#, Go, Node etc. &lt;/p&gt;

&lt;p&gt;By taking this approach I am able to mine the code details via &lt;a href="https://shaaf.dev/rgctl/docs/guides/hybrid-cpg/" rel="noopener noreferrer"&gt;Call graphs&lt;/a&gt;, &lt;a href="https://shaaf.dev/rgctl/docs/guides/inspecting-cfg-pdg-dominance/" rel="noopener noreferrer"&gt;dependenices&lt;/a&gt;, data flow in code, &lt;a href="https://shaaf.dev/rgctl/docs/guides/program-slicing/" rel="noopener noreferrer"&gt;slicing&lt;/a&gt;, &lt;a href="https://shaaf.dev/rgctl/docs/guides/blast-radius-analysis/" rel="noopener noreferrer"&gt;blast radius&lt;/a&gt;, &lt;a href="https://shaaf.dev/rgctl/docs/guides/migration-planning/" rel="noopener noreferrer"&gt;harmonic centrality&lt;/a&gt;, betweeness, &lt;a href="https://shaaf.dev/rgctl/docs/guides/semantic-search/" rel="noopener noreferrer"&gt;semantic search&lt;/a&gt; and a little more of those things 😎.&lt;br&gt;
&lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;rgctl&lt;/a&gt; takes one more step forward, using centraility, cfg etc, it creates migration hints. So one can choose the different ways to migrate and application.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; youtube SXxI-w9pOR0 &amp;gt;}}&lt;/p&gt;

&lt;p&gt;A common question in application modernization is &lt;code&gt;where do I start?&lt;/code&gt; You have a&lt;br&gt;
monolith with dozens of tightly-coupled EJBs, JMS resources, JNDI lookups, and&lt;br&gt;
vendor-specific descriptors. You need to get it onto Quarkus. The traditional&lt;br&gt;
answer is a spreadsheet, tribal knowledge, and hope. This post walks through a&lt;br&gt;
different approach: using static call-graph analysis to make migration decisions&lt;br&gt;
based on structural data rather than guesswork.&lt;/p&gt;

&lt;p&gt;I'll use &lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;rgctl&lt;/a&gt; to analyze a real&lt;br&gt;
Java EE 7 application -- the CoolStore monolith -- and walk through the entire&lt;br&gt;
migration from WebLogic to Quarkus, step by step, with actual tool output at&lt;br&gt;
each stage.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Application: CoolStore on WebLogic
&lt;/h2&gt;

&lt;p&gt;The CoolStore is a Java EE 7 e-commerce application packaged as a WAR and&lt;br&gt;
deployed on Oracle WebLogic Server 12.2.1.4. It has 25 Java classes across 5&lt;br&gt;
packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;com.redhat.coolstore/
  model/          8 classes   JPA entities and DTOs
  service/        9 classes   EJB business logic + MDBs
  rest/           4 classes   JAX-RS endpoints
  persistence/    1 class     CDI EntityManager producer
  utils/          3 classes   Logger producer, startup hook, transformers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture follows a standard Java EE layered pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Browser -&amp;gt; AngularJS SPA
             |
        JAX-RS /services/&lt;span class="k"&gt;*&lt;/span&gt;
             |
    CartEndpoint    ProductEndpoint    OrderEndpoint
    @SessionScoped  @RequestScoped     @RequestScoped
             |              |               |
    ShoppingCart-    ProductService    OrderService
    Service         @Stateless        @Stateless
    @Stateful            |
         |          CatalogService
         |          @Stateless
         |               |
         +---&amp;gt; ShippingService @Remote &lt;span class="o"&gt;(&lt;/span&gt;JNDI lookup&lt;span class="o"&gt;)&lt;/span&gt;
         |
         +---&amp;gt; PromoService @ApplicationScoped
         |
         +---&amp;gt; ShoppingCartOrderProcessor @Stateless
                       |
                  JMS Topic &lt;span class="o"&gt;(&lt;/span&gt;jms/topic/orders&lt;span class="o"&gt;)&lt;/span&gt;
                       |
              +--------+--------+
              |                 |
        OrderServiceMDB    InventoryNotificationMDB
        &lt;span class="o"&gt;(&lt;/span&gt;persist order&lt;span class="o"&gt;)&lt;/span&gt;    &lt;span class="o"&gt;(&lt;/span&gt;low-stock alerts&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Java EE APIs in use that need migration work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EJB&lt;/strong&gt;: 5 &lt;code&gt;@Stateless&lt;/code&gt;, 1 &lt;code&gt;@Stateful&lt;/code&gt;, 1 &lt;code&gt;@Singleton&lt;/code&gt;, 2 &lt;code&gt;@MessageDriven&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JMS&lt;/strong&gt;: &lt;code&gt;ConnectionFactory&lt;/code&gt; + &lt;code&gt;Topic&lt;/code&gt; via &lt;code&gt;@Resource(lookup=...)&lt;/code&gt;, 2 MDB consumers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JNDI&lt;/strong&gt;: &lt;code&gt;InitialContext.lookup("ejb/ShippingService")&lt;/code&gt; for &lt;code&gt;@Remote&lt;/code&gt; EJB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JPA&lt;/strong&gt;: EclipseLink provider, &lt;code&gt;@PersistenceContext&lt;/code&gt;, 4 entity classes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JAX-RS&lt;/strong&gt;: 3 resource endpoints + &lt;code&gt;@ApplicationPath&lt;/code&gt; activator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebLogic-specific&lt;/strong&gt;: &lt;code&gt;weblogic.xml&lt;/code&gt;, &lt;code&gt;weblogic-ejb-jar.xml&lt;/code&gt;, WLST scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Phase 1: Build the Call Graph
&lt;/h2&gt;

&lt;p&gt;The first step is to index the codebase. rgctl's &lt;code&gt;discover&lt;/code&gt; command parses&lt;br&gt;
source files, builds the function-level call graph, computes centrality metrics,&lt;br&gt;
detects communities, and pre-computes blast radius for every function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl discover example/coolstore-weblogic &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--with-cfg&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--with-harmonic&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--with-security&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--export-migration-hints&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--migration-preset&lt;/span&gt; risk_mitigation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&amp;gt;]&lt;/span&gt; rgctl discover
&lt;span class="o"&gt;[!]&lt;/span&gt; Deep analysis enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--with-cfg&lt;/span&gt; / &lt;span class="nt"&gt;--with-taint&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;✓] Indexed 1226 files -&amp;gt; 14760 nodes, 50082 edges
Skipped files due to errors: 1
&lt;span class="o"&gt;[&lt;/span&gt;✓] Analyzed 7526 functions &lt;span class="o"&gt;(&lt;/span&gt;avg complexity: 1.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;✓] Detected 10870 communities &lt;span class="o"&gt;(&lt;/span&gt;modularity: 0.30&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[!]&lt;/span&gt; Found 186 circular dependencies

✓ Security analysis:
  Potential secrets found: 5

✓ Control flow analysis:
  Field writes indexed: 3299
  CFG/PDG/Dominance: 6585 functions analyzed
  Skipped: 941 functions &lt;span class="o"&gt;(&lt;/span&gt;unsupported language or parse error&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;✓] Migration plan &lt;span class="o"&gt;(&lt;/span&gt;Risk Mitigation&lt;span class="o"&gt;)&lt;/span&gt;: 152 steps
&lt;span class="o"&gt;[&lt;/span&gt;✓] rgctl discover finished &lt;span class="k"&gt;in &lt;/span&gt;22.7s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;186 circular dependencies&lt;/strong&gt; -- this is normal for a Java EE monolith where
EJBs freely inject each other. It is exactly the kind of coupling that makes
migration ordering hard without tooling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 potential secrets&lt;/strong&gt; -- the security scan flagged hardcoded values. In a
real migration, you would rotate these before they end up in a Quarkus
&lt;code&gt;application.properties&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6585 functions analyzed&lt;/strong&gt; -- the project includes bower_components (the
AngularJS frontend), so the graph covers both Java and JavaScript. For
migration purposes, I focus on the Java side, but the cross-language call
edges (REST endpoint -&amp;gt; JS controller) are real and show up in blast radius.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a &lt;code&gt;.rgctl/&lt;/code&gt; directory with the graph snapshot, blast engine&lt;br&gt;
index, and a &lt;code&gt;migration_plan.json&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Phase 2: Inventory the Codebase
&lt;/h2&gt;

&lt;p&gt;Before planning, I need to understand what I have. Query the graph for all&lt;br&gt;
Java classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json gql &lt;span class="s2"&gt;"MATCH (c:Class) RETURN c"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
data = json.load(sys.stdin)
for row in data['rows']:
    for item in row:
        if '.java' in item.get('file',''):
            print(f'{item[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;node&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]:40s} {item[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;qualified_name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]}')"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;CartEndpoint                             com.redhat.coolstore.rest.CartEndpoint
CatalogItemEntity                        com.redhat.coolstore.model.CatalogItemEntity
CatalogService                           com.redhat.coolstore.service.CatalogService
InventoryEntity                          com.redhat.coolstore.model.InventoryEntity
InventoryNotificationMDB                 com.redhat.coolstore.service.InventoryNotificationMDB
Order                                    com.redhat.coolstore.model.Order
OrderEndpoint                            com.redhat.coolstore.rest.OrderEndpoint
OrderItem                                com.redhat.coolstore.model.OrderItem
OrderService                             com.redhat.coolstore.service.OrderService
OrderServiceMDB                          com.redhat.coolstore.service.OrderServiceMDB
Producers                                com.redhat.coolstore.utils.Producers
Product                                  com.redhat.coolstore.model.Product
ProductEndpoint                          com.redhat.coolstore.rest.ProductEndpoint
ProductService                           com.redhat.coolstore.service.ProductService
Promotion                                com.redhat.coolstore.model.Promotion
PromoService                             com.redhat.coolstore.service.PromoService
Resources                                com.redhat.coolstore.persistence.Resources
RestApplication                          com.redhat.coolstore.rest.RestApplication
ShippingService                          com.redhat.coolstore.service.ShippingService
ShoppingCart                             com.redhat.coolstore.model.ShoppingCart
ShoppingCartItem                         com.redhat.coolstore.model.ShoppingCartItem
ShoppingCartOrderProcessor               com.redhat.coolstore.service.ShoppingCartOrderProcessor
ShoppingCartService                      com.redhat.coolstore.service.ShoppingCartService
StartupListener                          com.redhat.coolstore.utils.StartupListener
Transformers                             com.redhat.coolstore.utils.Transformers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;25 classes. Every one I need to touch is here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: Understand Risk with Blast Radius
&lt;/h2&gt;

&lt;p&gt;Blast radius answers a specific question: &lt;strong&gt;"if I change this function, what&lt;br&gt;
breaks upstream?"&lt;/strong&gt; It uses pre-computed SCC (Strongly Connected Component)&lt;br&gt;
decomposition of the call graph to identify all transitive callers of a given&lt;br&gt;
symbol. The result is an impact score from 0 to 100, the list of direct callers,&lt;br&gt;
and the full impact zone.&lt;/p&gt;

&lt;p&gt;This is the most operationally useful tool for migration ordering. Let's run it&lt;br&gt;
on the riskiest-looking class first -- &lt;code&gt;ShoppingCartService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic blast-radius getShoppingCart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Blast radius &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'getShoppingCart'&lt;/span&gt;
  Score: 40.5/100
  Direct callers: 6
  Impact zone: 10
  Callers: ShoppingCartService.checkOutShoppingCart,
           CartEndpoint.add,
           CartEndpoint.dedupeCartItems,
           CartEndpoint.delete,
           CartEndpoint.getCart,
           CartEndpoint.set
  Impact:  CartEndpoint.getCart,
           CartEndpoint.add,
           CartEndpoint.set,
           CartEndpoint.delete,
           CartEndpoint.dedupeCartItems,
           CartEndpoint.checkout,
           ShoppingCartService.checkOutShoppingCart,
           reset &lt;span class="o"&gt;(&lt;/span&gt;controllers.js&lt;span class="o"&gt;)&lt;/span&gt;,
           performAction &lt;span class="o"&gt;(&lt;/span&gt;controllers.js&lt;span class="o"&gt;)&lt;/span&gt;,
           anonymous &lt;span class="o"&gt;(&lt;/span&gt;controllers.js&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A score of 40.5 with 10 nodes in the impact zone. Change &lt;code&gt;getShoppingCart&lt;/code&gt; and&lt;br&gt;
you potentially affect 6 Java methods plus 3 JavaScript controller functions.&lt;br&gt;
That is a lot of surface area -- this is not where you want to start your&lt;br&gt;
migration.&lt;/p&gt;

&lt;p&gt;Now look at the JSON output for more detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius getShoppingCart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"canonical_fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ShoppingCartService::getShoppingCart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"class_context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ShoppingCartService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".../service/ShoppingCartService.java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"java"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public ShoppingCart getShoppingCart(String cartId) {"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"topology"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scc_component_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;580&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.service.ShoppingCartService.checkOutShoppingCart"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.add"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.dedupeCartItems"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.delete"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.getCart"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.redhat.coolstore.rest.CartEndpoint.set"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"...10 entries..."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gatekeeping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"policy_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SKIPPED"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;scc_component_id: 580&lt;/code&gt; tells me this function is part of a strongly&lt;br&gt;
connected component in the call graph -- there are cycles (which matches the 186&lt;br&gt;
circular dependencies the discover phase reported).&lt;/p&gt;

&lt;p&gt;Let's compare with a simpler function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-r&lt;/span&gt; example/coolstore-weblogic &lt;span class="nt"&gt;-f&lt;/span&gt; json blast-radius calculateShipping &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--file&lt;/span&gt; ShippingService.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"canonical_fqn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ShippingService::calculateShipping"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"class_context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ShippingService"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"direct_callers_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact_zone_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Score of 0.0. Zero direct callers in the static graph. &lt;code&gt;ShippingService&lt;/code&gt; is&lt;br&gt;
called via JNDI lookup (&lt;code&gt;InitialContext.lookup("ejb/ShippingService")&lt;/code&gt;), which&lt;br&gt;
is a runtime dispatch that static analysis cannot resolve. This is actually&lt;br&gt;
useful information: it tells me this class is structurally isolated. I can&lt;br&gt;
migrate it safely without breaking any statically-visible call paths.&lt;/p&gt;

&lt;p&gt;A few more blast radius checks to build the full picture:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symbol&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Direct Callers&lt;/th&gt;
&lt;th&gt;Impact Zone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCartService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getShoppingCart&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;40.5&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCartService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;priceShoppingCart&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;40.4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ShoppingCartService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;checkOutShoppingCart&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ProductService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getProducts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;40.1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PromoService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;applyCartItemPromotions&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CatalogService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getCatalogItems&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;OrderService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;getOrders&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CartEndpoint::&lt;/code&gt;&lt;br&gt;&lt;code&gt;checkout&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;OrderServiceMDB::&lt;/code&gt;&lt;br&gt;&lt;code&gt;onMessage&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ShippingService::&lt;/code&gt;&lt;br&gt;&lt;code&gt;calculateShipping&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is clear: the &lt;code&gt;ShoppingCartService&lt;/code&gt; methods have the highest blast&lt;br&gt;
radius because everything flows through the cart. The MDBs and&lt;br&gt;
&lt;code&gt;ShippingService&lt;/code&gt; have zero because they are invoked through JMS and JNDI&lt;br&gt;
respectively -- runtime dispatch, not static calls.&lt;/p&gt;
&lt;h2&gt;
  
  
  Phase 4: Read the Migration Plan
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;--export-migration-hints&lt;/code&gt; flag during discover produced a&lt;br&gt;
&lt;code&gt;migration_plan.json&lt;/code&gt;. This is a package-level roadmap that combines three&lt;br&gt;
signals into a priority score:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PageRank (alpha)&lt;/strong&gt; -- how central the package is in the call graph&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harmonic centrality (beta)&lt;/strong&gt; -- how reachable the package is from all others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max blast radius (gamma)&lt;/strong&gt; -- the worst-case impact of changing anything in the package&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The priority formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Priority&lt;span class="o"&gt;(&lt;/span&gt;p&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; alpha &lt;span class="k"&gt;*&lt;/span&gt; norm&lt;span class="o"&gt;(&lt;/span&gt;pagerank&lt;span class="o"&gt;)&lt;/span&gt; + beta &lt;span class="k"&gt;*&lt;/span&gt; norm&lt;span class="o"&gt;(&lt;/span&gt;harmonic&lt;span class="o"&gt;)&lt;/span&gt; - gamma &lt;span class="k"&gt;*&lt;/span&gt; norm&lt;span class="o"&gt;(&lt;/span&gt;max_blast&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used &lt;code&gt;--migration-preset risk_mitigation&lt;/code&gt;, which sets &lt;code&gt;gamma = 0.70&lt;/code&gt; --&lt;br&gt;
heavily penalizing high-blast packages. This means the plan orders safe,&lt;br&gt;
low-impact packages first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .rgctl/migration_plan.json | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
data = json.load(sys.stdin)
for s in data['steps']:
    if 'redhat' in s['label']:
        print(f'Step {s[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;step&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]:3d} | schedule:{s[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;schedule_step&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]:3d} '
              f'| rank:{s[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;priority_rank&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]:3d} '
              f'| score:{s[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;priority_score&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]:+.4f} | {s[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]}')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Step   2 | schedule:  2 | rank: 72 | score:-0.3424 | com.redhat.coolstore.model
Step  49 | schedule: 49 | rank:127 | score:-0.3500 | com.redhat.coolstore.persistence
Step  76 | schedule: 76 | rank:  2 | score:-0.1801 | com.redhat.coolstore.rest
Step  84 | schedule: 84 | rank: 10 | score:-0.2695 | com.redhat.coolstore.utils
Step  87 | schedule: 87 | rank: 13 | score:-0.2791 | com.redhat.coolstore.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;schedule_step&lt;/code&gt; column is a dependency-aware topological sort (Kahn's&lt;br&gt;
algorithm on the package call graph): packages that are called by others come&lt;br&gt;
first. The &lt;code&gt;priority_rank&lt;/code&gt; column is pure score ranking.&lt;/p&gt;

&lt;p&gt;Reading this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;com.redhat.coolstore.model&lt;/code&gt;&lt;/strong&gt; (step 2) -- migrate first. These are JPA
entities and POJOs. They sit at the bottom of the dependency chain and
nothing else breaks when you change them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;com.redhat.coolstore.persistence&lt;/code&gt;&lt;/strong&gt; (step 49) -- the &lt;code&gt;Resources&lt;/code&gt; CDI
producer for &lt;code&gt;EntityManager&lt;/code&gt;. Small, isolated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;com.redhat.coolstore.rest&lt;/code&gt;&lt;/strong&gt; (step 76) -- the REST endpoints. They sit at
the top of the call chain (rank 2 by priority, meaning high harmonic
centrality) but depend on everything below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;com.redhat.coolstore.utils&lt;/code&gt;&lt;/strong&gt; (step 84) -- utility classes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;com.redhat.coolstore.service&lt;/code&gt;&lt;/strong&gt; (step 87) -- the EJB layer. Migrated last
because it has the most complexity (stateful beans, JMS, JNDI) and the
highest aggregate blast radius.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ordering matches engineering intuition, but it was derived from the graph&lt;br&gt;
structure, not from guessing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Phase 5: Execute the Migration
&lt;/h2&gt;

&lt;p&gt;With the analysis done, I migrate package by package following the plan. Below&lt;br&gt;
is the concrete work for each phase.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Entities and DTOs (&lt;code&gt;com.redhat.coolstore.model&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The model classes need three changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Namespace&lt;/strong&gt;: &lt;code&gt;javax.persistence.*&lt;/code&gt; -&amp;gt; &lt;code&gt;jakarta.persistence.*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop JAXB&lt;/strong&gt;: Remove &lt;code&gt;@XmlRootElement&lt;/code&gt; from &lt;code&gt;InventoryEntity&lt;/code&gt; (Quarkus uses
Jackson by default)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JPA provider&lt;/strong&gt;: Switch from EclipseLink to Hibernate ORM (Quarkus default)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before (&lt;code&gt;CatalogItemEntity.java&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.persistence.Entity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.persistence.Table&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jakarta.persistence.Entity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jakarta.persistence.Table&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ShoppingCart&lt;/code&gt; class is annotated &lt;code&gt;@Dependent&lt;/code&gt; (CDI). In Quarkus, this&lt;br&gt;
continues to work as-is after the namespace change.&lt;/p&gt;

&lt;p&gt;Delete &lt;code&gt;persistence.xml&lt;/code&gt; entirely. Replace with &lt;code&gt;application.properties&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;quarkus.datasource.db-kind&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;h2&lt;/span&gt;
&lt;span class="py"&gt;quarkus.datasource.jdbc.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;jdbc:h2:mem:coolstore;DB_CLOSE_DELAY=-1&lt;/span&gt;
&lt;span class="py"&gt;quarkus.hibernate-orm.database.generation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;drop-and-create&lt;/span&gt;
&lt;span class="py"&gt;quarkus.hibernate-orm.sql-load-script&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;import.sql&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is safe. Blast radius for the model classes is low -- they are callees, not&lt;br&gt;
callers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Persistence Producer (&lt;code&gt;com.redhat.coolstore.persistence&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Resources&lt;/code&gt; class produces an &lt;code&gt;EntityManager&lt;/code&gt; via &lt;code&gt;@PersistenceContext&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="nd"&gt;@PersistenceContext&lt;/span&gt;
&lt;span class="nd"&gt;@Produces&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;EntityManager&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Quarkus, you do not need a CDI producer for &lt;code&gt;EntityManager&lt;/code&gt;. Quarkus injects&lt;br&gt;
it directly. Remove the &lt;code&gt;Resources&lt;/code&gt; class entirely and inject &lt;code&gt;EntityManager&lt;/code&gt;&lt;br&gt;
where needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// After (in service classes)&lt;/span&gt;
&lt;span class="nd"&gt;@Inject&lt;/span&gt;
&lt;span class="nc"&gt;EntityManager&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Utilities (&lt;code&gt;com.redhat.coolstore.utils&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Producers.java&lt;/code&gt;&lt;/strong&gt; -- The &lt;code&gt;Logger&lt;/code&gt; CDI producer works in Quarkus after&lt;br&gt;
&lt;code&gt;javax&lt;/code&gt; -&amp;gt; &lt;code&gt;jakarta&lt;/code&gt; namespace changes. Alternatively, Quarkus provides built-in&lt;br&gt;
logging via &lt;code&gt;@Inject Logger&lt;/code&gt; or the &lt;code&gt;jboss-logging&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;StartupListener.java&lt;/code&gt;&lt;/strong&gt; -- This is an EJB &lt;code&gt;@Singleton&lt;/code&gt; + &lt;code&gt;@Startup&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="nd"&gt;@Singleton&lt;/span&gt;
&lt;span class="nd"&gt;@Startup&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StartupListener&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@PostConstruct&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onStartup&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CoolStore Application Started"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace with Quarkus CDI lifecycle event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StartupListener&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onStartup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Observes&lt;/span&gt; &lt;span class="nc"&gt;StartupEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CoolStore Application Started"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;Transformers.java&lt;/code&gt;&lt;/strong&gt; -- Uses &lt;code&gt;javax.json.*&lt;/code&gt; (JSON-P) for manual JSON&lt;br&gt;
building. In Quarkus, replace with Jackson &lt;code&gt;ObjectMapper&lt;/code&gt; or keep JSON-P with&lt;br&gt;
the &lt;code&gt;quarkus-jsonp&lt;/code&gt; extension. Either works; Jackson is the path of least&lt;br&gt;
resistance since Quarkus RESTEasy uses it by default.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: ShippingService (low-risk EJB extraction)
&lt;/h3&gt;

&lt;p&gt;Blast radius: &lt;strong&gt;0.0&lt;/strong&gt;. This is the ideal first EJB to migrate.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Stateless&lt;/span&gt;
&lt;span class="nd"&gt;@Remote&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShippingService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShippingServiceRemote&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;calculateShipping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// price-based shipping tiers&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShippingService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;calculateShipping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// same logic, no EJB, no @Remote&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove the &lt;code&gt;ShippingServiceRemote&lt;/code&gt; interface entirely. Remove the JNDI lookup&lt;br&gt;
in &lt;code&gt;ShoppingCartService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before (ShoppingCartService.java line 114-122)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ShippingServiceRemote&lt;/span&gt; &lt;span class="nf"&gt;lookupShippingServiceRemote&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InitialContext&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShippingServiceRemote&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lookup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ejb/ShippingService"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NamingException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace with CDI injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="nd"&gt;@Inject&lt;/span&gt;
&lt;span class="nc"&gt;ShippingService&lt;/span&gt; &lt;span class="n"&gt;shippingService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// In priceShoppingCart(), replace:&lt;/span&gt;
&lt;span class="c1"&gt;//   lookupShippingServiceRemote().calculateShipping(sc)&lt;/span&gt;
&lt;span class="c1"&gt;// with:&lt;/span&gt;
&lt;span class="c1"&gt;//   shippingService.calculateShipping(sc)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This eliminates the JNDI dependency, the &lt;code&gt;@Remote&lt;/code&gt; interface, and the&lt;br&gt;
&lt;code&gt;weblogic.xml&lt;/code&gt; JNDI binding for &lt;code&gt;ejb/ShippingService&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Stateless EJBs (CatalogService, OrderService, ProductService, PromoService)
&lt;/h3&gt;

&lt;p&gt;These all follow the same pattern. Take &lt;code&gt;CatalogService&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Stateless&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;EntityManager&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CatalogItemEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getCatalogItems&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;CriteriaBuilder&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCriteriaBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;CriteriaQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CatalogItemEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CatalogItemEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Root&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CatalogItemEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CatalogItemEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getResultList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;EntityManager&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CatalogItemEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getCatalogItems&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Same JPA logic -- works identically on Hibernate&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The change: &lt;code&gt;@Stateless&lt;/code&gt; -&amp;gt; &lt;code&gt;@ApplicationScoped&lt;/code&gt; + &lt;code&gt;@Transactional&lt;/code&gt;. The JPA&lt;br&gt;
code remains the same. Quarkus's Hibernate ORM handles the EntityManager&lt;br&gt;
lifecycle.&lt;/p&gt;

&lt;p&gt;Blast radius for &lt;code&gt;CatalogService::getCatalogItems&lt;/code&gt; is 25.1 (1 direct caller:&lt;br&gt;
&lt;code&gt;ProductService.getProducts&lt;/code&gt;, which propagates to &lt;code&gt;ProductEndpoint.listAll&lt;/code&gt;).&lt;br&gt;
Moderate risk, but manageable because the callers are simple pass-through methods.&lt;/p&gt;

&lt;p&gt;Apply the same pattern to &lt;code&gt;OrderService&lt;/code&gt;, &lt;code&gt;ProductService&lt;/code&gt;, and &lt;code&gt;PromoService&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 6: ShoppingCartOrderProcessor (JMS producer)
&lt;/h3&gt;

&lt;p&gt;This is the most involved single-class change. The original uses WebLogic JMS&lt;br&gt;
resources injected via JNDI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Stateless&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCartOrderProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Resource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"weblogic.jms.ConnectionFactory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ConnectionFactory&lt;/span&gt; &lt;span class="n"&gt;connectionFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Resource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jms/topic/orders"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Topic&lt;/span&gt; &lt;span class="n"&gt;ordersTopic&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JMSContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connectionFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProducer&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordersTopic&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="nc"&gt;Transformers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shoppingCartToJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two options for Quarkus:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: SmallRye Reactive Messaging (recommended)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCartOrderProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nd"&gt;@Channel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Emitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ordersEmitter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ordersEmitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Transformers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shoppingCartToJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;application.properties&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;mp.messaging.outgoing.orders.connector&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;smallrye-in-memory&lt;/span&gt;
&lt;span class="c"&gt;# or for Kafka: smallrye-kafka
# or for AMQP: smallrye-amqp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option B: Quarkus Artemis JMS&lt;/strong&gt; (if you want to keep JMS semantics):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCartOrderProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;ConnectionFactory&lt;/span&gt; &lt;span class="n"&gt;connectionFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JMSContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connectionFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProducer&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createTopic&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;Transformers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shoppingCartToJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7: Message-Driven Beans (OrderServiceMDB, InventoryNotificationMDB)
&lt;/h3&gt;

&lt;p&gt;Both MDBs have blast radius of 0.0 (they are entry points, invoked by the&lt;br&gt;
messaging runtime, not by application code). But they depend on services&lt;br&gt;
migrated in earlier steps, so they must come after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OrderServiceMDB&lt;/strong&gt; -- before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@MessageDriven&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"OrderServiceMDB"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@ActivationConfigProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propertyName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"destinationLookup"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;propertyValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"topic/orders"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nd"&gt;@ActivationConfigProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propertyName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"destinationType"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;propertyValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"javax.jms.Topic"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nd"&gt;@ActivationConfigProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propertyName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"acknowledgeMode"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;propertyValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Auto-acknowledge"&lt;/span&gt;&lt;span class="o"&gt;)})&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceMDB&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MessageListener&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="nc"&gt;CatalogService&lt;/span&gt; &lt;span class="n"&gt;catalogService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="n"&gt;rcvMessage&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;TextMessage&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextMessage&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;rcvMessage&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Transformers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonToOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderStr&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItemList&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderItem&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;catalogService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateInventoryItems&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;orderItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;orderItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQuantity&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After (SmallRye Reactive Messaging):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceMDB&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="nc"&gt;CatalogService&lt;/span&gt; &lt;span class="n"&gt;catalogService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Incoming&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderStr&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Transformers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonToOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderStr&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItemList&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderItem&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;catalogService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateInventoryItems&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;orderItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;orderItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQuantity&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@MessageDriven&lt;/code&gt; annotation, &lt;code&gt;MessageListener&lt;/code&gt; interface,&lt;br&gt;
&lt;code&gt;ActivationConfigProperty&lt;/code&gt; array, and JMS &lt;code&gt;Message&lt;/code&gt; unwrapping are all gone.&lt;br&gt;
The method now takes a &lt;code&gt;String&lt;/code&gt; directly from the channel.&lt;/p&gt;

&lt;p&gt;Apply the same pattern to &lt;code&gt;InventoryNotificationMDB&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 8: ShoppingCartService (the hard one)
&lt;/h3&gt;

&lt;p&gt;This is the highest-risk class: blast radius of 40.5 for &lt;code&gt;getShoppingCart&lt;/code&gt;,&lt;br&gt;
40.4 for &lt;code&gt;priceShoppingCart&lt;/code&gt;. It has three problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@Stateful&lt;/code&gt; EJB&lt;/strong&gt; -- holds a &lt;code&gt;ShoppingCart&lt;/code&gt; instance in memory per session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JNDI lookup&lt;/strong&gt; -- &lt;code&gt;InitialContext.lookup("ejb/ShippingService")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Injects other EJBs&lt;/strong&gt; -- &lt;code&gt;ProductService&lt;/code&gt;, &lt;code&gt;PromoService&lt;/code&gt;,
&lt;code&gt;ShoppingCartOrderProcessor&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The JNDI lookup was already eliminated in Step 4. The other EJBs were migrated&lt;br&gt;
to CDI beans in Steps 5-6. What remains is the stateful nature.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Stateful&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCartService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCart&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="nf"&gt;getShoppingCart&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;cartId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quarkus has no EJB container, so &lt;code&gt;@Stateful&lt;/code&gt; does not exist. The cart state&lt;br&gt;
needs to go somewhere. For a monolith migration, the simplest option is&lt;br&gt;
&lt;code&gt;@ApplicationScoped&lt;/code&gt; with a &lt;code&gt;ConcurrentHashMap&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ApplicationScoped&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCartService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCart&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;carts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCart&lt;/span&gt; &lt;span class="nf"&gt;getShoppingCart&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;cartId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;carts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;computeIfAbsent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cartId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCart&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production use, back this with Redis or a database. But for a migration&lt;br&gt;
that preserves behavior, the in-memory map is functionally equivalent.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 9: REST Endpoints
&lt;/h3&gt;

&lt;p&gt;The JAX-RS endpoints need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;javax.ws.rs.*&lt;/code&gt; -&amp;gt; &lt;code&gt;jakarta.ws.rs.*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;RestApplication.java&lt;/code&gt; (Quarkus auto-discovers JAX-RS resources)&lt;/li&gt;
&lt;li&gt;Fix &lt;code&gt;CartEndpoint&lt;/code&gt;'s &lt;code&gt;@SessionScoped&lt;/code&gt; -- Quarkus does not support
&lt;code&gt;@SessionScoped&lt;/code&gt; for REST endpoints by default&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;CartEndpoint&lt;/code&gt; before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SessionScoped&lt;/span&gt;
&lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/cart"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CartEndpoint&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Serializable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCartService&lt;/span&gt; &lt;span class="n"&gt;shoppingCartService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RequestScoped&lt;/span&gt;
&lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/cart"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CartEndpoint&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;ShoppingCartService&lt;/span&gt; &lt;span class="n"&gt;shoppingCartService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;ShoppingCartService&lt;/code&gt; now manages cart state by ID (the &lt;code&gt;ConcurrentHashMap&lt;/code&gt;&lt;br&gt;
approach), the endpoint no longer needs to be &lt;code&gt;@SessionScoped&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 10: WebLogic Descriptors
&lt;/h3&gt;

&lt;p&gt;Delete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WEB-INF/weblogic.xml&lt;/code&gt; -- JNDI mappings, session replication config,
classloader preferences. None apply to Quarkus.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WEB-INF/weblogic-ejb-jar.xml&lt;/code&gt; -- EJB pool sizes, stateful cache/clustering,
MDB destination bindings. None apply.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;weblogic-config/*.py&lt;/code&gt; -- WLST domain creation scripts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose.yml&lt;/code&gt; -- WebLogic container definition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Replace &lt;code&gt;persistence.xml&lt;/code&gt; with &lt;code&gt;application.properties&lt;/code&gt; (done in Step 1).&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 11: Frontend
&lt;/h3&gt;

&lt;p&gt;The JSP files (&lt;code&gt;index.jsp&lt;/code&gt;, &lt;code&gt;health.jsp&lt;/code&gt;) do not work in Quarkus. Move the&lt;br&gt;
AngularJS SPA to &lt;code&gt;src/main/resources/META-INF/resources/&lt;/code&gt; and serve as static&lt;br&gt;
files. Replace &lt;code&gt;health.jsp&lt;/code&gt; with the Quarkus SmallRye Health extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# pom.xml dependency
&lt;/span&gt;&lt;span class="err"&gt;quarkus-smallrye-health&lt;/span&gt;
&lt;span class="c"&gt;# exposes /q/health automatically
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phase 6: CI Guardrails
&lt;/h2&gt;

&lt;p&gt;After the migration, set up policy checks to prevent regressions. Write a&lt;br&gt;
&lt;code&gt;policy.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_impact_nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"centrality_alert_threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gate PRs in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl discover &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--with-cfg&lt;/span&gt; &lt;span class="nt"&gt;--with-harmonic&lt;/span&gt; &lt;span class="nt"&gt;--export-migration-hints&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rgctl &lt;span class="nt"&gt;-f&lt;/span&gt; json check &lt;span class="nt"&gt;--policy-file&lt;/span&gt; policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any changed function has a blast radius exceeding 15 impact nodes, or if the&lt;br&gt;
impact zone crosses a high-betweenness bridge node, the check exits with code 1&lt;br&gt;
and the PR fails. This is useful during the migration itself to catch accidental&lt;br&gt;
scope creep -- changing a "safe" class that inadvertently pulls in a high-blast&lt;br&gt;
dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The migration order derived from the call graph analysis:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Package/Class&lt;/th&gt;
&lt;th&gt;Blast Radius&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;model/*&lt;/code&gt; (entities, DTOs)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;javax&lt;/code&gt; -&amp;gt; &lt;code&gt;jakarta&lt;/code&gt; namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;persistence/Resources&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;Delete (Quarkus injects EntityManager)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;utils/*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@Singleton @Startup&lt;/code&gt; -&amp;gt; &lt;code&gt;@Observes StartupEvent&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ShippingService&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;Drop &lt;code&gt;@Remote&lt;/code&gt;, &lt;code&gt;@Stateless&lt;/code&gt; -&amp;gt; &lt;code&gt;@ApplicationScoped&lt;/code&gt;, delete JNDI lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CatalogService&lt;/code&gt;, &lt;code&gt;OrderService&lt;/code&gt;, &lt;code&gt;ProductService&lt;/code&gt;, &lt;code&gt;PromoService&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@Stateless&lt;/code&gt; -&amp;gt; &lt;code&gt;@ApplicationScoped @Transactional&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ShoppingCartOrderProcessor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;JMS &lt;code&gt;@Resource&lt;/code&gt; -&amp;gt; SmallRye Reactive Messaging &lt;code&gt;@Channel&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OrderServiceMDB&lt;/code&gt;, &lt;code&gt;InventoryNotificationMDB&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@MessageDriven&lt;/code&gt; -&amp;gt; &lt;code&gt;@Incoming&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ShoppingCartService&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;40.5&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@Stateful&lt;/code&gt; -&amp;gt; &lt;code&gt;@ApplicationScoped&lt;/code&gt; + external state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;REST endpoints&lt;/td&gt;
&lt;td&gt;25.1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@SessionScoped&lt;/code&gt; -&amp;gt; &lt;code&gt;@RequestScoped&lt;/code&gt;, remove &lt;code&gt;RestApplication&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;WebLogic descriptors&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Delete &lt;code&gt;weblogic.xml&lt;/code&gt;, &lt;code&gt;weblogic-ejb-jar.xml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;JSP -&amp;gt; static files, add SmallRye Health&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key insight is not that this ordering is surprising -- an experienced Java&lt;br&gt;
EE developer would likely arrive at a similar sequence. The point is that the&lt;br&gt;
call graph provides &lt;strong&gt;structural evidence&lt;/strong&gt; for migration decisions. When you are&lt;br&gt;
working on a codebase you do not know well, or one with 500 classes instead of&lt;br&gt;
25, the blast radius numbers replace guesswork with data. A score of 40.5 means&lt;br&gt;
something concrete: 6 direct callers and 10 transitive dependents. That is the&lt;br&gt;
scope of what needs testing after you touch that method.&lt;/p&gt;

&lt;p&gt;The migration plan's topological sort also catches dependency ordering mistakes&lt;br&gt;
that are easy to make manually. You cannot migrate &lt;code&gt;ShoppingCartService&lt;/code&gt; before&lt;br&gt;
&lt;code&gt;ShippingService&lt;/code&gt; because the JNDI lookup replacement requires &lt;code&gt;ShippingService&lt;/code&gt;&lt;br&gt;
to already be a CDI bean. The plan encodes this constraint automatically.&lt;/p&gt;

&lt;p&gt;For larger codebases -- hundreds of EJBs, nested module dependencies, shared&lt;br&gt;
libraries -- this kind of structural analysis scales where spreadsheets do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;Stop migrating by spreadsheet and tribal knowledge. If you are staring down a monolith and wondering where the hidden dependencies are, let the call graph tell you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get the tool:&lt;/strong&gt; Grab &lt;code&gt;rgctl&lt;/code&gt; from &lt;a href="https://github.com/sshaaf/rgctl" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test it on your own codebase:&lt;/strong&gt; Navigate to your project root and run &lt;code&gt;rgctl discover . --export-migration-hints&lt;/code&gt; to index your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop guessing:&lt;/strong&gt; Run &lt;code&gt;rgctl blast-radius&lt;/code&gt; on your most heavily-used service and see exactly what you will break before you write a single line of code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;code&gt;rgctl&lt;/code&gt; saves you from a broken build or a doomed migration sprint, star the repository. Drop into the GitHub Discussions to share your custom agent recipes—or just to show off your most horrifying circular dependency graph.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>java</category>
      <category>quarkus</category>
      <category>migration</category>
    </item>
    <item>
      <title>Lessons from Building My First Claude Skill - a code tutorial generator</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Thu, 07 May 2026 07:49:57 +0000</pubDate>
      <link>https://dev.to/sshaaf/lessons-from-building-my-first-claude-skill-a-code-tutorial-generator-3onm</link>
      <guid>https://dev.to/sshaaf/lessons-from-building-my-first-claude-skill-a-code-tutorial-generator-3onm</guid>
      <description>&lt;p&gt;About a year ago, I kicked off (&lt;a href="https://github.com/sshaaf/waver" rel="noopener noreferrer"&gt;Waver&lt;/a&gt;), a project designed to analyze codebases and build developer friendly, readable tutorials. The goal was to generate clean Markdown and Mermaid diagrams so onboarding becomes frictionless and the docs can slide right into existing project pipelines.&lt;/p&gt;

&lt;p&gt;I chose &lt;a href="https://docs.langchain4j.dev/intro/" rel="noopener noreferrer"&gt;Langchain4J&lt;/a&gt; for the LLM integration. And in a classic "engineers-quest", I actually ended up building &lt;a href="https://shaaf.dev/post/2025-08-25-think-in-graphs-not-just-chains-jgraphlet-for-taskpipelines/#undefined" rel="noopener noreferrer"&gt;JGraphlet&lt;/a&gt; as well, which came to life while I was trying to squeeze better performance out of the LLM communication.&lt;/p&gt;

&lt;p&gt;Working in pure Java offers clear benefits, specifically regarding granular control and performance optimization. However, these implementations often become highly opinionated. In contrast, while SKILLS maintains a degree of opinionated design, its actual application depends entirely on the coding agent. Both methods have advantages, and I’m using this post to process what I’ve learned while writing my first SKILL. Here is a code example from my Java project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;dev.shaaf.waver.cli&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;dev.langchain4j.model.chat.ChatModel&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;dev.shaaf.jgraphlet.TaskPipeline&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;dev.shaaf.waver.llm.config.AppConfig&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;dev.shaaf.waver.llm.config.ModelProviderFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;dev.shaaf.waver.llm.tutorial.task.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.nio.file.Path&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.nio.file.Paths&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.logging.Logger&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TutorialGenerator&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TutorialGenerator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;


    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AppConfig&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;ChatModel&lt;/span&gt; &lt;span class="n"&gt;chatModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModelProviderFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildChatModel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;llmProvider&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Path&lt;/span&gt; &lt;span class="n"&gt;outputDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;absoluteOutputPath&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;projectName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"🚀 Starting Tutorial Generation for: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inputPath&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TaskPipeline&lt;/span&gt; &lt;span class="n"&gt;tasksPipeLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TaskPipeline&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;tasksPipeLine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Code-crawler"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CodeCrawlerTask&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Identify-abstraction"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IdentifyAbstractionsTask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;projectName&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Identify-relationships"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IdentifyRelationshipsTask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;projectName&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Chapter-organizer"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChapterOrganizerTask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Technical-writer"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TechnicalWriterTask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outputDir&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Meta-info"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MetaInfoTask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outputDir&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;projectName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inputPath&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
            &lt;span class="n"&gt;tasksPipeLine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inputPath&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n✅ Tutorial generation complete! Output located at: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;outputDir&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these Tasks in the TaskPipeline above are backed by a prompt. So basically its prompt chaining. There wasnt much about &lt;code&gt;SKILLS.md&lt;/code&gt; back then. Fast track to today. I have tried to take all the prompts and put them in a Skill instead. And atleast from the intial tests I am looking at its looking pretty good. For example here is a mermaid drawing generated from one of &lt;a href="https://github.com/sshaaf/scribe" rel="noopener noreferrer"&gt;Scribe's&lt;/a&gt; code base (an MCP server)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    KantraTool[KantraTool&amp;lt;br/&amp;gt;MCP Server Entry Point] --&amp;gt;|queries availability| CommandRegistry[CommandRegistry&amp;lt;br/&amp;gt;Plugin System]
    KantraTool --&amp;gt;|invokes execute| KantraCommand[KantraCommand&amp;lt;br/&amp;gt;Command Interface]
    KantraTool --&amp;gt;|accepts as parameter| KantraOperation[KantraOperation&amp;lt;br/&amp;gt;Operation Catalog]

    CommandRegistry --&amp;gt;|discovers via CDI| KantraCommand
    CommandRegistry --&amp;gt;|uses as map key| KantraOperation
    CommandRegistry --&amp;gt;|applies filtering| CommandConfig[Configuration System]

    KantraCommand --&amp;gt;|declares operation| KantraOperation

    ConcreteCommands[Concrete Command&amp;lt;br/&amp;gt;Implementations] --&amp;gt;|extends| AbstractCommand[AbstractCommand&amp;lt;br/&amp;gt;Command Base Class]
    ConcreteCommands --&amp;gt;|constructs| Rule[Rule&amp;lt;br/&amp;gt;Domain Model]
    ConcreteCommands --&amp;gt;|creates instances| Condition[Condition&amp;lt;br/&amp;gt;Polymorphic Detection]

    AbstractCommand --&amp;gt;|delegates serialization| RuleValidator[RuleValidator&amp;lt;br/&amp;gt;Validation Service]
    AbstractCommand --&amp;gt;|converts to enums| DomainEnums[Domain Enumerations&amp;lt;br/&amp;gt;JavaLocation, Category]
    AbstractCommand --&amp;gt;|constructs| SupportingModels[Supporting Model Classes&amp;lt;br/&amp;gt;Link, CustomVariable]

    Rule --&amp;gt;|contains when field| Condition
    Rule --&amp;gt;|uses category| DomainEnums
    Rule --&amp;gt;|composes| SupportingModels

    Condition --&amp;gt;|references| DomainEnums
    Condition --&amp;gt;|contains| SupportingModels

    RuleValidator --&amp;gt;|validates &amp;amp; serializes| Rule
    RuleValidator --&amp;gt;|validates recursively| Condition

    style KantraTool fill:#e1f5ff,stroke:#0066cc,stroke-width:3px
    style Rule fill:#fff4e1,stroke:#ff9900,stroke-width:2px
    style Condition fill:#fff4e1,stroke:#ff9900,stroke-width:2px
    style CommandRegistry fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style RuleValidator fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the SKILLS.md
&lt;/h3&gt;

&lt;p&gt;Lets take a look at the basic file structure when creating a skill. &lt;/p&gt;

&lt;h4&gt;
  
  
  File Structure
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SKILL.md&lt;/code&gt; - Main skill definition and instructions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package.json&lt;/code&gt; - NPM package configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;installer.js&lt;/code&gt; - Installation script&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bin/&lt;/code&gt; - Executable commands&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/&lt;/code&gt; - Test files, in my case these are test projects in multiple languages so I can compare whats generated. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Skills file is quite large (100+ lines), here is a little snippet from the code discover phase during anaylsis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;1.&lt;/span&gt; Determine project details:
&lt;span class="p"&gt;   -&lt;/span&gt; If user provided path in command (e.g., &lt;span class="sb"&gt;`/tutorial analyze ./src`&lt;/span&gt;), use that
&lt;span class="p"&gt;   -&lt;/span&gt; Otherwise ask: "What directory should I analyze?"
&lt;span class="p"&gt;   -&lt;/span&gt; Auto-detect primary language from file extensions
&lt;span class="p"&gt;   -&lt;/span&gt; Ask if they want to focus on specific areas (optional)
&lt;span class="p"&gt;
2.&lt;/span&gt; Find source files using Glob:
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Java**&lt;/span&gt;: &lt;span class="sb"&gt;`**/*.java`&lt;/span&gt; (exclude &lt;span class="sb"&gt;`**/test/**`&lt;/span&gt;, &lt;span class="sb"&gt;`**/target/**`&lt;/span&gt;)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Python**&lt;/span&gt;: &lt;span class="sb"&gt;`**/*.py`&lt;/span&gt; (exclude &lt;span class="sb"&gt;`**/test/**`&lt;/span&gt;, &lt;span class="sb"&gt;`**/__pycache__/**`&lt;/span&gt;, &lt;span class="sb"&gt;`**/venv/**`&lt;/span&gt;)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**JavaScript/TypeScript**&lt;/span&gt;: &lt;span class="sb"&gt;`**/*.{js,ts,jsx,tsx}`&lt;/span&gt; (exclude &lt;span class="sb"&gt;`**/node_modules/**`&lt;/span&gt;, &lt;span class="sb"&gt;`**/dist/**`&lt;/span&gt;, &lt;span class="sb"&gt;`**/build/**`&lt;/span&gt;)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Go**&lt;/span&gt;: &lt;span class="sb"&gt;`**/*.go`&lt;/span&gt; (exclude &lt;span class="sb"&gt;`**/*_test.go`&lt;/span&gt;, &lt;span class="sb"&gt;`**/vendor/**`&lt;/span&gt;)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**C#**&lt;/span&gt;: &lt;span class="sb"&gt;`**/*.cs`&lt;/span&gt; (exclude &lt;span class="sb"&gt;`**/bin/**`&lt;/span&gt;, &lt;span class="sb"&gt;`**/obj/**`&lt;/span&gt;)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Ruby**&lt;/span&gt;: &lt;span class="sb"&gt;`**/*.rb`&lt;/span&gt; (exclude &lt;span class="sb"&gt;`**/spec/**`&lt;/span&gt;, &lt;span class="sb"&gt;`**/test/**`&lt;/span&gt;)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Rust**&lt;/span&gt;: &lt;span class="sb"&gt;`**/*.rs`&lt;/span&gt; (exclude &lt;span class="sb"&gt;`**/target/**`&lt;/span&gt;)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**PHP**&lt;/span&gt;: &lt;span class="sb"&gt;`**/*.php`&lt;/span&gt; (exclude &lt;span class="sb"&gt;`**/vendor/**`&lt;/span&gt;, &lt;span class="sb"&gt;`**/tests/**`&lt;/span&gt;)
&lt;span class="p"&gt;
3.&lt;/span&gt; Handle large codebases:
&lt;span class="p"&gt;   -&lt;/span&gt; If &amp;gt;50 files found, ask: "Found {N} files. Analyze all or focus on specific subdirectory?"
&lt;span class="p"&gt;   -&lt;/span&gt; Suggest core directories: &lt;span class="sb"&gt;`src/main`&lt;/span&gt;, &lt;span class="sb"&gt;`lib`&lt;/span&gt;, &lt;span class="sb"&gt;`app`&lt;/span&gt;, etc.
&lt;span class="p"&gt;
4.&lt;/span&gt; Read discovered files with Read tool
&lt;span class="p"&gt;5.&lt;/span&gt; Report: "Found {N} files totaling {LOC} lines of code"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @sshaaf/tutorial-skill &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# reload the coding agent. e.g. claude.&lt;/span&gt;

/tutorial build

&lt;span class="c"&gt;#You can preview the files in markdown on disk. for example preview in VSCode. &lt;/span&gt;
&lt;span class="c"&gt;#However if you want to preview in html then use the following command.&lt;/span&gt;

/tutorial preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Skill Modes
&lt;/h3&gt;

&lt;p&gt;Currently the Skill is built into two modes. This is exactly what Waver also did, analyze the code base and then generte a contextual tutorial. Here I have broken down that process, so if users just want very basic documentation like the mermaid diagram above with some markdown of the analysis they can do that. Else building tutorial which is the second mode has more steps, to make in consumable and nice 😎&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/tutorial analyze&lt;/code&gt; - Analyze Mode&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3-stage pipeline for quick codebase understanding&lt;/li&gt;
&lt;li&gt;Generates architecture diagrams with Mermaid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NEW&lt;/strong&gt;: Option to save diagrams to disk&lt;/li&gt;
&lt;li&gt;Time: 2-5 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/tutorial build&lt;/code&gt; - Build Mode&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6-stage pipeline for comprehensive tutorial generation&lt;/li&gt;
&lt;li&gt;Creates multi-chapter Markdown tutorials&lt;/li&gt;
&lt;li&gt;Time: 10-30 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  More Commands
&lt;/h3&gt;

&lt;p&gt;While the above commands publish markdown onto disk, it can also be useful to preview them or even push them into github pages. Following will allow previewing locally and checking that it all works. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/tutorial preview&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local tutorial preview with HonKit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt;: 5-30 seconds to start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt;: Local docs site (usually &lt;code&gt;http://localhost:4000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use for&lt;/strong&gt;: Reviewing generated docs before publishing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/tutorial doctor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Diagnostics for local preview/runtime + docs scaffolding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt;: ~10-30 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt;: Pass/fail checklist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use for&lt;/strong&gt;: Verifying HonKit runtime + &lt;code&gt;book.json&lt;/code&gt; before publishing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The markdown can also be pushed into github pages. Here is a &lt;a href="https://github.com/kborup-redhat/ovro/blob/main/.github/workflows/docs.yml" rel="noopener noreferrer"&gt;nice example&lt;/a&gt; from &lt;a href="https://www.linkedin.com/in/kimborup/" rel="noopener noreferrer"&gt;@Kim Borup&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzysj1bbgcchhodvb054o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzysj1bbgcchhodvb054o.jpg" alt="Example" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to use it: Tutorial Generator &amp;amp; Analyzer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick analysis&lt;/span&gt;
/tutorial analyze &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# With path&lt;/span&gt;
/tutorial analyze ./src/main/java

&lt;span class="c"&gt;# Full tutorial&lt;/span&gt;
/tutorial build &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# With output directory&lt;/span&gt;
/tutorial build &lt;span class="nt"&gt;--output&lt;/span&gt; ./docs/tutorial

&lt;span class="c"&gt;# Preview generated tutorial in Claude mode&lt;/span&gt;
/tutorial preview ./docs/tutorial

&lt;span class="c"&gt;# Diagnose local preview/runtime/docs scaffolding&lt;/span&gt;
/tutorial doctor ./docs/tutorial

&lt;span class="c"&gt;# Initialize docs files for HonKit&lt;/span&gt;
npx @sshaaf/tutorial-skill docs init &lt;span class="nt"&gt;--dir&lt;/span&gt; ./docs/tutorial

&lt;span class="c"&gt;# Preview locally with HonKit&lt;/span&gt;
npx @sshaaf/tutorial-skill docs preview &lt;span class="nt"&gt;--dir&lt;/span&gt; ./docs/tutorial

&lt;span class="c"&gt;# Build static site with HonKit&lt;/span&gt;
npx @sshaaf/tutorial-skill docs build &lt;span class="nt"&gt;--dir&lt;/span&gt; ./docs/tutorial

&lt;span class="c"&gt;# Diagnose runtime/plugin setup&lt;/span&gt;
npx @sshaaf/tutorial-skill docs doctor &lt;span class="nt"&gt;--dir&lt;/span&gt; ./docs/tutorial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The difference
&lt;/h3&gt;

&lt;p&gt;SKILL works directly within Claude Code CLI - no separate tools needed, integrates into the developer workflow. No setup or installation and is pretty aware of the context. &lt;br&gt;
Waver on the other hand complete control on which models are being used, can run parallel and join tasks with its use of JGraphlet, can easily integarte into CI and batch modes for multiple projects etc. &lt;/p&gt;

&lt;p&gt;What happens if I edit some files and regenerate? Well how Gen-AI is, it might not chosse the same chapter names or order again. So making a diff is perhaps not useful. Context at that point would mismatch. &lt;br&gt;
So the real use is that the skill and prompt has nailed what it gets out of it. For me I think generating the first time is a good starting point, to start editing. I probably wont use Gen-AI over and over again for the same generation if I am happy with the first one. At its core, it gives a good structure to start with. &lt;/p&gt;

&lt;p&gt;Furthermore in the prompt you can ask the agent to do some checks and verifications after the tutorial is generated. However evals could be done as well, likely with other skills or mcp tools etc. This is important incase of hallucinations. &lt;/p&gt;

&lt;p&gt;I am not entirely sure if there is an &lt;strong&gt;either&lt;/strong&gt; or an &lt;strong&gt;or&lt;/strong&gt; here, I would still prefer waver just because its a simplified CLI approach, which is pretty solid for multiple cases, however as soon as we speak about developer workflow a Skill makes a lot more sense when one is using Claude or OpenCode etc. I think there is another bit of optmization here that can be investigated where I can take the waver project and integrate it within the skill, so the skill can handover some of the work. A back an forth to take the best of the two. &lt;/p&gt;

&lt;p&gt;I have to admit; defining constraints in plain English and letting a coding agent determine the implementation is quite daunting. It requires a significant mindset shift, and I’m eager to see how this scales and where the true limitations lie.&lt;/p&gt;

&lt;p&gt;The skill is live, and the journey from a standalone cli to an NPM-distributable skill is complete. Now, the real testing begins 😎😊&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;May 10 update: NPM package has been updated with some command deperecated (analyze, preveiw, doctor) and new features added e.g. asciidoc. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;View the project&lt;/strong&gt;: &lt;a href="https://github.com/sshaaf/tutorial-skill" rel="noopener noreferrer"&gt;github.com/sshaaf/tutorial-skill&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it out&lt;/strong&gt;: &lt;code&gt;npx @sshaaf/tutorial-skill&lt;/code&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>tools</category>
    </item>
    <item>
      <title>Rendering powerpoint to png files with JBang</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Wed, 29 Apr 2026 13:18:33 +0000</pubDate>
      <link>https://dev.to/sshaaf/rendering-powerpoint-to-png-files-with-jbang-1nhi</link>
      <guid>https://dev.to/sshaaf/rendering-powerpoint-to-png-files-with-jbang-1nhi</guid>
      <description>&lt;p&gt;Part of my daily work is to create instructions for workshops, and labs. One of the things in that entire content creation process, is to take screenshots and matching instructions and inorder to do that sometimes I need to add arrows, boxes, etc.. Using something like Google docs is pretty easy at that point, drag the screen shot create the overlay items. This time around I ended up having tons of such slides (a little over exaggeration). The next problem, download them as PNG files to add to the instructions. Out of all the nice usability tricks Google docs does not allow me to optimize on this. So I have to download one slide image at a time. yes really!! Well that was some rant, but hey now we have a JBang script that will do the rest. :) &lt;/p&gt;

&lt;p&gt;This post demonstrates how to leverage &lt;strong&gt;JBang&lt;/strong&gt; and &lt;strong&gt;Apache POI&lt;/strong&gt; to build a zero-ceremony script that renders PPTX slides to high-DPI PNGs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JBang: For utility scripts, the overhead of a Maven/Gradle project is a deterrent. &lt;a href="https://jbang.dev" rel="noopener noreferrer"&gt;JBang&lt;/a&gt; eliminates this by allowing "script-style" Java execution. It handles dependency resolution, compilation, and caching in the background, making Java a viable alternative to Python or Bash for system automation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay so here is the solution that uses &lt;strong&gt;Apache POI's Common SL (SlideShow)&lt;/strong&gt; API to parse the OOXML structure and &lt;strong&gt;Java 2D (Graphics2D)&lt;/strong&gt; for the rasterization.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Dependency Management
&lt;/h3&gt;

&lt;p&gt;Using JBang's &lt;code&gt;//DEPS&lt;/code&gt; directives, we pull in the necessary POI components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;///usr/bin/env jbang "$0" "$@" ; exit $?&lt;/span&gt;
&lt;span class="c1"&gt;//DEPS org.apache.poi:poi:5.2.5&lt;/span&gt;
&lt;span class="c1"&gt;//DEPS org.apache.poi:poi-ooxml:5.2.5&lt;/span&gt;
&lt;span class="c1"&gt;//DEPS org.apache.poi:poi-scratchpad:5.2.5&lt;/span&gt;
&lt;span class="c1"&gt;//DEPS org.apache.logging.log4j:log4j-core:2.20.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;poi-ooxml&lt;/code&gt; is required for &lt;code&gt;.pptx&lt;/code&gt; (XML-based) files, while &lt;code&gt;poi-scratchpad&lt;/code&gt; provides support for the older &lt;code&gt;.ppt&lt;/code&gt; (OLE2) format if needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Initializing the SlideShow
&lt;/h3&gt;

&lt;p&gt;We use &lt;strong&gt;Try-with-Resources&lt;/strong&gt; to ensure the &lt;code&gt;FileInputStream&lt;/code&gt; and &lt;code&gt;XMLSlideShow&lt;/code&gt; containers are disposed of correctly, preventing memory leaks during batch processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FileInputStream&lt;/span&gt; &lt;span class="n"&gt;fis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pptxFile&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="nc"&gt;XMLSlideShow&lt;/span&gt; &lt;span class="n"&gt;ppt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;XMLSlideShow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fis&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;Dimension&lt;/span&gt; &lt;span class="n"&gt;pgsize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ppt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPageSize&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// The logical slide dimensions&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;XSLFSlide&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slides&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ppt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSlides&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// ... iteration logic&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Scaling and Precision Math
&lt;/h3&gt;

&lt;p&gt;PowerPoint defines slide dimensions in &lt;strong&gt;points&lt;/strong&gt; (where 1 point = 1/72 inch). To produce high-resolution output (e.g., 300 DPI), we must calculate a scale factor relative to this 72 DPI baseline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetDpi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;72.0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ceil&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pgsize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ceil&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pgsize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Configuring the Graphics Context
&lt;/h3&gt;

&lt;p&gt;To avoid pixelation and "aliased" text, we must explicitly configure the &lt;code&gt;Graphics2D&lt;/code&gt; rendering pipeline.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hint Key&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KEY_ANTIALIASING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VALUE_ANTIALIAS_ON&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Smooths vector edges and shapes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KEY_TEXT_ANTIALIASING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VALUE_TEXT_ANTIALIAS_ON&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ensures readable, sharp typography.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KEY_INTERPOLATION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VALUE_INTERPOLATION_BICUBIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High-quality image downsampling/upsampling.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KEY_RENDERING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VALUE_RENDER_QUALITY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;General preference for fidelity over speed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BufferedImage&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BufferedImage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BufferedImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TYPE_INT_ARGB&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Graphics2D&lt;/span&gt; &lt;span class="n"&gt;graphics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createGraphics&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;graphics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRenderingHints&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;RenderingHints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;KEY_ANTIALIASING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RenderingHints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VALUE_ANTIALIAS_ON&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;RenderingHints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;KEY_INTERPOLATION&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RenderingHints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VALUE_INTERPOLATION_BICUBIC&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;RenderingHints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;KEY_TEXT_ANTIALIASING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RenderingHints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VALUE_TEXT_ANTIALIAS_ON&lt;/span&gt;
&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;graphics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Map logical points to pixel coordinates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Execution and Output
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;slide.draw(graphics)&lt;/code&gt; call is where the heavy lifting happens. Apache POI iterates through the slide's visual tree (shapes, text boxes, images) and issues the corresponding draw commands to our scaled graphics context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;slide&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;draw&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graphics&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;ImageIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PNG"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputName&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;graphics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the Script
&lt;/h3&gt;

&lt;p&gt;Once saved as &lt;code&gt;pptx2png.java&lt;/code&gt;, make the script executable and run it directly. JBang will fetch the JARs on the first run and cache them at &lt;code&gt;~/.jbang&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x pptx2png.java
./pptx2png.java my_deck.pptx out_prefix 300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well that made life a step easier. ;)&lt;br&gt;
Scritpt source available [here].(&lt;a href="https://github.com/sshaaf/jbang-catalog/blob/main/src/pptx2png.java" rel="noopener noreferrer"&gt;https://github.com/sshaaf/jbang-catalog/blob/main/src/pptx2png.java&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>java</category>
      <category>llm</category>
      <category>tools</category>
    </item>
    <item>
      <title>Modernizing Legacy Code with Konveyor AI: From EJB to Kubernetes</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Wed, 15 Apr 2026 11:06:09 +0000</pubDate>
      <link>https://dev.to/sshaaf/modernizing-legacy-code-with-konveyor-ai-from-ejb-to-kubernetes-3d70</link>
      <guid>https://dev.to/sshaaf/modernizing-legacy-code-with-konveyor-ai-from-ejb-to-kubernetes-3d70</guid>
      <description>&lt;p&gt;I always enjoy participating in KubeCon. This time it was at the RAI center in Amsterdam. I have been to many conferences and the ones that are the best IMHO are the ones that are very community focused. For example DevNexus for Java, GeeCon for Geeks ;), and obviously KubeCon for everything Kubernetes. And obvsiouly making new friends and connections is a great way of learning from all the cool stuff thats going on. Thats probably enough name dropping for a wednesday ;) &lt;/p&gt;

&lt;p&gt;I had the opportunity to represent the &lt;a href="https://konveyor.io/" rel="noopener noreferrer"&gt;Konveyor Community&lt;/a&gt; project update. &lt;/p&gt;

&lt;h2&gt;
  
  
  Modernizing Legacy Code with Konveyor AI: From EJB to Kubernetes
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"We want to have a meaningful result out of generative AI, not just chatting around... we can context engineer that code, send it into the LLM, and get results integrated back into the codebase." — &lt;strong&gt;Shaaf Syed&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Application modernization is a daunting task, especially when dealing with legacy codebases that are decades old. In my recent CNCF Project Lightning Talk, I introduced how the &lt;strong&gt;Konveyor&lt;/strong&gt; community is leveraging AI and static analysis to automate this process for almost any programming language. While there are multiple ways of solving the migration and modernization challenges, &lt;a href="https://konveyor.io/" rel="noopener noreferrer"&gt;Konveyor community&lt;/a&gt; takes a unique approach to it. &lt;/p&gt;

&lt;p&gt;For example many enterprises still rely on 30-year-old Enterprise Java Beans (EJB) or legacy protocols like RMI/IIOP. When leadership asks to move these applications to Kubernetes, developers face a nightmare of serialization issues, stubs, and complex clustering logic that simply doesn't fit a modern cloud-native environment. It like you just encountered the "Legacy Wall!". Nothing can get passed that point. True, but hardly anymore IMHO. Why not use the traditional static code anaylsis to enahnce the responses of an LLM. &lt;/p&gt;

&lt;p&gt;During the talk, I demonstrated a live example of an outdated EJB, i.e. being transformed into a modern REST service. What would normally take hours of manual refactoring was reduced to a generated &lt;strong&gt;Git patch&lt;/strong&gt; that the developer could simply review and apply.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; youtube 6FNR4jGox9w &amp;gt;}}&lt;/p&gt;

&lt;p&gt;Konveyor solves this by combining deep static code analysis with the power of Large Language Models (LLMs). The core workflow involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Static Code Analysis:&lt;/strong&gt; The engine analyzes source code (Java, Go, Python, NodeJS, etc.) to identify "incidents"—specific lines of code that won't run on Kubernetes.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Context Engineering:&lt;/strong&gt; By understanding the code paths via the Language Server Protocol (LSP), Konveyor provides the necessary context to an LLM.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Automated Remediation:&lt;/strong&gt; Instead of just chatting, the AI generates meaningful code fixes, such as replacing legacy protocols with REST endpoints or suggesting the use of Kubernetes Secrets and ConfigMaps.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Beyond Chat: Agents and Memory
&lt;/h3&gt;

&lt;p&gt;What makes Konveyor AI particularly powerful is its move toward &lt;strong&gt;Agentic AI&lt;/strong&gt; and &lt;strong&gt;Distributed Memory&lt;/strong&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Agentic Validation
&lt;/h4&gt;

&lt;p&gt;When AI fixes one part of the code, it often breaks another. Konveyor’s agents handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compilation:&lt;/strong&gt; Ensuring the new code actually builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation Testing:&lt;/strong&gt; Running tests to verify functionality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanitization:&lt;/strong&gt; Cleaning up the output before it reaches the developer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Organizational Memory
&lt;/h4&gt;

&lt;p&gt;If a developer modifies an AI-generated fix (e.g., changing how exceptions are handled), Konveyor can "remember" that preference. This memory is shared across the organization, so future migrations automatically follow the team's specific coding standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://konveyor.io/" rel="noopener noreferrer"&gt;Konveyor&lt;/a&gt; is an open-source community project. You can find them at their CNCF kiosk or join the community to help build the future of automated application modernization.&lt;/p&gt;

</description>
      <category>java</category>
      <category>llm</category>
      <category>tools</category>
    </item>
    <item>
      <title>Nano Agent, Mega Senses: Adding LSP to the 260-Line Coding Agent</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Fri, 10 Apr 2026 05:19:09 +0000</pubDate>
      <link>https://dev.to/sshaaf/nano-agent-mega-senses-adding-lsp-to-the-260-line-coding-agent-3343</link>
      <guid>https://dev.to/sshaaf/nano-agent-mega-senses-adding-lsp-to-the-260-line-coding-agent-3343</guid>
      <description>&lt;p&gt;Learn, learn, and learn more—that’s the name of the game. Coding agents are innovating fast; things are getting bigger and, quite often, bloated. To understand what an agent is actually doing, I’ve found it’s best to go back to the basics. It takes a bit more time, but the expertise you gain along the way sets you up for the long haul." So here I read &lt;a href="https://xam.dk/blog/nanocode-coding-agent-in-260-lines-of-java/" rel="noopener noreferrer"&gt;Max's&lt;/a&gt; post and thought, how about add some more things to this. Fetching ideas... done.. Lets add LSP support. &lt;/p&gt;

&lt;p&gt;The original &lt;a href="https://github.com/1rgs/nanocode" rel="noopener noreferrer"&gt;nanocode&lt;/a&gt; idea is easy to lose in the hype: &lt;strong&gt;a coding agent is mostly a loop&lt;/strong&gt;. You send prompt plus tool definitions to the model; the model answers with text or tool calls; you execute tools, feed results back, and repeat. The heavy lifting is the model; your code is &lt;strong&gt;hands&lt;/strong&gt; (write, edit, shell) and &lt;strong&gt;eyes&lt;/strong&gt; (read, glob, grep).&lt;/p&gt;

&lt;p&gt;That pattern is how “real” agents are structured too — usually with more polish, safety rails, and often &lt;strong&gt;richer senses&lt;/strong&gt; than plain text search.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://github.com/sshaaf/nanocode" rel="noopener noreferrer"&gt;fork&lt;/a&gt; keeps that loop and the same six core tools. It adds an optional extra sense for Java workspaces: a &lt;strong&gt;language server&lt;/strong&gt;, so the model can ask project-aware questions instead of inferring everything from raw files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvarlr5ftdm7gl2r91a9j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvarlr5ftdm7gl2r91a9j.jpg" alt="nanocode Java with LSP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Does LSP fits the same architecture?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;read&lt;/code&gt; are universal and powerful. They are also &lt;strong&gt;syntax-blind&lt;/strong&gt;. A symbol might be a field, a local, an import alias, or a string that happens to match. For Java — especially with Maven or Gradle classpaths — the &lt;strong&gt;compiler’s view&lt;/strong&gt; of the project is not the same as “all lines containing this word.”&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://microsoft.github.io/language-server-protocol/" rel="noopener noreferrer"&gt;Language Server Protocol&lt;/a&gt; is the boring industry answer: one process holds the classpath, incremental errors, types, and navigation graph; the client sends small questions (&lt;code&gt;go to definition&lt;/code&gt;, &lt;code&gt;hover&lt;/code&gt;, &lt;code&gt;publishDiagnostics&lt;/code&gt;) and gets structured answers.&lt;/p&gt;

&lt;p&gt;nanocode is a thin client in that sense: it forwards requests and prints results. Wiring &lt;strong&gt;Eclipse JDT Language Server&lt;/strong&gt; (the same engine behind most Java editor extensions) is not a different &lt;em&gt;kind&lt;/em&gt; of product — it is &lt;strong&gt;narrower, deeper vision&lt;/strong&gt; for one language, and stays optional so a “single-file story” remains when you do not need Java semantics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this fork adds (in practice)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Detection&lt;/strong&gt; — If the workspace contains &lt;code&gt;.java&lt;/code&gt; files, nanocode can offer to turn Java support on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local install&lt;/strong&gt; — On consent (or via an environment flag), it downloads JDT LS once into your user cache (&lt;code&gt;~/.cache/nanocode&lt;/code&gt; or &lt;code&gt;$XDG_CACHE_HOME/nanocode&lt;/code&gt;), not into the repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process + protocol&lt;/strong&gt; — A small &lt;a href="https://github.com/eclipse-lsp4j/lsp4j" rel="noopener noreferrer"&gt;LSP4J&lt;/a&gt; client drives the server; documents are synced before semantic queries so answers match what is on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three extra tools&lt;/strong&gt; the model sees only when LSP is enabled:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;java_definition&lt;/code&gt;&lt;/strong&gt; — Where is this name bound? (1-based line/column, like many editors.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;java_hover&lt;/code&gt;&lt;/strong&gt; — Tooltip-grade material: Javadoc, type hints, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;java_diagnostics&lt;/code&gt;&lt;/strong&gt; — Compiler and analysis diagnostics for one file, or a roll-up across files the server has published.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main script grew; supporting code lives alongside it so newcomers can still read the core agent in one place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;  &lt;span class="cm"&gt;/**
   * Dispatch tool calls to their respective implementations.
   *
   * Routes tool execution based on the tool name. Basic tools (read, write, edit, glob, grep, bash)
   * are always available. Java LSP tools (java_definition, java_hover, java_diagnostics) require
   * the javaLsp flag to be enabled.
   *
   * @param name The name of the tool to execute
   * @param args JSON arguments for the tool
   * @param javaLsp Whether Java LSP tools are enabled
   * @return Tool execution result, or error message prefixed with ERROR_PREFIX
   */&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;runTool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;javaLsp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// Basic file and search tools&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"read"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toolRead&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"write"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toolWrite&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"edit"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toolEdit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"glob"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toolGlob&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"grep"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toolGrep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"bash"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toolBash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

              &lt;span class="c1"&gt;// Java LSP tools - require javaLsp flag&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"java_definition"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;javaLsp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                      &lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="no"&gt;ERROR_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"Java LSP not enabled"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                  &lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;toolJavaDefinition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="o"&gt;}&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"java_hover"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;javaLsp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                      &lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="no"&gt;ERROR_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"Java LSP not enabled"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                  &lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;toolJavaHover&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="o"&gt;}&lt;/span&gt;
              &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"java_diagnostics"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;javaLsp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                      &lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="no"&gt;ERROR_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"Java LSP not enabled"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                  &lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;toolJavaDiagnostics&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
              &lt;span class="o"&gt;}&lt;/span&gt;

              &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ERROR_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"unknown tool "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
          &lt;span class="o"&gt;};&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Catch all exceptions and return as error message&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;ERROR_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Java LSP tool: Go to the definition of a symbol at the specified location.
   *
   * @param args JSON with path (required), line (default 1), column (default 1)
   * @return Location information for the symbol's definition
   */&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;toolJavaDefinition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JavaLspSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"line"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
              &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"column"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Java LSP tool: Get type information, signature, and documentation for a symbol at cursor.
   *
   * @param args JSON with path (required), line (default 1), column (default 1)
   * @return Type info, Javadoc, and signature details for the symbol
   */&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;toolJavaHover&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JavaLspSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hover&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"line"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
              &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"column"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Java LSP tool: Get compiler errors and warnings for a file or all cached files.
   *
   * @param args JSON with optional path (empty string = all cached diagnostics)
   * @return List of compiler/LSP diagnostics
   */&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;toolJavaDiagnostics&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JavaLspSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;diagnostics&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Examples (prompts that now “make sense”)
&lt;/h2&gt;

&lt;p&gt;These are the sorts of tasks where &lt;strong&gt;text tools alone&lt;/strong&gt; are brittle, but &lt;strong&gt;JDT + the loop&lt;/strong&gt; line up nicely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigate instead of guessing&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“In &lt;code&gt;src/main/java/.../OrderService.java&lt;/code&gt;, around line 48, what type is &lt;code&gt;discount&lt;/code&gt;? Use java_hover at that position, then java_definition if it is a field.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The model no longer has to re-derive the type from imports and local inference across half a dozen files unless it wants to double-check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Errors with a real classpath&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Run java_diagnostics on &lt;code&gt;PaymentMapper.java&lt;/code&gt; and fix whatever JDT reports; then run &lt;code&gt;mvn -q test&lt;/code&gt; in bash.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Diagnostics are tied to how the project is built, not to “did my regex find the word &lt;code&gt;error&lt;/code&gt; in the log.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onboarding a module&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’m new to this package. Summarize the main entry points: use glob for &lt;code&gt;*Application.java&lt;/code&gt;, read those files, and for the class that implements &lt;code&gt;Runnable&lt;/code&gt; or hosts &lt;code&gt;main&lt;/code&gt;, show java_hover on the superclass or key API types.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read + LSP combine the way humans use an IDE: skim files, drill into symbols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails before a large edit&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Before we rename this method, list java_diagnostics for the whole workspace (no path) and tell me if anything is already red.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You still do not get a one-click &lt;strong&gt;rename refactor&lt;/strong&gt; in this fork — but you get &lt;strong&gt;honest compiler state&lt;/strong&gt;, which is often enough to avoid stepping on a broken build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;────────────────────────────────────────────────────────────────────────────────
❯ What does `GET_USERS` mean in `KeycloakOperation`?
────────────────────────────────────────────────────────────────────────────────

⏺ Java_hover(src/main/java/dev/shaaf/keycloak/mcp/server/KeycloakOperation.java)
  ⎿  dev ... +1 lines

⏺ Glob(**/KeycloakOperation.java)
  ⎿  ./src/main/java/dev/shaaf/keycloak/mcp/server/KeycloakOperat...

⏺ Read(src/main/java/dev/shaaf/keycloak/mcp/server/KeycloakOperation.java)
  ⎿     1| package dev.shaaf.keycloak.mcp.server; ... +67 lines

⏺ `GET_USERS` in the `KeycloakOperation` enum is a constant that represents an operation related to retrieving a list of users from the Keycloak server. This operation is part of a group of user operations defined in the enum, which allows users of the `KeycloakTool` to easily reference and execute actions related to user management, such as creating, updating, deleting users, and retrieving user details.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ground this in something non-trivial, the same &lt;strong&gt;Java LSP stack&lt;/strong&gt; was exercised against &lt;strong&gt;&lt;code&gt;keycloak-mcp-server&lt;/code&gt;&lt;/strong&gt;: a Quarkus-based MCP server on Java 21, Maven BOM, and many command classes under &lt;code&gt;dev.shaaf.keycloak.mcp.server&lt;/code&gt;. Typical results: clean diagnostics on command base classes, classpath-backed &lt;strong&gt;hover&lt;/strong&gt; on &lt;code&gt;ObjectMapper&lt;/code&gt; (Jackson JAR version matching the build), and &lt;strong&gt;definition&lt;/strong&gt; jumping to the real &lt;code&gt;KeycloakCommand&lt;/code&gt; source — the sort of answers &lt;code&gt;grep&lt;/code&gt; alone cannot guarantee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trying JDT without an LLM API key:&lt;/strong&gt; the fork ships &lt;code&gt;LspProbe.java&lt;/code&gt; (JBang), a tiny harness that calls the same &lt;code&gt;JavaLspSupport&lt;/code&gt; as nanocode’s &lt;code&gt;java_*&lt;/code&gt; tools. From the nanocode repo:&lt;br&gt;&lt;br&gt;
&lt;code&gt;jbang run LspProbe.java /path/to/your-maven-project&lt;/code&gt;&lt;br&gt;&lt;br&gt;
(optional: &lt;code&gt;LSP_PROBE_SYNC_SEC&lt;/code&gt; to tune wait for Maven sync.) First run may download JDT (~50 MB) into &lt;code&gt;~/.cache/nanocode/&lt;/code&gt;; after that you can confirm hover, definition, and diagnostics on disk before wiring up &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;, &lt;code&gt;OPENROUTER_API_KEY&lt;/code&gt;, or &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs worth saying out loud
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weight&lt;/strong&gt; — JDT LS is not a toy; it is a real JVM product. The agent stays teachable; the server is the same class of dependency serious Java tooling already uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tokens&lt;/strong&gt; — A richer tool list and longer system text cost a little each request; &lt;strong&gt;targeted&lt;/strong&gt; LSP results can replace huge file reads. Net savings are not guaranteed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust&lt;/strong&gt; — &lt;code&gt;bash&lt;/code&gt; remains full power. LSP does not sandbox anything; it mainly reduces wrong guesses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nano&lt;/strong&gt; -- Nano but at about 4x more code from the original port.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Read it, run it, break it, extend it. It’s a great way to understand how tools like Claude Code, Cursor, and Co Pilot work under the hood.  -- &lt;a href="https://xam.dk/blog/nanocode-coding-agent-in-260-lines-of-java/" rel="noopener noreferrer"&gt;Max&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Nanocode with LSP - &lt;a href="https://github.com/sshaaf/nanocode" rel="noopener noreferrer"&gt;https://github.com/sshaaf/nanocode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A Coding Agent in 260 Lines of Java - &lt;a href="https://xam.dk/blog/nanocode-coding-agent-in-260-lines-of-java/" rel="noopener noreferrer"&gt;https://xam.dk/blog/nanocode-coding-agent-in-260-lines-of-java/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original demo agent: github.com/1rgs/nanocode - &lt;a href="https://github.com/1rgs/nanocode" rel="noopener noreferrer"&gt;https://github.com/1rgs/nanocode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Max’s JBang port: github.com/maxandersen/nanocode - &lt;a href="https://github.com/maxandersen/nanocode" rel="noopener noreferrer"&gt;https://github.com/maxandersen/nanocode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Eclipse JDT Language Server: eclipse-jdtls/eclipse.jdt.ls - &lt;a href="https://github.com/eclipse-jdtls/eclipse.jdt.ls" rel="noopener noreferrer"&gt;https://github.com/eclipse-jdtls/eclipse.jdt.ls&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>llm</category>
      <category>tools</category>
    </item>
    <item>
      <title>Java + LLMs: A hands-on guide to building LLM Apps in Java with Jakarta</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Sat, 08 Feb 2025 08:12:51 +0000</pubDate>
      <link>https://dev.to/sshaaf/java-llms-a-hands-on-guide-to-building-llm-apps-in-java-with-jakarta-4adf</link>
      <guid>https://dev.to/sshaaf/java-llms-a-hands-on-guide-to-building-llm-apps-in-java-with-jakarta-4adf</guid>
      <description>&lt;p&gt;&lt;a href="https://foojay.io/" rel="noopener noreferrer"&gt;Java&lt;/a&gt; is an amazing language to work with. Millions of developers use it for daily work routines, and many mission-critical applications run on Java today. Whether we talk about banks, stock exchanges, or space, Java is prevalent and a language of choice. &lt;/p&gt;

&lt;p&gt;With the advent of Large Language Models(LLM), new opportunities are at play. While Python has been the dominating language runtime for apparent reasons, there is a misconception that creating applications, agents, or other components for LLMs should also be done in Python. Most of the integration in LLMs is achieved by using REST API. Java is not short on anything in that space. System integration has been pretty awesome in Java, with many tools in that space. So why not Java? &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.langchain4j.dev/" rel="noopener noreferrer"&gt;LangChain4J&lt;/a&gt;! The amazing developers and &lt;a href="https://github.com/langchain4j/langchain4j/graphs/contributors" rel="noopener noreferrer"&gt;contributors&lt;/a&gt; have provided many goodies for us to infuse LLMs into our apps. Use LangChain4J if you haven't done so yet. &lt;/p&gt;

&lt;p&gt;I had the opportunity to speak with &lt;a href="https://x.com/bazlur_rahman" rel="noopener noreferrer"&gt;A N M Bazlur&lt;/a&gt; about this topic at the JChampionsConf last week. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/4fFZjtDAXW0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The source code and step-by-step guide is available here on &lt;a href="https://github.com/rokon12/llm-jakarta" rel="noopener noreferrer"&gt;github&lt;/a&gt;. &lt;br&gt;
And the &lt;a href="https://speakerdeck.com/sshaaf/java-plus-llms-a-hands-on-guide-to-building-llm-apps-in-java-with-jakarta" rel="noopener noreferrer"&gt;speakerdeck&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>java</category>
      <category>llm</category>
      <category>langchain</category>
      <category>ai</category>
    </item>
    <item>
      <title>Embracing the Future of Application Modernization with KAI</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Mon, 29 Jul 2024 16:16:20 +0000</pubDate>
      <link>https://dev.to/sshaaf/embracing-the-future-of-application-modernization-with-kai-1hp</link>
      <guid>https://dev.to/sshaaf/embracing-the-future-of-application-modernization-with-kai-1hp</guid>
      <description>&lt;p&gt;&lt;a href="https://www.konveyor.io" rel="noopener noreferrer"&gt;Konveyor’s&lt;/a&gt; main strength lies in its comprehensive approach to migration and modernization. At the core of &lt;a href="https://www.konveyor.io" rel="noopener noreferrer"&gt;Konveyor’s&lt;/a&gt; functionality is its powerful analysis engine. This engine performs static source code analysis, identifying anti-patterns and issues that might hinder the application’s operation on a target platform. Utilizing community standards like the Language Server Protocol, Konveyor's analysis engine uses rules designed to aid in various migration scenarios. Users can also create custom rules to address specific migration needs, enhancing Konveyor's flexibility and adaptability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Konveyor AI - "KAI"
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/konveyor/kai" rel="noopener noreferrer"&gt;Konveyor AI&lt;/a&gt;, the latest advancement under the Konveyor umbrella, leverages generative AI to further streamline application modernization. The primary goal of Konveyor AI (or Kai) is to automate source code changes, thereby improving the economics of migration and modernization efforts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our approach is to use static code analysis to find the areas in source code that need to be transformed. 'kai' will iterate through analysis information and work with LLMs to generate code changes to resolve incidents identified from analysis. This approach does not require fine-tuning of LLMs, we augment a LLMs knowledge via the prompt, similar to approaches with &lt;a href="https://arxiv.org/abs/2005.11401" rel="noopener noreferrer"&gt;RAG&lt;/a&gt; by leveraging external data from inside of Konveyor and from Analysis Rules to aid the LLM in constructing better results. For example, &lt;a href="https://github.com/konveyor/analyzer-lsp/blob/main/docs/rules.md" rel="noopener noreferrer"&gt;analyzer-lsp Rules&lt;/a&gt; such as these (&lt;a href="https://github.com/konveyor/rulesets/tree/main/default/generated/quarkus" rel="noopener noreferrer"&gt;Java EE to Quarkus rulesets&lt;/a&gt;) are leveraged to aid guiding a LLM to update a legacy Java EE application to Quarkus&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is an &lt;a href="https://www.konveyor.io/blog/kai-deep-dive-2024/" rel="noopener noreferrer"&gt;awesome blog post&lt;/a&gt; by &lt;a href="https://github.com/jwmatthews" rel="noopener noreferrer"&gt;John Matthews&lt;/a&gt; one of the Lead engineers in the project explaining "Kai" in depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  BYO Large Language Model
&lt;/h3&gt;

&lt;p&gt;One of the standout features of Kai is its model-agnostic approach. Unlike other solutions, Kai doesn’t bundle any specific large language model (LLM). Instead, it extends Konveyor to interact with various LLMs, providing the flexibility to use the best-suited model for each specific migration context. This approach ensures that organizations can optimize their migration strategies without being locked into a single technology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical usecase: From Java EE to Quarkus
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/cXJPHdETwcY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The short demonstration shows how Konveyor AI facilitates migrations from legacy frameworks like Java EE to modern solutions like Quarkus. The process begins with a static code analysis using the Konveyor CLI, which identifies migration issues within the codebase. Once the analysis is complete, Konveyor AI steps in to generate patches for the identified issues, leveraging LLMs to suggest precise code changes.&lt;/p&gt;

&lt;p&gt;For instance, outdated Java EE annotations are seamlessly replaced with their modern Jakarta EE counterparts, and JMS-based message-driven beans were converted to JakartaEE annotations, and legacy technology like EJBs turned into REST end points. These changes can be validated by a developer within an integrated development environment (IDE), showcasing how Konveyor AI integrates into existing developer workflows. Konveyor AI does this by using KAI extension.&lt;/p&gt;

&lt;p&gt;For a &lt;a href="https://www.youtube.com/watch?v=0eh-B55zMPI&amp;amp;t=1s" rel="noopener noreferrer"&gt;detailed demo and presentation&lt;/a&gt; watch this episode of &lt;a href="https://commons.openshift.org/" rel="noopener noreferrer"&gt;OpenShift commons&lt;/a&gt; where &lt;a href="https://www.linkedin.com/in/rromannissen/" rel="noopener noreferrer"&gt;Ramón&lt;/a&gt; and I go through the details.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Why should I use Konveyor AI's or how?&lt;/code&gt; the power of "Kai" lies in its ability to automate repetitive migration tasks, leveraging accumulated knowledge from previous migrations. By focusing on code transformation rather than architectural changes, Kai provides a robust tool for modernizing applications efficiently. It empowers developers to make informed decisions, enabling smoother transitions to modern frameworks and cloud-native environments.&lt;/p&gt;

&lt;p&gt;To give the latest build a try head out the to instructions &lt;a href="https://github.com/konveyor/kai" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get involved in the &lt;a href="https://www.konveyor.io" rel="noopener noreferrer"&gt;Konveyor community&lt;/a&gt; through their mailing lists, &lt;a href="https://www.konveyor.io/slack/" rel="noopener noreferrer"&gt;Slack channels&lt;/a&gt;, and &lt;a href="https://www.youtube.com/@konveyor361" rel="noopener noreferrer"&gt;regular meetings&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>java</category>
      <category>migration</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Java monitoring: Exploring Cryostat 2.4 features on OpenShift</title>
      <dc:creator>Shaaf Syed</dc:creator>
      <pubDate>Mon, 18 Dec 2023 21:38:37 +0000</pubDate>
      <link>https://dev.to/sshaaf/java-monitoring-exploring-cryostat-24-features-on-openshift-5527</link>
      <guid>https://dev.to/sshaaf/java-monitoring-exploring-cryostat-24-features-on-openshift-5527</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Orignally posted at&lt;/em&gt; &lt;a href="https://developers.redhat.com/articles/2023/12/18/java-monitoring-exploring-cryostat-24-features-openshift" rel="noopener noreferrer"&gt;Red Hat Developers&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://developers.redhat.com/products/cryostat/overview" rel="noopener noreferrer"&gt;Red Hat's latest build of Cryostat 2.4&lt;/a&gt;, designed specifically for the &lt;a href="https://developers.redhat.com/products/openshift/overview" rel="noopener noreferrer"&gt;Red Hat OpenShift Container Platform&lt;/a&gt;, brings a wealth of features and enhancements that cater to various monitoring needs for &lt;a href="https://developers.redhat.com/java" rel="noopener noreferrer"&gt;Java&lt;/a&gt; applications.&lt;/p&gt;

&lt;p&gt;At its core, Cryostat 2.4 excels in comprehensive Java Flight Recorder (JFR) data management. Users can effortlessly start, stop, retrieve, archive, import, and export JFR data, all through an intuitive web console or an accessible HTTP API. This enhances the ease with which developers can handle JVM performance data. Moreover, Cryostat 2.4 provides flexibility in terms of data storage and analysis. Users can store and analyze JFR data directly on their Red Hat OpenShift or export it to external monitoring applications for a deeper dive into the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;ARM (aarch64)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A significant enhancement in Cryostat 2.4 is its support for Red Hat OpenShift Container Platform 4.11 and later versions, particularly for the ARM64 (aarch64) architecture. This broadens its applicability across various platforms, making it a versatile tool in diverse environments, e.g., edge deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Smart triggering and HTTP API&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Another notable feature is the introduction of dynamic JFR recording with MBean custom triggers. The Cryostat agent is equipped with smart triggers that continuously monitor MBean counter values such as runtime, memory, thread, and operating system metrics. Users are able to set custom trigger conditions, thereby adding a layer of precision to JVM monitoring.&lt;/p&gt;

&lt;p&gt;Further enhancing its functionality, Cryostat 2.4 introduces an improved HTTP API provided by the Cryostat agent. This serves as an alternative to an application’s JMX port, allowing users to fully utilize Cryostat's features without the need for JMX port exposure in target applications. This is particularly beneficial for enhancing security and simplifying configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;JAR distribution&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cryostat 2.4 also offers flexibility in agent deployment. It provides two types of agent JAR file distributions—an all-in-one "shaded" JAR file that includes all dependencies, and a standard JAR file containing only the agent code. This choice caters to different user requirements and helps manage potential dependency conflicts more effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Topology dashboard view&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Additionally, Cryostat 2.4 brings new features and fixes to enhance user experience. The Topology and Dashboard views in the Cryostat web console now display additional information about target JVMs, such as operating system name, memory statistics, class path, library paths, input arguments, and system properties. The introduction of a parameter to restart flight recordings offers more control over recording management, addressing a common user pain point.&lt;/p&gt;

&lt;p&gt;Significant issues from previous versions have also been addressed. Automated rule-triggering issues in discovered JVM targets have been resolved, with Cryostat performing regular rechecks and reattempts at rule triggering. This ensures a more reliable connection with JVMs, improving consistency and predictability. The agent registration protocol has also seen improvements, resolving issues related to agent registration with the Cryostat server and ensuring a smoother, more reliable process.&lt;/p&gt;

&lt;p&gt;Cryostat 2.4 is a testament to Red Hat's commitment to providing robust and innovative solutions for modern containerized applications. With its enhanced features, improved user experience, and greater flexibility, Cryostat 2.4 is poised to be a pivotal tool in JVM monitoring, offering a sophisticated and user-friendly approach to performance monitoring in containerized environments. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How to use Cryostat for your Java workloads&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You can &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_build_of_cryostat/2/html/getting_started_with_cryostat/installing-cryostat-on-openshift-using-an-operator_cryostat" rel="noopener noreferrer"&gt;install the Red Hat build of Cryostat&lt;/a&gt; using our OpenShift operator, available in &lt;a href="https://developers.redhat.com/products/openshift/overview" rel="noopener noreferrer"&gt;Red Hat OpenShift&lt;/a&gt;'s Operator Hub.&lt;br&gt;
For non-production usage, you can also try our &lt;a href="https://developers.redhat.com/articles/2022/06/20/install-cryostat-new-helm-chart" rel="noopener noreferrer"&gt;Helm chart&lt;/a&gt;, included as part of OpenShift’s Helm chart repository.&lt;br&gt;
You can also try Red Hat build of Cryostat &lt;a href="https://developers.redhat.com/products/cryostat/getting-started" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Get support for Java&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Support for Cryostat, OpenJDK, and Eclipse Temurin is available to Red Hat customers through a subscription to&lt;a href="https://www.redhat.com/en/products/runtimes" rel="noopener noreferrer"&gt; Red Hat Runtimes&lt;/a&gt;, &lt;a href="https://developers.redhat.com/products/rhel/overview" rel="noopener noreferrer"&gt;Red Hat Enterprise Linux&lt;/a&gt;, and Red Hat OpenShift. Contact your local Red Hat representative or&lt;a href="https://www.redhat.com/en/about/contact/sales" rel="noopener noreferrer"&gt; Red Hat sales&lt;/a&gt; for more details. You can expect support for Java and other runtimes as described under the&lt;a href="https://access.redhat.com/support/policy/updates/jboss_notes/" rel="noopener noreferrer"&gt; Red Hat Product Update and Support Lifecycle&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Resources&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.redhat.com/java" rel="noopener noreferrer"&gt;Catch up with the latest on Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=rKG6nvk9xlE" rel="noopener noreferrer"&gt;Video: What is Eclipse Temurin?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://access.redhat.com/documentation/en-us/openjdk/17/html-single/getting_started_with_eclipse_temurin/index" rel="noopener noreferrer"&gt;Getting started with Eclipse Temurin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.redhat.com/en/blog/red-hat-joins-eclipse-adoptium-working-group" rel="noopener noreferrer"&gt;Red Hat joins the Eclipse Adoptium Working Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.redhat.com/en/blog/eclipse-adoptium-achieves-its-first-java-se-release" rel="noopener noreferrer"&gt;Eclipse Adoptium achieves its first Java SE release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.redhat.com/en/about/press-releases/red-hat-introduces-commercial-support-openjdk-microsoft-windows" rel="noopener noreferrer"&gt;Red Hat Introduces Commercial Support for OpenJDK on Microsoft Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.redhat.com/en/blog/history-and-future-openjdk" rel="noopener noreferrer"&gt;The history and future of OpenJDK&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>monitoring</category>
      <category>container</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
