Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b6675e4
Add engine-intent: source-of-truth .intent artefact above EDM/BPMN/form
delchev Jun 12, 2026
9570405
Add Intent perspective + first concrete generator (EntityIntentGenera…
delchev Jun 12, 2026
63f9e6c
Fix IntentRepository context startup: explicit setRunningToAll query
delchev Jun 12, 2026
8b06556
Correct intent altitude: generate model files, not code
delchev Jun 12, 2026
f3b061e
Add BpmnIntentGenerator: processes -> gen/<process>.bpmn
delchev Jun 12, 2026
37d8a4e
Merge branch 'master' into engine-intent-scaffold
delchev Jun 12, 2026
05ffdcf
Add FormIntentGenerator + comprehensive Orders IT
delchev Jun 12, 2026
1a20648
Add ReportIntentGenerator + PermissionIntentGenerator
delchev Jun 12, 2026
623e789
Add Country reference entity + seed-data support (.csvim + .csv)
delchev Jun 12, 2026
ae6f306
Make forceProcessSynchronizers wait until a pass actually runs
delchev Jun 12, 2026
ef8aeee
engine-intent: fix the broken pipeline end-to-end and align with plat…
delchev Jun 12, 2026
9e754dd
Document the intent layer and session learnings in the root CLAUDE.md
delchev Jun 12, 2026
04d5412
engine-intent: editor-first rework - the intent is an authoring artif…
delchev Jun 12, 2026
98a97c9
editor-intent: load the editor config and ng-editor platform links
delchev Jun 13, 2026
1fb8b41
Add IntentEditorLoadsIT - browser smoke test for the Intent Editor
delchev Jun 13, 2026
23c96a8
engine-intent: generate diagram layout so the EDM and BPMN editors re…
delchev Jun 13, 2026
8ec6b09
Fix EDM editor light-theme text + drive intent Mermaid from the Blimp…
delchev Jun 13, 2026
5ae3d0c
Fix intent Mermaid contrast - drive structure from the theme foreground
delchev Jun 13, 2026
8824ae9
Fix intent Mermaid: visible edges in dark mode + no syntax error on t…
delchev Jun 14, 2026
6ccdfc6
Document the unresolved Intent Editor Mermaid theming + the stale-jar…
delchev Jun 14, 2026
8215051
Render the Intent Editor diagram with mxGraph instead of Mermaid
delchev Jun 14, 2026
5831a05
Intent Editor: Monaco YAML source pane + readable diagram layout
delchev Jun 14, 2026
b0d3cea
Merge branch 'master' into engine-intent-scaffold
delchev Jun 15, 2026
e83276c
Intent EDM: integer-only PKs, opt-in composition, codbex-style naming
delchev Jun 15, 2026
2ac7ea2
Intent EDM: emit FK relationship metadata so generated dropdowns work
delchev Jun 15, 2026
fa7b791
Intent reports: emit codbex .report shape with a materialised SQL query
delchev Jun 15, 2026
5df140a
Intent forms/reports: PascalCase form bindings + relation-aware repor…
delchev Jun 15, 2026
abc6d46
add intent to know files in preview
delchev Jun 15, 2026
6dcfc17
IntentEditorLoadsIT: clone dirigiblelabs/sample-intent-model
delchev Jun 15, 2026
a65c933
Intent triggers (model side): validate onCreate + add ProcessId field
delchev Jun 15, 2026
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
18 changes: 15 additions & 3 deletions CLAUDE.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,47 @@ public String getRegistryFolder() {
return internalRegistryPath;
}

/** How long {@link #forceProcessSynchronizers()} waits for a full pass to run after the force. */
private static final long FORCE_SYNC_TIMEOUT_MILLIS = 5 * 60 * 1000L;

/** Pause between retries while another synchronization run holds the processing slot. */
private static final long FORCE_SYNC_RETRY_MILLIS = 100L;

/**
* Force process synchronizers.
* Force process synchronizers and wait (bounded) until a full pass has actually run.
*
* <p>
* {@link #processSynchronizers()} silently skips when the runtime is not prepared yet or when
* another synchronization (e.g. the scheduled {@code SynchronizationJob}) is mid-run. Callers of
* the <i>force</i> variant - tests and the IDE publish flow - rely on the registry state being
* reconciled when this method returns, so a silent skip is a race: the caller immediately queries
* for an artefact the skipped pass never persisted. This method therefore retries until the force
* flag has been consumed by a completed pass (ours or a concurrent one) and no run is still in
* progress, up to {@link #FORCE_SYNC_TIMEOUT_MILLIS}.
*/
public void forceProcessSynchronizers() {
this.synchronizationWatcher.force();
processSynchronizers();
long deadline = System.currentTimeMillis() + FORCE_SYNC_TIMEOUT_MILLIS;
while (true) {
processSynchronizers();
if (!synchronizationWatcher.isModified() && !isSynchronizationRunning()) {
return;
}
if (System.currentTimeMillis() >= deadline) {
logger.warn("Forced synchronization did not complete within [{}] ms - modified: [{}], running: [{}]",
FORCE_SYNC_TIMEOUT_MILLIS, synchronizationWatcher.isModified(), isSynchronizationRunning());
return;
}
logger.debug("Forced synchronization is waiting for a concurrent run to finish...");
try {
Thread.sleep(FORCE_SYNC_RETRY_MILLIS);
} catch (InterruptedException e) {
Thread.currentThread()
.interrupt();
logger.warn("Forced synchronization wait interrupted", e);
return;
}
}
}

/**
Expand Down
298 changes: 298 additions & 0 deletions components/engine/engine-intent/CLAUDE.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions components/engine/engine-intent/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>About</title>
</head>
<body lang="EN-US">
<h2>About This Content</h2>

<p>June 11, 2026</p>
<h3>License</h3>

<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise
indicated below, the Content is provided to you under the terms and conditions of the
Eclipse Public License Version 2.0 (&quot;EPL&quot;). A copy of the EPL is available
at <a href="http://www.eclipse.org/legal/epl-v20.html">http://www.eclipse.org/legal/epl-v20.html</a>.
For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>

<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is
being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
apply to your use of any object code in the Content. Check the Redistributor's license that was
provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
indicated below, the terms and conditions of the EPL still apply to any source code in the Content
and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>

</body>
</html>
40 changes: 40 additions & 0 deletions components/engine/engine-intent/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<parent>
<artifactId>dirigible-components-parent</artifactId>
<groupId>org.eclipse.dirigible</groupId>
<version>14.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<name>Components - Engine - Intent</name>
<artifactId>dirigible-components-engine-intent</artifactId>
<modelVersion>4.0.0</modelVersion>

<dependencies>
<!-- Components -->
<dependency>
<groupId>org.eclipse.dirigible</groupId>
<artifactId>dirigible-components-core-base</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.dirigible</groupId>
<artifactId>dirigible-components-core-database</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.dirigible</groupId>
<artifactId>dirigible-components-core-repository</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.dirigible</groupId>
<artifactId>dirigible-components-ide-workspace</artifactId>
</dependency>
</dependencies>

<properties>
<license.header.location>../../../licensing-header.txt</license.header.location>
<parent.pom.folder>../../../</parent.pom.folder>
</properties>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (c) 2010-2026 Eclipse Dirigible contributors
*
* All rights reserved. This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.dirigible.components.intent.endpoint;

import java.nio.charset.StandardCharsets;
import java.util.Map;

import jakarta.annotation.security.RolesAllowed;

import org.eclipse.dirigible.components.base.endpoint.BaseEndpoint;
import org.eclipse.dirigible.components.ide.workspace.domain.File;
import org.eclipse.dirigible.components.ide.workspace.domain.Project;
import org.eclipse.dirigible.components.ide.workspace.domain.Workspace;
import org.eclipse.dirigible.components.ide.workspace.service.WorkspaceService;
import org.eclipse.dirigible.components.intent.generator.IntentGenerationService;
import org.eclipse.dirigible.components.intent.model.IntentModel;
import org.eclipse.dirigible.components.intent.parser.IntentParser;
import org.eclipse.dirigible.components.intent.parser.IntentValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

/**
* REST surface for the intent editor. The intent is an authoring artifact (like the {@code .edm});
* both operations work on the current user's workspace, never on the registry:
* <ul>
* <li>{@code POST /services/ide/intent/parse} - body is the raw intent YAML; returns the parsed
* {@link IntentModel} (drives the editor's live diagram), or {@code 422} with the full list of
* structural issues.</li>
* <li>{@code POST /services/ide/intent/generate} - generates every derived model file into the
* given workspace project (next to the intent file) and scrubs stale output; returns the written
* and scrubbed file names.</li>
* </ul>
*/
@RestController
@RequestMapping(BaseEndpoint.PREFIX_ENDPOINT_IDE + "intent")
@RolesAllowed({"ADMINISTRATOR", "DEVELOPER"})
public class IntentEndpoint {

private static final Logger LOGGER = LoggerFactory.getLogger(IntentEndpoint.class);

private final IntentGenerationService generationService;
private final WorkspaceService workspaceService;

public IntentEndpoint(IntentGenerationService generationService, WorkspaceService workspaceService) {
this.generationService = generationService;
this.workspaceService = workspaceService;
}

/**
* Parse and validate an intent document. The editor calls this on every (debounced) change to
* refresh the diagram and surface validation issues without saving.
*
* @param yaml the raw intent YAML
* @return the parsed model, or {@code 422} with {@code {"issues": [...]}} when validation fails
*/
@PostMapping(value = "/parse", consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> parse(@RequestBody(required = false) String yaml) {
try {
IntentModel model = IntentParser.parse(yaml);
return ResponseEntity.ok(model);
} catch (IntentValidationException e) {
return ResponseEntity.unprocessableEntity()

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note

Invoking
ResponseEntity.unprocessableEntity
should be avoided because it has been deprecated.
.body(Map.of("issues", e.getIssues()));
}
}

/**
* Generate the derived model files for the given intent file into its workspace project.
*
* @param workspace the workspace name, e.g. {@code workspace}
* @param project the project name
* @param path the intent file path within the project, e.g. {@code app.intent}
* @return the written and scrubbed file names, or {@code 422} with the validation issues
*/
@PostMapping(value = "/generate", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> generate(@RequestParam("workspace") String workspace, @RequestParam("project") String project,
@RequestParam("path") String path) {
Workspace workspaceObject = workspaceService.getWorkspace(workspace);
if (!workspaceObject.exists()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Workspace [" + workspace + "] does not exist");
}
Project projectObject = workspaceObject.getProject(project);
if (!projectObject.exists()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Project [" + project + "] does not exist");
}
File intentFile = projectObject.getFile(path);
if (!intentFile.exists()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
"Intent file [" + path + "] does not exist in project [" + project + "]");
}
String yaml = new String(intentFile.getContent(), StandardCharsets.UTF_8);
try {
IntentGenerationService.GenerationResult result =
generationService.generate(yaml, projectObject.getPath(), project, baseName(path));
return ResponseEntity.ok(
Map.of("workspace", workspace, "project", project, "written", result.written(), "scrubbed", result.scrubbed()));
} catch (IntentValidationException e) {
return ResponseEntity.unprocessableEntity()

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note

Invoking
ResponseEntity.unprocessableEntity
should be avoided because it has been deprecated.
.body(Map.of("issues", e.getIssues()));
} catch (RuntimeException e) {
LOGGER.error("Intent generation failed for [{}/{}/{}]", workspace, project, path, e);

Check warning

Code scanning / CodeQL

Log Injection Medium

This log entry depends on a
user-provided value
.
This log entry depends on a
user-provided value
.
This log entry depends on a
user-provided value
.
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Generation failed: " + e.getMessage(), e);
}
}

/**
* The file's base name without path and the {@code .intent} extension - the fallback identity when
* the YAML declares no {@code name:}.
*/
private static String baseName(String path) {
String fileName = path.substring(path.lastIndexOf('/') + 1);
int dot = fileName.lastIndexOf('.');
return dot > 0 ? fileName.substring(0, dot) : fileName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2010-2026 Eclipse Dirigible contributors
*
* All rights reserved. This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.dirigible.components.intent.generator;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import org.eclipse.dirigible.components.intent.model.IntentModel;
import org.eclipse.dirigible.repository.api.IRepository;
import org.eclipse.dirigible.repository.api.IResource;

/**
* Per-generation call context handed to every {@link IntentTargetGenerator}. Carries the parsed
* intent model, the target project paths inside the Dirigible repository, and the single write
* entry point {@link #writeModelFile(String, String)}.
*
* <p>
* Generation targets the <b>developer's workspace project</b> ({@code /users/<user>/<workspace>/
* <project>}) - the intent is an authoring artifact like the {@code .edm}: the developer edits it
* in its editor, clicks Generate, reviews the derived model files in the project, and publishes
* everything together. Model files are written directly at the project root (next to the
* {@code .intent} file) - the location every downstream consumer is proven to handle. NOT under
* {@code gen/}: the model-to-code templates wipe that folder wholesale on every regeneration.
*
* <p>
* All writes go through {@link #writeModelFile(String, String)}, which records the emitted file
* names so {@link IntentGenerationService} can scrub files that a previous generation wrote but the
* current one no longer produces.
*/
public final class IntentGenerationContext {

/** Repository path of the target project root, e.g. {@code /users/admin/workspace/my-library}. */
private final String projectRoot;

/** The target project name. */
private final String projectName;

/** Base-name fallback when the intent YAML declares no {@code name:} - the file's base name. */
private final String fallbackName;

private final IntentModel model;
private final IRepository repository;

/** Bare file names written under {@link #projectRoot} during this generation pass. */
private final Set<String> writtenFileNames = new LinkedHashSet<>();

IntentGenerationContext(IntentModel model, String projectRoot, String projectName, String fallbackName, IRepository repository) {
this.model = model;
this.projectRoot = projectRoot;
this.projectName = projectName;
this.fallbackName = fallbackName;
this.repository = repository;
}

/**
* Write (create or overwrite) a model file at the project root. This is the only write surface
* generators may use; the emitted file name is recorded for the post-pass scrub of stale output.
* Byte-identical content is not rewritten.
*
* @param fileName bare file name including extension, e.g. {@code library.edm}
* @param content the full file content
*/
public void writeModelFile(String fileName, String content) {
String path = projectRoot + "/" + fileName;
byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
IResource existing = repository.getResource(path);
if (existing.exists()) {
if (!Arrays.equals(existing.getContent(), bytes)) {
existing.setContent(bytes);
}
} else {
repository.createResource(path, bytes);
}
writtenFileNames.add(fileName);
}

/**
* The bare file names emitted through {@link #writeModelFile(String, String)} so far.
*
* @return an unmodifiable view of the written file names
*/
public Set<String> getWrittenFileNames() {
return Collections.unmodifiableSet(writtenFileNames);
}

public String getProjectName() {
return projectName;
}

/**
* Base-name fallback for single-file outputs when the YAML omits {@code name:}.
*
* @return the intent file's base name, never null
*/
public String getFallbackName() {
return fallbackName;
}

public IntentModel getModel() {
return model;
}

public String getProjectRoot() {
return projectRoot;
}

public IRepository getRepository() {
return repository;
}
}
Loading
Loading