reading time: 12 minutes

Your codebase is growing. Dependencies between modules are multiplying. Architecture documentation becomes obsolete before it’s even written. What if your Gradle build could automatically generate up-to-date diagrams from the actual code structure? That’s exactly what the Graphify + PlantUML Gradle Plugin pipeline does:`graphify . --no-viz`extracts the Knowledge Graph,`./gradlew generateKnowledgeGraphDiagram`transforms it into PlantUML diagrams. Zero LLM, zero manual, 100% deterministic.

The problem: diagrams always lag behind the code

Any project that exceeds a few thousand lines experiences this syndrome:

  1. We draw an architecture diagram at the beginning of the project

  2. The code evolves, the dependencies change

  3. The diagram becomes a decorative lie

  4. Nobody updates it because it’s a pain

  5. Newcomers rely on it and make mistakes

@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #FDEDEC
skinparam ActivityDiamondBackgroundColor #FFF3CD

start

:Architecture diagram created\n(manually, by hand);

:The code evolves\n(new classes, new dependencies);

:Obsolete diagram\n(no longer reflects the code);

if (Qui met à jour ?) then (Personne)
    :Misleading documentation;
    note right: New developers\nrely on it... and make mistakes
else (Quelqu'un)
    :Manual update\n(2-3 hours of work);
    note right: Already obsolete\nuntil the next PR
endif

stop

@enduml

The question is not should we have diagrams? — everyone knows we should. The question is:Who keeps them up to date?

The answer: nobody. Unless it’s automatic.

The solution: a deterministic Knowledge Graph → PlantUML pipeline

The principle is simple: instead of drawing the diagrams by hand, we themgenerates from the real structure of the code.

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Développeur
component "Graphify\n(pip install graphifyy)" as Graphify
collections "graphify-out/graph.json\n(Knowledge Graph)" as KGJSON
component "PlantUML Gradle Plugin\n(generateKnowledgeGraphDiagram)" as Plugin
component "KnowledgeGraphParser" as Parser
component "KnowledgeGraphRenderer" as Renderer
component "PlantumlService\n(validation + rendering PNG)" as PS
collections "diagrams/knowledge-graph/\n(.puml + .png)" as Output

Développeur --> Graphify : graphify . --no-viz
Graphify --> KGJSON : extracts the structure\nof the source code
Développeur --> Plugin : ./gradlew generateKnowledgeGraphDiagram
Plugin --> Parser : parse(graph.json)
Parser --> Plugin : KnowledgeGraph\n(nodes + edges + communities)
Plugin --> Renderer : render(graph, filters)
Renderer --> Plugin : code PlantUML\ndeterministic
Plugin --> PS : validateSyntax + generateImage
PS --> Output : .puml + .png

note bottom of Plugin
Pipeline DÉTERMINISTE
No call LLM
Reproducible result
end note

@enduml

Two commands. That’s all.

# Étape 1 : extraire le Knowledge Graph
graphify . --no-viz

# Étape 2 : générer les diagrammes PlantUML
./gradlew generateKnowledgeGraphDiagram

The result? Files`.puml` et .png`in`diagrams/knowledge-graph/, versioned in Git, always up to date with the code.

Step 1: Install Graphify and extract the Knowledge Graph

Installation

Graphify is a Python tool that analyzes your codebase and builds a structured knowledge graph:

# Méthode recommandée
uv tool install graphifyy && graphify install --platform opencode

# Alternative avec pip
pip install graphifyy && graphify install --platform opencode

Configure Exclusions

Create a file`.graphifyignore`at the root of the project to exclude files that are not part of the business logic:

# Secrets — JAMAIS dans le graphe
*-context.yml
*.env

# Fichiers générés
build/
.gradle/

# Tests fonctionnels
src/functionalTest/

Extract the Knowledge Graph

graphify . --no-viz

The flag`--no-viz`skips HTML generation (unnecessary in a Gradle pipeline). The result is a file`graphify-out/graph.json`containing:

  • Knots: classes, functions, files — with their type and community

  • edgesrelations between nodes (EXTRACTED from the code, INFERRED by the LLM)

  • Communities: automatic groupings of linked nodes

Example of structure`graph.json`
{
  "nodes": [
    {"id": "0", "label": "LlmService", "file_type": "code", "community": 0},
    {"id": "1", "label": "ApiKeyPool", "file_type": "code", "community": 0},
    {"id": "2", "label": "PlantumlService", "file_type": "code", "community": 1}
  ],
  "links": [
    {"source": "1", "target": "0", "relation": "uses", "confidence": "EXTRACTED", "weight": 0.9},
    {"source": "0", "target": "2", "relation": "calls", "confidence": "INFERRED", "weight": 0.7}
  ]
}

The source code (.kt, .java) is analyzed locally by tree-sitter,unappealable LLM. Only the documentation files (.adoc, .md) require an LLM call for semantic extraction. So`--update`on Kotlin code is almost instantaneous.

Step 2: The Gradle plugin transforms the Knowledge Graph into PlantUML

Pipeline architecture

The plugin`com.cheroliv.plantuml`integrates a task`generateKnowledgeGraphDiagram`who transforms`graph.json`in PlantUML diagrams in a mannercompletely deterministic :

@startuml
skinparam backgroundColor #FEFEFE

participant "Gradle" as G
participant "GenerateKnowledgeGraphDiagramTask" as Task
participant "KnowledgeGraphParser" as Parser
participant "KnowledgeGraphRenderer" as Renderer
participant "PlantumlService" as PS

collections "graphify-out/graph.json" as JSON

G -> Task : execute
Task -> JSON : read
Task -> Parser : parse(graph.json)
Parser --> Task : KnowledgeGraph\n(nodes + edges + communities)
Task -> Renderer : render(graph, filters)
Renderer --> Task : code PlantUML\n(deterministic)
Task -> PS : validateSyntax(plantumlCode)
alt syntaxe valide
    Task -> PS : generateImage → .png
else syntaxe invalide
    Task -> Task : save .puml with warning
end

@enduml

The internal components

Component Role

KnowledgeGraphParser

Parse`graph.json`— supports 3 formats: graphify native (nodes+links), legacy (communities), flat. Resolves numeric IDs to labels.

KnowledgeGraphRenderer

Deterministically transforms a`KnowledgeGraph`in PlantUML code. Groups by type, communities in packages, automatic legend.

GenerateKnowledgeGraphDiagramTask

Gradle task orchestrating: parse → render → validate → PNG. Configurable via Gradle properties.

kgmodels.kt

Data models:`KnowledgeGraph`, KnowledgeGraphNode, KnowledgeGraphEdge, KnowledgeGraphCommunity, EdgeType.

PlantumlService

Syntax validation + PNG rendering (reused by all tasks of the plugin).

Rendering rules

The renderer applies deterministic visual conventions:

Edge type PlantUML notation Significance

EXTRACTED

-→(solid line, black)

Extracted relation from source code (certainty)

inferred

..>(dotted line)

Relation inferred by the LLM (confidence score)

AMBIGUOUS

--x(dotted red line)

Ambiguous relation (to be verified)

Communities are rendered as PlantUML packages with an automatic color palette.

Step 3: Daily use

Complete diagram

# Générer le diagramme du Knowledge Graph complet
./gradlew generateKnowledgeGraphDiagram

Output:`diagrams/knowledge-graph/knowledge-graph-full.puml`+.png

Filter by community

# Une seule communauté
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.community=community_0

# Limiter le nombre de noeuds (lisibilité)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.community=community_0 \
  -Pplantuml.kg.maxNodes=15

Filter by edge type

# Uniquement les relations certaines (EXTRACTED)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.edgeTypes=EXTRACTED

# Relations certaines + inférées
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.edgeTypes=EXTRACTED,INFERRED

Filter by node type and confidence

# Uniquement les classes de code
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.nodeTypes=code

# Seuil de confiance minimum (pour les INFERRED)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.minConfidence=0.7

Custom output directory

./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.outputDir=docs/architecture

Complete reference of properties

Property Default Description

plantuml.kg.community

(all)

Filter communities by name (substring match)

plantuml.kg.edgeTypes

(all)

Edge types separated by commas:`EXTRACTED`, INFERRED, AMBIGUOUS

plantuml.kg.minConfidence

0.0

Minimum confidence threshold for the edges

plantuml.kg.maxNodes

(unlimited)

Maximum number of nodes to display

plantuml.kg.nodeTypes

(all)

Types of nodes separated by commas (e.g.class, code)

plantuml.kg.outputDir

diagrams/knowledge-graph

Output directory for the files`.puml` et .png

The full pipeline in a Gradle workflow

Workflow type

@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #E8F4FD
skinparam ActivityDiamondBackgroundColor #FFF3CD
skinparam ActivityBorderColor #2C3E50

start

partition "EXTRACTION" {
    :graphify . --no-viz;
    note right: Analyze the source code\ntree-sitter (local, no LLM)

    :graphify-out/graph.json;
    note right: Nodes + edges\n+ communities
}

partition "GENRATION" {
    :./gradlew generateKnowledgeGraphDiagram;
    note right: Deterministic pipeline\nparse → render → validate → PNG

    if (Filtres actifs ?) then (Oui)
        :Apply filters\n(community, edgeTypes,\nmaxNodes, nodeTypes);
    else (Non)
        :Complete diagram;
    endif

    :diagrams/knowledge-graph/\nknowledge-graph-full.puml\nknowledge-graph-full.png;
}

partition "INTEGRATION" {
    :git add diagrams/knowledge-graph/;
    :git commit;
    note right: Versioned diagrams\nalways up to date with the code
}

stop

@enduml

Integration into the development cycle

The pipeline naturally integrates into the key steps of development:

@startuml
skinparam backgroundColor #FEFEFE

state "Development" as dev
state "Extraction\ngraphify . --no-viz" as extract
state "Generation\n./gradlew generateKnowledgeGraphDiagram" as generate
state "Commit\nversioned diagrams" as commit

[*] --> dev
dev --> extract : Modified code
extract --> generate : graph.json up to date
generate --> commit : .puml + .png generated
commit --> dev : Diagrams in the repo

note right of extract
Almost instantaneous on Kotlin code
(tree-sitter, no LLM)
end note

note right of generate
Deterministic
No LLM
Reproducible result
end note

@enduml

Incremental update

When the code changes, we don’t rebuild the entire graph from scratch:

# Mise à jour incrémentale (fichiers modifiés uniquement)
graphify . --update

# Puis régénérer les diagrammes
./gradlew generateKnowledgeGraphDiagram

--update`re-extracts only the modified files (detected by SHA256 in`graphify-out/cache/). On Kotlin code, it’s nearly instantaneous because tree-sitter works locally without an LLM call.

Dogfooding: the plugin documents itself

The PlantUML plugin exists to transform prompts into diagrams. It can also transform the Knowledge Graph of its own codebase into documentation diagrams. This is dogfooding: the plugin consumes its own service.

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package "Normal pipeline\n(user → diagrams)" as normal {
    [Fichier .prompt] as prompt
    [LlmService\n+ ApiKeyPool] as llm1
    [ProcessPlantumlPromptsTask] as task1
    [Diagramme PNG] as out1

    prompt --> task1
    task1 --> llm1
    llm1 --> out1
}

package "Knowledge Graph Pipeline\n(deterministic, no LLM)" as kg {
    [graphify-out/graph.json] as kgjson
    [KnowledgeGraphParser] as parser
    [KnowledgeGraphRenderer] as renderer
    [PlantumlService] as ps
    [Diagramme PNG\n(documentation du plugin)] as out2

    kgjson --> parser
    parser --> renderer
    renderer --> ps
    ps --> out2
}

package "Pipeline Dogfooding\n(LLM → plugin documentation)" as dogfood {
    [GraphifyPromptAdapter] as gpa
    [Fichiers .prompt\nauto-générés] as auto_prompt
    [LlmService\n+ ApiKeyPool] as llm2
    [ProcessPlantumlPromptsTask] as task2
    [Diagramme PNG\n(documentation LLM)] as out3

    kgjson --> gpa
    gpa --> auto_prompt
    auto_prompt --> task2
    task2 --> llm2
    llm2 --> out3
}

note "Same LlmService, same ApiKeyPool,\nsame PlantumlService\n— ZERO duplication" as N

@enduml

Associated Gradle tasks:

Task LLM ? Description

generateKnowledgeGraphDiagram

No

Transform`graph.json`in PlantUML (deterministic)

generateDiagramDocs

Yes

Generate des? Wait, we need to preserve fragment. French "Génère des" (imperative) meaning "Generate some". The English fragment: "Generate some". Usually translation: "Generate some". Let’s output that.

# Documentation déterministe (rapide, pas de LLM)
./gradlew generateKnowledgeGraphDiagram

# Documentation LLM (plus riche, consomme des tokens)
./gradlew generateDiagramDocs

Why it’s deterministic (and why it matters)

The key point of the pipeline`generateKnowledgeGraphDiagram` : He does not call any LLM. The transformation`graph.json`→ PlantUML is a pure function.

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

rectangle "Pipeline DÉTERMINISTE\n(generateKnowledgeGraphDiagram)" as det {
    [graph.json] as json
    [KnowledgeGraphParser] as parser
    [KnowledgeGraphRenderer] as renderer
    [PlantUML code] as puml

    json --> parser : parse
    parser --> renderer : KnowledgeGraph
    renderer --> puml : render (pure function)
}

rectangle "Pipeline LLM\n(processPlantumlPrompts)" as llm {
    [.prompt] as prompt
    [LlmService] as llmSvc
    [ChatModel] as model
    [PlantUML code] as puml2

    prompt --> llmSvc
    llmSvc --> model : API call
    model --> puml2 : non-deterministic response
}

note bottom of det
Same input → same output
No network latency
No token cost
Reproducible in CI
end note

note bottom of llm
Same input → variable output
Network latency (1-5s)
Token cost
Requires a key API
end note

@enduml

The concrete advantages:

Advantage Impact

Reproducibility

Even`graph.json`→ same diagram. Exactly. Every time.

Zero cost

No LLM call = no tokens = no API invoice.

Zero latency

Parse + render takes ~100ms, not 1-5 seconds.

CI-compatible

No API key needed. No flaky test due to a variable LLM response.

versionable

Le .puml`generated is text. We can it`diff, the reviewer in the PR, to version it in Git

Diagrams of the pipeline itself

To close the loop, here is the pipeline diagram as it would be generated by the plugin:

@startuml
skinparam backgroundColor #FEFEFE

participant "Developer" as Dev
participant "Gradle" as G
participant "GenerateKnowledgeGraphDiagramTask" as Task
participant "KnowledgeGraphParser" as Parser
participant "KnowledgeGraphRenderer" as Renderer
participant "PlantumlService" as PS
collections "graphify-out/graph.json" as JSON
collections "diagrams/knowledge-graph/" as Out

Dev -> G : ./gradlew generateKnowledgeGraphDiagram
G -> Task : execute
Task -> JSON : read
Task -> Parser : parse(`graph.json`)
Parser --> Task : KnowledgeGraph

alt filtres actifs
    Task -> Task : apply filters\n(community, edgeTypes, maxNodes…)
end

Task -> Renderer : render(graph, filters)
Renderer --> Task : code PlantUML

Task -> PS : validateSyntax()
alt valide
    Task -> PS : generateImage()
    PS --> Out : .puml + .png
else invalide
    Task -> Out : .puml (with warning)
end

@enduml

Integration in project governance

In our project, this pipeline integrates into an EAGER/LAZY context management strategy for the AI agent. The Knowledge Graph replaces manual architecture documentation with a structured, queryable, and automatically updated graph.

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package "EAGER\n(always loaded)" as eager {
    [PROMPT_REPRISE.adoc\n(contexte session)] as pr
    [INDEX.adoc\n(vue d'ensemble)] as idx
    [GRAPH_REPORT.adoc\n(~50 lignes)] as gr
}

package "LAZY — Strategy\n(on demand)" as lazy_strat {
    [Méthodologies] as meth
    [Archives sessions] as sessions
}

package "LAZY — Graphify\n(targeted queries)" as lazy_graph {
    [graph.json] as gj
    [Queries\n(query/path/explain)] as queries
}

package "Pipeline PlantUML\n(auto-generated)" as puml {
    [generateKnowledgeGraphDiagram] as kgTask
    [generateDiagramDocs] as ddTask
    [Diagrammes PNG] as diagrams
}

Agent --> eager : Read at the beginning of the session
Agent ..> lazy_strat : Load if type detected
Agent ..> lazy_graph : Query if structural need
kgTask --> diagrams : Deterministic
ddTask --> diagrams : Via LLM

note right of gr
Condensed Knowledge Graph
God nodes + communities
Replaces ECOSYSTEM_OVERVIEW
(225 lines → 50 lines)
end note

@enduml

The marriage of the two systems is complementary:

  • The strategy manages the WHEN and the HOW— governance, workflow, archiving

  • Graphify manages the WHAT and the WHERE— code structure, relations, targeted queries

_ The session strategy manages theWHEN et le how(governance, workflow, thresholds), Graphify manages theWHAT et le where(code structure, relationships, targeted queries). The PlantUML pipeline manages theWITH WHAT(deterministic diagrams, versioned, always up-to-date). _

Setup: checklist in 5 minutes

# step Order

1

Install Graphify

uv tool install graphifyy

2

Configure exclusions

Create`.graphifyignore`

3

Extract the Knowledge Graph

graphify . --no-viz

4

Generate the diagrams

./gradlew generateKnowledgeGraphDiagram

5

Commit the results

git add diagrams/knowledge-graph/

# Script complet en 5 commandes
uv tool install graphifyy
cat > .graphifyignore << 'EOF'
*-context.yml
*.env
build/
.gradle/
EOF
graphify . --no-viz
./gradlew generateKnowledgeGraphDiagram
git add graphify-out/GRAPH_REPORT.adoc graphify-out/graph.json diagrams/knowledge-graph/

Traps and mitigations

Trap Description Mitigation

Graph too dense

A large project generates hundreds of unreadable nodes

Use`-Pplantuml.kg.maxNodes=30`and filter by community

Exclusions too broad

Too many files in`.graphifyignore`reduces the value of the graph

Start by only excluding credentials and build

Direction of the arrows

INFERRED edges may have an ambiguous direction

Filter by`-Pplantuml.kg.edgeTypes=EXTRACTED`for certain relationships only

Obsolete graph

The code changes but the graph is not rebuilt

Use`graphify . --update`regularly or the git hook`graphify hook install`

Updated cost

Incremental updates are almost free (local tree-sitter)

Only the docs (.adoc) consume LLM tokens

What we get in the end

@startuml
skinparam backgroundColor #FEFEFE

rectangle "BEFORE" as avant {
    card "Hand-drawn diagrams\nAlways outdated\nNobody updates them" as av1 #FDEDEC
}

rectangle "AFTER" as apres {
    card "Automatically generated diagrams\nAlways up to date with the code\nVersioned in Git\nDeterministic and reproducible" as ap1 #E8F8E8
}

avant --> apres : graphify . --no-viz\n+ ./gradlew generateKnowledgeGraphDiagram

@enduml

The concrete benefits :

Profit Detail

Always up-to-date documentation

The diagrams reflect the current code, not a manual snapshot

Zero maintenance effort

The diagrams regenerate at each build

Technical debt reduction

No longer need to maintain diagrams by hand

Visual context for the new

A new developer understands the architecture by looking at the diagrams

Potential fine-tuning

Pairs (subgraph → diagram) are examples of AI training

Automatic validation

`PlantumlService.validateSyntax()`checks each generated diagram

Rapid onboarding

5 diagrams = full view of the architecture view

_ He who has a _why can bear all the hows. __

The why : always up-to-date diagrams. The how : two commands in a Gradle pipeline.

Related articles