A worked session: ask, DSL, build, run, call
1. Ask
In an empty folder, start Claude Code with the kit installed and ask for an application "with ElastiCORE":
Build a REST API for a library with ElastiCORE: books with a 13-digit ISBN, members with a unique e-mail, loans the server dates, a 409 when no copy of a book is left, a return operation. Build it, start it on port 18080, check it, stop it.
In the runs the kit measured, the agent then read the skill's reference and the recipe that fits, wrote the YAML, generated and built with the Gradle wrapper, started the jar in the background, sent its requests - the 400s, 404s and 409s among them - stopped the server by its process id and reported, naming what it had not checked. Normally the kit sets a project up from the newest ElastiCORE release on Maven Central, with the template its CI builds every week; the measured runs used a pre-release build, 3.0.261021, from the local Maven repository.
2. The project and the DSL
From new-project.md: settings.gradle (Maven Central for plugins), the Gradle wrapper
(gradle --no-daemon wrapper --gradle-version 8.14.3, after settings.gradle, before
build.gradle), build.gradle with architecture = 'hexagonal', an application class. Then the
library recipe's model:
entity:
Book:
meta: entity @root @editable @expose(20) @sort(title+) -- book
fields:
isbn: string(13)! @id @pattern('^[0-9]{13}$', 'ISBN must be 13 digits') -- ISBN, 13 digits
title: string(200)! @notblank @search(like) -- title
author: string(100)! @notblank @search(like) -- author
pubYear: int @min(1450) @max(2100) -- publication year
copies: int! @min(0) -- copies owned
Member:
meta: entity @root @editable @expose(20) @sort(memberId+) @Index(name="uk_member_email", columnList="email", unique=true) -- member
fields:
memberId: long @id @sequence
name: string(50)! @notblank -- name
email: string(100)! @email -- e-mail address
joinedAt: date @readonly @default(now) -- joined on, set by the server
Loan:
meta: entity @root @editable @expose(20) @sort(loanId+) -- loan
fields:
loanId: long @id @sequence
member: Member! -- the borrower
book: Book! -- the book lent out
loanDate: date @readonly @default(now) -- lent on, set by the server
dueDate: date @readonly -- due back, loan date + 14
returnedDate: date @readonly @search(isnull) -- returned on, empty until returned
The rule that reads other rows (no copy left - ConflictException, 409), the due date in
beforeSave, and the return operation are a few methods in LoanService plus one small controller.
3. Build
./gradlew.bat compileJava # on Windows, from Git Bash too; ./gradlew elsewhere
The hooks tell the agent after the YAML edit to generate and compile, and do not let it finish until
a successful build has done both. The agent reads the whole output: the summary
(=== elcore3: ... written / ... unchanged / ... skipped / 0 failed ===), no line under
[elcore3] DSL validation warnings:, no * LOAD ERROR:.
4. Run - in the background, by process id
./gradlew.bat bootJar
JAR=$(ls build/libs/*.jar | grep -v -- -plain.jar | head -1)
mkdir -p build/tmp
java -Djava.io.tmpdir="$PWD/build/tmp" -jar "$JAR" --server.port=18080 < /dev/null > build/app.log 2>&1 &
echo $! > build/app.pid
for i in $(seq 1 90); do
kill -0 "$(cat build/app.pid)" 2>/dev/null || { echo "the server stopped:"; tail -30 build/app.log; break; }
curl -s -o /dev/null localhost:18080/v3/api-docs && break; sleep 1
done
Never ./gradlew bootRun from a tool's shell (it does not return); start the server in one tool
call and call it in the next ones; keep scratch files under build/.
5. Call - the refusals included
From the library recipe's table of requests and answers:
| Operation | Request | Answer |
|---|---|---|
| An ISBN of 12 or 14 digits, or with a letter | POST /api/library/book {"isbn":"978000000000",...} | 400, fieldErrors: isbn, "ISBN must be 13 digits" |
| An ISBN that exists | the same book again | 409, code already-exists |
| Join | POST /api/library/member {"name":"Kim","email":"kim@example.com"} | 200 with memberId and joinedAt today; a joinedAt in the request is ignored |
| Lend | POST /api/library/loan {"memberId":1,"bookIsbn":"9780000000001"} | 200 with loanDate today, dueDate today + 14 |
Lend without memberId | {"bookIsbn":"9780000000001"} | 400, fieldErrors: memberId |
| Unknown member | {"memberId":999999,"bookIsbn":"9780000000001"} | 404, "Member 999999 does not exist" (generated) |
| No copy left | a second loan of a book with 1 copy | 409, code conflict, "No copies of book 9780000000001 left: 1 of 1 are on loan" |
| Return | POST /api/library/loan/1/return | 200, returnedDate today |
| Return again | the same request | 409, "Loan 1 was already returned on " and the date |
| Not yet returned | POST /api/library/loan/search {"memberId":1,"returnedDateEmpty":true} | 200, those loans |
curl -s -X POST localhost:18080/api/library/loan/1/return
curl -s -X POST localhost:18080/api/library/loan/search -H 'Content-Type: application/json' -d '{"returnedDateEmpty":true}'
What to check, in this order: no DSL warnings and no LOAD ERROR; the server answers
/v3/api-docs; every operation the task names answers as the task says - the 400s, 404s and 409s too.
6. Stop, report
kill "$(cat build/app.pid)" && rm build/app.pid # the server you started - never by name
The report starts with what changed for the business, then what was checked and how, then what was
not checked. For an existing project, /elasticore:change-report writes it from the DSL diff.
What it took
The same library task with and without the kit, graded on 21 checks by a separate agent (details):
| Agent | Without the kit | With the kit |
|---|---|---|
| Claude Code, Sonnet | 4 min 41 s, 20.5 of 21 | 2 min 18 s, 20.5 of 21 |
Codex CLI, gpt-5.6-sol | 7 min 13 s, 21 of 21 | 3 min 45 s, 21 of 21 |
| opencode, a small local model | no final report in four attempts | 11 min 42 s, 21 of 21 |
The library recipe was written from this task after the first round, so these with-kit runs had a recipe for the task's domain.