Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .claude/SCHEMA_DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,26 @@ be the same observation. Before this, `:JApplication` carried only `name`/`schem
`analyzer_name`/`analyzer_version`, and the 2,604 projected `:JEntrypoint` marks came with no record of
how the pass that found them behaved.

### D32 — View templates are artifacts with a role; `J_DISPATCHES_TO` reaches them from the dispatching body node
Spec `codellm-devkit/.github` `docs/design/specs/2026-09-11-java-view-templates-and-dispatch.md` (#259).
`ArtifactDiscovery` classifies the JSP family (`format: jsp`), Facelets (`xhtml`) and Thymeleaf `.html` under
`templates/` or `WEB-INF/` (`html`) as `roles: ["view-template"]`; a bare `.html` anywhere else stays `unknown`
because a static page and a template are not distinguishable by name. `faces-config.xml` is `tool-config`.
`return` body nodes carry the returned expression in `argument_expr` (empty for a bare `return`) — a population
change to an existing field, and the one fact that lets the literal tier see `return "home"`. The dispatch pass
mirrors `ConfigUses`: detection by declared receiver type (`RequestDispatcher.forward/include`,
`HttpServletResponse.sendRedirect`, `ModelAndView` construction / `setViewName`, and a Spring entrypoint's
String `return`), the literal tier at every level, `DataflowTiers` (hoisted out of `ConfigUses`) widening at
`-a 3` / `-a 4`, and a target that closes on exactly one artifact or is recorded unresolved as `non-literal` /
`no-such-artifact` / `ambiguous`. View names expand through `spring.mvc.view.*` when declared and
`spring.thymeleaf.*` or Thymeleaf's defaults otherwise; `redirect:` / `forward:` prefixes re-dispatch as paths.
Two consequences stated rather than left implicit: `return` nodes exist only from L3, so a controller's
return-based view name is invisible at `-a 1` (its `ModelAndView` sites are not); and the servlet / Spring
types must resolve for the pass to see anything, the same condition config reads live with. `J_DISPATCHES_TO`
is one edge type with `via` (`forward | include | redirect | view-name | navigation`) rather than one per
mechanism; `navigation` is reserved for JSF and emits nothing until a JSF finder exists. Unresolved dispatches
have no target node and stay JSON-only (`view_dispatches_unresolved`).

### Graph contract version, on both of the above
`V2SchemaCatalog.SCHEMA_VERSION` does **not** move. Both additions are additive over labels the held
`2.0.0` baseline already reserves, and a re-baseline is a coordinated cross-analyzer decision
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,16 @@ not assume it is always a body node. A read that matched no declared key is kept
that resolves. The literal tier runs at every level; `-a 3` and `-a 4` widen it over the dataflow
graph (`prov: ["dataflow"]`).

**View dispatches.** JSP, Facelets and Thymeleaf templates are `:Artifact` nodes with
`roles: ["view-template"]`, and `J_DISPATCHES_TO` says which code reaches one: its source is the
`:JBodyNode` that hands the request over — a `RequestDispatcher.forward` / `include` or
`HttpServletResponse.sendRedirect` call, a `ModelAndView` construction or `setViewName`, or a Spring
controller's `return "home"` — and `via` names the mechanism (`forward`, `include`, `redirect`,
`view-name`). View names expand through `spring.mvc.view.*` / `spring.thymeleaf.*` when declared and
Thymeleaf's defaults otherwise. A target that is a variable, a servlet URL, or a name matching two
templates is kept in `analysis.json` as `view_dispatches_unresolved` with its reason rather than
guessed; like config reads, `-a 3` and `-a 4` widen the literal tier over the dataflow graph.

**Entrypoint coverage.** `:JApplication` carries `entrypoint_frameworks` and
`entrypoint_report_json`, and every entrypoint node carries `entrypoint_frameworks` naming the
framework finders that recognised it. The report is present **even when empty**: the detection pass
Expand Down
13 changes: 13 additions & 0 deletions schema.neo4j.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,19 @@
"prov": "string[]",
"_k": "string"
}
},
{
"type": "J_DISPATCHES_TO",
"from": [
"JBodyNode"
],
"to": [
"Artifact"
],
"properties": {
"via": "string",
"prov": "string[]"
}
}
],
"constraints": [
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/ibm/cldk/CodeAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.ibm.cldk.artifacts.ConfigKeys;
import com.ibm.cldk.artifacts.ConfigUses;
import com.ibm.cldk.artifacts.DependencyView;
import com.ibm.cldk.artifacts.ViewDispatches;
import com.ibm.cldk.entities.JavaCompilationUnit;
import com.ibm.cldk.javaee.EntrypointScan;
import com.ibm.cldk.neo4j.BoltConfig;
Expand Down Expand Up @@ -637,6 +638,17 @@ private void analyzeV2() throws Exception {
analysis.getApplication().setConfigReadsUnresolved(configReads.unresolved);
}

// View dispatches (#259) join dispatch sites to the artifact layer's view templates -- the
// same shape as config reads, at every level, with the dataflow tiers widening from -a 3.
ViewDispatches.Result views = ViewDispatches.detect(application, modules, artifacts,
analysisLevel, analysis.getApplication().getCallGraph());
if (!views.dispatches.isEmpty()) {
analysis.getApplication().setViewDispatches(views.dispatches);
}
if (!views.unresolved.isEmpty()) {
analysis.getApplication().setViewDispatchesUnresolved(views.unresolved);
}

if ("neo4j".equalsIgnoreCase(emit)) {
// The RESOLVED name, not the raw --app-name: the payload and the root's can:// id are
// both built from `application`, so handing the emitter a blank/raw `appName` here keys
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/ibm/cldk/artifacts/ArtifactDiscovery.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,20 @@ private ArtifactDiscovery() {}
new Rule("bootstrap*.yml", "yaml", List.of("tool-config")),
new Rule("logback*.xml", "xml", List.of("tool-config")),
new Rule("web.xml", "xml", List.of("tool-config")),
new Rule("faces-config.xml", "xml", List.of("tool-config")),
new Rule("persistence.xml", "xml", List.of("tool-config")),
new Rule("beans.xml", "xml", List.of("tool-config")),
// View templates (#259, spec 2026-09-11 D1). A bare *.html anywhere else stays `unknown`:
// a static page and a Thymeleaf template are not distinguishable by name, and `*`
// crosses '/' here, so `*/templates/*.html` reaches a nested templates/admin/x.html.
new Rule("*.jsp", "jsp", List.of("view-template")),
new Rule("*.jspx", "jsp", List.of("view-template")),
new Rule("*.jspf", "jsp", List.of("view-template")),
new Rule("*.tag", "jsp", List.of("view-template")),
new Rule("*.tagx", "jsp", List.of("view-template")),
new Rule("*.xhtml", "xhtml", List.of("view-template")),
new Rule("*/templates/*.html", "html", List.of("view-template")),
new Rule("*/WEB-INF/*.html", "html", List.of("view-template")),
new Rule("*.tf", "text", List.of("iac")),
new Rule(".github/workflows/*.yml", "yaml", List.of("ci")),
new Rule(".github/workflows/*.yaml", "yaml", List.of("ci")),
Expand Down
Loading