agentsclimarketplace

Codelens ratpack analysis

Skill charliek/codelens/skills/codelens-ratpack-analysis

Use this skill for any request to examine, search, inventory, count, or map an existing Ratpack codebase, or to scope, size, order, or estimate the effort of migrating one off Ratpack onto another JVM framework (Spring, Micronaut, Helidon, etc.). It covers: finding route handlers and Chain routing, building a route inventory, measuring Promise/Blocking.get/fork/async usage, spotting handlers that block the compute thread, locating Guice modules and Registry bindings, and listing external integrations. Ratpack hides handlers and routes inside lambdas, invokedynamic, and bytecode that plain grep and generic search miss, so reach for this skill even when the request sounds like a simple "find the handlers," "count the Promise usage," or grep-style lookup over the code. Skip it for writing or scaffolding new Ratpack code, conceptual "how does Ratpack work" questions, and analyzing apps not built on Ratpack (plain Spring, Struts, etc.). It is a worked example of framework-specific analysis built entirely from CodeLens's general primitives — `classes`, `calls`, `xref`, `deps`, `methods`, `annotations`, `source` — with the Ratpack knowledge supplied by this skill's reference docs.From its SKILL.md

Install
npx -y skills add charliek/codelens --skill codelens-ratpack-analysis

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

14.0 KB, as published. Nobody here has run it

Ratpack Migration Analysis

CodeLens deliberately ships no Ratpack-specific features. Instead it exposes a small set of framework-agnostic primitives that return exhaustive bytecode facts. This skill is the worked example of turning those facts into a Ratpack migration assessment: you supply the Ratpack knowledge (which FQNs are handlers, what the Promise API looks like — see the reference docs), CodeLens supplies the data, and you supply the judgment (complexity, ordering, severity). Nothing here is a tool verdict; you read raw facts and explain your reasoning.

When to use

  • Scope or plan a migration off Ratpack (to Spring MVC/WebFlux, Micronaut, etc.).
  • Inventory handlers, routes, Promise/async usage, Guice DI, external integrations.
  • Find constructs that complicate a migration (blocking on the compute thread, etc.).
  • Propose a migration order grounded in the dependency graph.

Prerequisites

Build the project first — CodeLens scans compiled bytecode (build/classes), so an uncompiled project scans to zero classes and every query comes back empty: cd /path/to/ratpack-project && ./gradlew build -x test. Then:

codelens start --project /path/to/ratpack-project

The first start may take a few minutes while CodeLens resolves the Gradle classpath and scans bytecode; codelens status reports LOADING → READY. Sanity-check with codelens classes stats --json — a projectClassCount of 0 (also flagged by a warning: line from start) means rebuild, then codelens refresh. In a terminal, commands print human-readable tables; pass --json to get the stable machine-readable payload (it is also auto-selected when output is piped or captured, as in tool execution). The recipes below pass --json explicitly so they parse reliably with jq.

Read these reference docs for the Ratpack knowledge the recipes rely on — they hold the FQNs, handler shapes, anti-pattern catalog, and the complexity factors you weigh by hand:

  • references/RATPACK-CONCEPTS.md — handler types, the Promise/exec API, Chain routing, Guice.
  • references/ANTIPATTERNS.md — what complicates a migration, how to spot it, how to fix it.
  • references/COMPLEXITY-FACTORS.md — factors to weigh when judging effort and ordering.

Handlers

Ratpack request handlers implement known interfaces. Find them with implementations:

codelens classes implementations ratpack.handling.Handler
codelens classes implementations ratpack.func.Action          # Action<Chain> route configurers
codelens classes implementations ratpack.groovy.handling.GroovyHandler

Inline/lambda handlers compile to invokedynamic and have no class of their own — find them inside the chain that registers them (see Routes). Their bodies live in synthetic lambda$… methods of the enclosing class, which calls resolves via implMethodName, so you can read a lambda handler's body too. To classify a handler (see RATPACK-CONCEPTS.md for the taxonomy) and judge its complexity, read its body:

codelens source show com.example.UserHandler                  # full source if available
codelens calls com.example.UserHandler --method handle        # what it invokes, from bytecode
codelens calls com.example.ApiChain --method 'lambda$execute$0'  # an inline lambda handler's body

calls is the key signal: it returns every invocation handle makes with constant arguments and line numbers, so you see real Blocking.get / Promise / repository calls rather than guessing from names.

Routes

An Action<Chain> builds the route table in its execute(Chain) method. calls extracts those route registrations directly from bytecode:

codelens calls com.example.ApiChain --method execute --json

In the result, each route is a call whose ownerType is ratpack.handling.Chain and whose methodName is a routing method (get, post, put, patch, delete, options, head, all, prefix, path). The path is the STRING entry in that call's constantArgs; a class-literal (CLASS) constant arg, when present, is the handler. For example:

# Path + method for every route the chain registers:
codelens calls com.example.ApiChain --method execute --json \
  | jq -r '.methods[].calls[]
           | select(.ownerType=="ratpack.handling.Chain")
           | "\(.methodName | ascii_upcase) " +
             ((.constantArgs[]? | select(.kind=="STRING") | .value) // "(no literal path)")'

prefix(path, …) nests a sub-chain. If the second arg is a class literal (prefix("admin", AdminChain.class)), follow the CLASS constant arg by running calls on that nested Action<Chain> and prepend the prefix path. If it's an inline lambda (prefix("admin", c -> { … })), the sub-chain lives in a synthetic lambda$… method — resolve it like any inline handler (below) and recurse into it, since it registers its own routes (which may themselves use inline lambdas). To preview the target shape, map each METHOD path to the destination framework's annotation by hand (e.g. GET /users/:id → Spring @GetMapping("/users/{id}")).

Inline lambda handlers (chain.post(ctx -> …)) resolve too. The lambda is an "invokeDynamic": true call site, and the reliable pairing signal is position: the indy site sits immediately before the route call it feeds and shares its lineNumber. Its implMethodName (e.g. lambda$execute$0) names the handler body, which is its own method — read it with another calls. So walk the call list in program order: a route call (ownerType == "ratpack.handling.Chain") whose handler was inline is preceded by an indy site on the same line; pair them, then read the body:

# Pair adjacent indy→route entries WITHIN one method (the indy handler and its Chain route):
codelens calls com.example.ApiChain --method execute --json \
  | jq -r '.methods[].calls as $c | range(0; ($c|length)-1) as $i
           | select($c[$i].invokeDynamic and $c[$i+1].ownerType=="ratpack.handling.Chain")
           | "\($c[$i+1].methodName | ascii_upcase)\t\($c[$i].implMethodName)"'

# Read an inline handler's body (or a prefix's sub-chain), then recurse if it registers routes:
codelens calls com.example.ApiChain --method 'lambda$execute$0'

Treat that jq as a per-method starting point, not the whole answer: it pairs sites within a single method, so run it again on each lambda$… sub-chain to cover nested routes, and confirm pairings against the raw calls output (the indy and its route share a lineNumber).

Known limit: computed (non-literal) paths show no string constant. (Lambda and method-reference handlers are resolved — via the invokeDynamic call site's implMethodName, as above.)

Promise / async usage

Promise-heavy code is usually the hardest to migrate. Three complementary lenses:

# Methods whose signature returns a Promise:
codelens methods search --return-type ratpack.exec.Promise

# Every project class that touches the blocking/exec API (who, and where):
codelens xref ratpack.exec.Blocking
codelens xref ratpack.exec.Execution
codelens xref ratpack.exec.Promise

# Which domain types flow through promises? xref spans type arguments, so a type
# returned as Promise<Order> (or held as Promise<List<Order>>) is found here too:
codelens xref com.example.model.Order --kind RETURN

# Exactly what a class's method does with promises (operators, Blocking.get, fork):
codelens calls com.example.UserHandler --method handle --json \
  | jq '.methods[].calls[] | select(.ownerType | test("ratpack.exec"))'

# Or scope across ALL Promise-returning methods of a class at once (one query,
# no manual methods-search × calls intersection):
codelens calls com.example.OrderService --in-methods-returning ratpack.exec.Promise --json \
  | jq '.methods[].calls[] | select(.ownerType | test("ratpack.exec"))'

xref of ratpack.exec.Promise finds every method that returns or takes a Promise<…>; because references span type arguments, xref of a domain type also surfaces it where it only ever appears wrapped in a Promise<…>. xref of ratpack.exec.Blocking returns each reference as a CALL_RECEIVER with the method (get/on) and line number — the real blocking call sites. Judge intensity by counting these and reading the chains in source. See RATPACK-CONCEPTS.md for the operator/blocking taxonomy.

calls --in-methods-returning ratpack.exec.Promise scopes call-sites to a class's Promise-returning methods in one pass (and --in-methods-annotated <fqn> by annotation) — but only direct call-sites, by the enclosing method's declared signature. It does not reach the synthetic lambda$execute$N handler bodies where Ratpack hides most routing/Promise logic (read those with --method 'lambda$…', as above) nor transitive callees.

Guice modules and bindings

# Modules:
codelens classes implementations com.google.inject.AbstractModule
codelens classes implementations com.google.inject.Module

# Provider methods and their constant-arg construction:
codelens methods search --annotation com.google.inject.Provides
codelens calls com.example.AppModule --method configure         # bind(...).to(...) calls

# Constructor / field / method injection sites in one call (#43). @Inject usually sits on the
# constructor (target=CONSTRUCTOR, <init> + a derived param-type signature); --scope all also
# catches field/method injection. Pair with @Singleton-scoped @Provides methods (--scope method).
codelens annotations usages javax.inject.Inject --scope all
codelens annotations usages com.google.inject.Singleton --scope method   # @Provides @Singleton factories

# Where a given type is bound / who depends on it:
codelens xref com.example.UserRepository

calls … --method configure surfaces the bind/to/toInstance invocations (with any class-literal/string constants); xref <type> shows everywhere a bound type is used.

External integrations

There is no integration detector — xref a library type and classify it yourself using the catalog in ANTIPATTERNS.md / RATPACK-CONCEPTS.md. Examples:

codelens xref ratpack.http.client.HttpClient        # Ratpack HTTP client
codelens xref software.amazon.awssdk.services.dynamodb.DynamoDbClient
codelens xref org.apache.kafka.clients.producer.KafkaProducer
codelens xref javax.sql.DataSource                  # JDBC

Each result lists the project classes (and members/line numbers) that reference the type — your integration inventory, grouped by countsByKind and countsByPackage.

Anti-patterns (migration risks)

ANTIPATTERNS.md is the catalog (what each is, how to confirm, how to fix). Surface candidates with xref / calls, then read context with source and judge:

# Blocking I/O that may sit on the compute thread:
codelens xref java.sql.Connection
codelens xref java.net.HttpURLConnection
codelens xref java.io.FileInputStream

# Thread.sleep and console logging in a class body:
codelens calls com.example.SlowHandler --json \
  | jq '.methods[].calls[]
        | select((.ownerType=="java.lang.Thread" and .methodName=="sleep")
              or (.ownerType=="java.io.PrintStream" and (.methodName=="println" or .methodName=="print")))'

Whether a java.sql.* call is an anti-pattern depends on whether it runs inside Blocking.get — confirm by reading the method body (source) or its calls ordering. The tool finds candidates; you make the call.

Complexity and migration order

Derive the inputs from the recipes above plus the dependency graph; then rank and explain using COMPLEXITY-FACTORS.md as guidance (these are factors to weigh, not a formula):

# Foundation classes — most depended-on; usually migrate first:
codelens deps foundation

# Full project dependency graph (json or dot for visualization):
codelens deps --format dot -o deps.dot

# Per-class blast radius:
codelens classes dependencies com.example.UserService --json | jq '.incoming | length'

A reasonable ordering: migrate high-in-degree foundation classes first, then handlers in increasing complexity (size from source, async intensity from the Promise lenses, integration count from xref, anti-pattern count). Show the factors behind each ranking rather than a single opaque score.

Suggested workflow

  1. Inventory — classes stats; handlers via implementations; routes via calls.
  2. Async & risk — Promise lenses (methods search / xref / calls); anti-pattern xref/calls + source.
  3. Structure — deps foundation and deps for the graph; Guice modules.
  4. Plan — rank by the factors in COMPLEXITY-FACTORS.md, foundation-first, and justify the order from the collected facts.

Related skills

  • codelens-jvm-analysis — the general primitives this skill builds on (calls, xref, deps, …).
  • codelens-source-lookup — read handler/service bodies and library/JDK source.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.