Chapter 15 Companion: Code, Explanations and Solutions
AI as a Software Engineering Assistant
OO Development and Agile Practices
1. Scope and execution
This file accompanies Chapter 15, AI as a Software Engineering Assistant. It contains the lab, a small stand-alone Maven project for the chapter’s worked example; the two constructed AI attempts and the work orders that produced them; the AI assistance record, its template and a script that checks it; three further constructed outputs for the chapter’s other activities; the model solutions; the experiments behind the chapter’s figures; and teaching notes.
The lending project is not touched. The lab does not depend on it, and its handover version 0.14.0 stays as Chapter 14 left it. The lab holds its own copy of LoanPeriod. Chapter 15 therefore has no starting version and no carry-forward to a later chapter. Because the code is not part of the lending project, the dependency rules, the handover check and the requirements table of Chapter 14 are unaffected.
All AI output in this chapter is constructed. Every attempt, chat response and explanation is author-written and labelled, in its own file, as constructed to illustrate. None is a recording of a real model. No live assistant was used to produce or check anything here.
1.1 Reconstruct the lab
- Create the directory
lab. - Save every listing in Sections 3 to 5 of this file at its path under
lab.
Use a full Java 17 JDK and Maven 3.9.11. The pinned versions are those of the earlier chapters (JUnit Jupiter 5.13.4, Compiler Plugin 3.14.1, Surefire 3.5.4). Initial builds need dependency access. Then, from inside lab:
1.2 Expected output
mvn --batch-mode test ends with Tests run: 9, Failures: 0, Errors: 0, Skipped: 0 and BUILD SUCCESS. The attempts are run in a scratch copy of the lab, which is deleted afterwards:
| Command | Expected |
|---|---|
try-attempt.sh 1 | BUILD FAILURE with two cannot find symbol errors, at AvailabilityFinder.java line 18 and line 20 |
try-attempt.sh 2 generated | Tests run: 7, Failures: 0, BUILD SUCCESS |
try-attempt.sh 2 (acceptance) | BUILD FAILURE: reference to AvailabilityFinder is ambiguous in AvailabilityFinderTest |
try-attempt.sh 2 adapted | Tests run: 9, Failures: 2: ex4_... and ex5_... |
check-record.sh | checked docs/AI-RECORD-earliest-start.md and RECORD CHECK PASSED |
check-record.sh where only the template exists | FAIL no assistance record to check..., RECORD CHECK FAILED: nothing was checked, exit status 1 |
(Maven and the build print WARNING lines on a recent JDK. They are not errors.)
1.3 Compile attempt 1 by hand
2. Design map
2.1 What is in the lab
| Path | Purpose |
|---|---|
pom.xml | Maven build, Java 17 (maven.compiler.release), JUnit Jupiter 5.13.4, Surefire with failIfNoTests |
README.md | What the lab is and how to run it |
src/main/java/assist/LoanPeriod.java | A copy of the lending project’s half-open period |
src/main/java/assist/AvailabilityFinder.java | The accepted version, written by the developer |
src/test/java/assist/AvailabilityFinderTest.java | The nine acceptance examples EX1 to EX9, written before any prompt |
scripts/try-attempt.sh | Tries a constructed attempt in a scratch copy |
scripts/check-record.sh | Checks that an AI assistance record has every heading and none is empty |
prompts/prompt1.md, prompts/prompt2.md | The two work orders |
attempts/attempt1/*, attempts/attempt2/* | The constructed AI output, outside the build |
docs/AI-RECORD-TEMPLATE.md, docs/AI-RECORD-earliest-start.md | The record template and the filled record |
fixtures/* | Three further constructed outputs: an explanation, a prompt to send and not to send, a dependency suggestion |
2.2 The task, stated once
Given the periods for which an item is already booked, return the earliest start date, on or after from, among searchDays candidate start dates beginning with from itself, on which the item could be reserved for days days. Periods are half-open. Nothing is reserved. Invalid days or searchDays (below one) is an IllegalArgumentException; a null from or bookings list is a NullPointerException.
The acceptance examples, with the bookings 20–22 and 24–26 October unless stated:
| ID | Request | Result |
|---|---|---|
| EX1 | from 20, 2 days, window 10 | 22 October |
| EX2 | from 20, 3 days, window 10 | 26 October |
| EX3 | from 10, 2 days, window 10; and no bookings, from 10, 5 days, window 1 | 10 October |
| EX4 | from 20, 2 days, window 2 | none |
| EX5 | from 20, 2 days, window 3 | 22 October; and window 2 gives none |
| EX6 | booking 18–20 only; from 20, 2 days, window 1 | 20 October |
| EX7 | days 0, window 0, a null from, a null list | the exceptions above |
| EX8 | the caller’s list gains a booking after construction | the finder is unchanged |
| EX9 | one booking covering 1–31 October; from 5, 1 day, window 10 | none |
3. The lab: build and code listings
3.1 lab/pom.xml
3.2 lab/README.md
3.3 lab/src/main/java/assist/LoanPeriod.java
3.4 lab/src/main/java/assist/AvailabilityFinder.java
3.5 lab/src/test/java/assist/AvailabilityFinderTest.java
3.6 lab/scripts/try-attempt.sh
3.7 lab/scripts/check-record.sh
4. Constructed AI output: work orders, attempts and responses
Everything in this section is author-written fictional AI output, constructed to illustrate. It is not a recorded service response.
4.1 lab/prompts/prompt1.md
4.2 Attempt 1
4.2.1 lab/attempts/attempt1/AvailabilityFinder.java
4.2.2 lab/attempts/attempt1/RESPONSE.md
4.3 lab/prompts/prompt2.md
4.4 Attempt 2
4.4.1 lab/attempts/attempt2/AvailabilityFinder.java
4.4.2 lab/attempts/attempt2/AvailabilityRule.java
4.4.3 lab/attempts/attempt2/AvailabilityRules.java
4.4.4 lab/attempts/attempt2/AvailabilityFinderGeneratedTest.java
4.4.5 lab/attempts/attempt2/RESPONSE.md
5. Records and examples
5.1 lab/docs/AI-RECORD-TEMPLATE.md
5.2 lab/docs/AI-RECORD-earliest-start.md
5.3 lab/fixtures/explanation-visitor-gate.md
5.4 lab/fixtures/privacy-prompt.md
5.5 lab/fixtures/dependency-suggestion.md
6. Worked explanation and evidence
6.1 Attempt 1 under three compilers’ settings
| Command | Result |
|---|---|
javac -d out1 *.java (a JDK of version 21 or later, no --release) | One error: cannot find symbol, symbol: method intersects(LoanPeriod), location: variable b of type LoanPeriod |
javac --release 17 -d out2 *.java | Two errors: the same, and cannot find symbol, symbol: method getFirst(), location: variable free of type List<LocalDate> |
After changing only intersects to overlaps: plain javac | No error. The code compiles |
After the same change: javac --release 17 | One error: getFirst() |
Maven (maven.compiler.release 17), try-attempt.sh 1 | Two errors, at [18,42] and [20,68] |
| Either command on a JDK 17 | Both errors. The plain-javac contrast needs a JDK of version 21 or later; the project’s command fails in the same way on every JDK |
The third row is the point. After the invented method is fixed, the code compiles on the machine it was written on and fails on the baseline. List.getFirst() is declared in the Java 21 List (since 21) and not in the Java 17 List, and --release restricts the API as well as the language level. An assistant told that the project targets Java 17 may still use it, and a developer who compiles without --release will not notice until the pipeline does.
6.2 Attempt 2, step by step
| Run | Result |
|---|---|
| Attempt 2 with its own seven generated tests | 7 pass |
| Attempt 2 with the acceptance tests as written | Does not compile: reference to AvailabilityFinder is ambiguous at the line new AvailabilityFinder(null) of ex7. Attempt 2 has two constructors, one taking a List and one taking an AvailabilityRule, so a bare null fits both |
Attempt 2 with the acceptance tests after the one-line cast (try-attempt.sh 2 adapted) | 9 run, 2 fail: ex4_... (expected Optional.empty, was Optional[2026-10-22]) and ex5_... (the same) |
| The 7 generated tests against the accepted finder | 6 pass and 1 fails: theSearchWindowIsInclusiveOfTheLastDay |
What this shows:
- The failing acceptance examples are both about the window. The loop in attempt 2 is
offset <= searchDays, so it examinessearchDays + 1candidates. From the 20th with a window of 2 it examines 20, 21 and 22 and returns the 22nd. - The generated test
theSearchWindowIsInclusiveOfTheLastDayasserts exactly that result. It was derived from the code, passes on it, and fails on correct code. It is a test of what the code does. - The acceptance examples were independent because they were derived from the requirement before any candidate existed, and not from code. They were also not shared with the assistant, which let them be used a second way, to judge whether the output generalises. Sharing them would have been a legitimate choice. It would probably have produced a correct loop, and the pass would then have shown only that the assistant satisfied those examples.
- The ambiguity is a cost of the abstraction. The one-line cast in
try-attempt.sh 2 adaptedchanges the type of an argument and does not change what the test asserts. Editing a test to suit the code is the wrong direction in general: here the edit is only so that the logic failure can be seen at all. The right response to the compile error, in the developer’s own project, is to remove the unneeded overload.
Size of the abstractions. The accepted AvailabilityFinder is one type of 38 lines, six of them documentation. Attempt 2’s production code is three types in 49 lines (AvailabilityFinder 30, AvailabilityRule 6, AvailabilityRules 13) with no documentation comment. The interface has one implementation, built in one place by a factory that is called once. The task has one rule.
6.3 The explanation of VisitorGate.rejection
The code and tests referred to are in the lending project at version 0.14.0.
| # | Claim | Verdict | Evidence |
|---|---|---|---|
| 1 | The flag is checked first and the “not enabled” text is returned | Supported | The first statement of rejection is if (!enabled) return Optional.of(DISABLED) |
| 2 | Then the item is checked against CAM1 or CAM2 | Supported | ALLOWED_ITEMS = Set.of("CAM1", "CAM2") and the check that follows |
| 3 | Fewer than one day returns empty without asking anyone, because the policy will reject it | Supported | if (days < 1) return Optional.empty() and its comment; ac4_zeroOrNegativeDaysIsRejectedWithoutReadingTheRegister asserts that the register is not read; VisitorReservationRules.mayRequest requires requestedDays >= 1 |
| 4 | The clock is read once | Supported | oneRequestReadsTheClockOnce |
| 5 | The exact request is asked of the source, and the request is rejected unless the record matches and is valid at that instant | Supported | aSourceThatReturnsADifferentRequestDoesNotApprove; theWindowIncludesItsStartAndExcludesItsEnd |
| 6 | The gate caches the record for later requests | Contradicted | The class has no field that holds a record. Its fields are the source, the clock and the flag. The file source itself reads afresh on every call (theFileIsReadAfreshOnEveryCall) |
| 7 | Each rejection is written to the audit log | Contradicted | The class has no logger and no call that writes anywhere |
| 8 | If the source throws, the gate returns the “no valid sponsor approval” rejection | Contradicted | The exception is not caught: anOperationalFailureOfTheSourceIsNotCaught asserts that the same exception object reaches the caller. This is the difference between a rejection and a failure that Chapters 12 and 13 insist on |
A corrected explanation: rejection returns an empty result when the request may go on to the coordinator, and a rejection text otherwise. It checks the flag first, then the item, then returns empty for fewer than one day (the policy rejects it). Otherwise it reads the clock once, asks the source for the exact request and rejects unless the record matches and is valid at that instant. It keeps no state and writes no log. If the source throws, the exception reaches the caller; nothing is reserved.
Note which three claims were wrong. Each describes something a gate like this could plausibly do. All three would have been believed by a reader who did not check, and the third would have produced a wrong design decision about error handling.
6.4 The two prompts
| Prompt | Contains | Does not contain |
|---|---|---|
prompt1.md | The task sentence; the source of LoanPeriod; the constraints (Java 17, standard library only, no new dependencies); the output format; the scope | The acceptance examples; any statement of how the window counts |
prompt2.md | The two compiler findings; the request for a small solution and tests | The acceptance examples |
Two things about them. First, prompt1.md says “within searchDays days” and nowhere says what that counts. The assistant resolved the ambiguity one way and the developer meant the other. The record shows that the requirement was rewritten afterwards. Second, prompt2.md asks the assistant to write tests. That request is where the shared misreading gets locked in: code and tests from the same source agree with each other.
If the acceptance examples had been in the prompt, the assistant would probably have returned a correct loop. They would still have been good requirement-based acceptance checks. What would have changed is what a pass means: it would show that the assistant satisfied those examples, not that its code is right beyond them. A sound practice is to state the requirement precisely, share reviewed examples if they help, add boundary cases or properties that you derived independently, and keep some of them undisclosed when you want to judge generalisation. The one thing to avoid is deriving the expected results and the implementation from the same unexamined assumption, which is what happens when an assistant writes the code and then its tests from that code.
6.5 The privacy example
The first prompt of privacy-prompt.md should not be sent. What it discloses, and what the second prompt does instead:
| In the first prompt | Why it should not be sent | In the second prompt |
|---|---|---|
| A student’s name, student ID and email | Personal data about a real borrower | Keys replaced by B1, B2, B3 |
| A configuration file with a password (invented here) | A credential. Anything that is a secret in real life must not be pasted | No configuration; the method only |
| An internal address and path | Information about your infrastructure | <base>, no real URL |
| 200 lines of production log with other borrowers’ names | More personal data, and far more than the question needs | Three log lines, with the keys replaced |
| “What is wrong?” | No hypothesis, no limit on scope | “What are the possible causes? List them without choosing one” |
The second prompt is also better engineering: it states the symptom precisely (a timeout after 1000 ms) and asks for causes, which you will check, and not for a verdict.
6.6 The dependency suggestion
The constructed suggestion proposes two dependencies for a test helper that asserts the first element of a list. The status codes were read from Maven Central with curl -sI -o /dev/null -w "%{http_code}" on 13 October 2026. Nothing was downloaded.
| Coordinate | Request | Status |
|---|---|---|
org.mockito:mockito-core | https://repo1.maven.org/maven2/org/mockito/mockito-core/ | 200 |
org.mockito:mockito-junit-jupiter | the same pattern | 200 |
org.mockito:mockito-fluent | https://repo1.maven.org/maven2/org/mockito/mockito-fluent/ | 404 |
org.mockito:mockito-core:5.20.0 | the version’s .pom | 200 |
org.mockito:mockito-core:9.9.9 | the version’s .pom | 404 |
com.google.guava:guava | https://repo1.maven.org/maven2/com/google/guava/guava/ | 200 |
com.google.guava:guava:33.0.0-jre | the version’s .pom | 200 |
Decisions. mockito-fluent: reject. It does not exist under that group, and a plausible name is not a reason to search for a similar one. Even a real mocking library would be wrong here, because a helper that asserts the first element of a list does not need mocking. guava:33.0.0-jre: reject. It exists, and nothing in the task needs it: list.get(0) and assertEquals do the job. A status of 200 shows only that something is published under that name. It does not show that it is the project you meant, that it is safe, or that you need it.
6.7 The fault experiment (summary)
The full tables are in Section 10.2.
7. Suggested practical solution
7.1 the book’s project, steps B1–B4
B1. Reject attempt 1. A good answer runs try-attempt.sh 1 and reports two errors, then repeats by hand with javac --release 17 and reads both errors (and, with a JDK of version 21 or later, shows that plain javac reports one). It lists the defects and categories: invented method (intersects); API newer than the baseline (getFirst, Java 21); self-reported verification (“I tested it with Java 17”). It says what the claim could mean: possibly that it compiled on some JDK, perhaps without --release; it cannot mean that it was checked under the project’s settings, because the code does not compile under them.
B2. Diagnose attempt 2. A good answer reports 7 passing generated tests, then 2 failing acceptance examples (EX4, EX5) and the cause (offset <= searchDays). It names the shared misunderstanding (the window is inclusive of one extra candidate) and points at the generated test that asserts it. It says why the acceptance examples were independent (derived first, from the requirement, and not from the code; they were also kept back, so that they could judge generalisation). It reports that the generated tests fail one test against the accepted finder. It notes the overload ambiguity and the unneeded abstraction.
B3. Make the tests bite. A good answer applies three faults, for example offset < searchDays - 1, start.plusDays(days - 1) and allMatch, predicts the failing examples before running (Section 10.2 lists what happened), and finds one that survives. The survivors in the experiment are equivalent faults; a good answer says why no test of the exception type can detect them.
B4. Fill in a record. A good answer uses all eight headings, keeps rejected output, names at least one check that is independent of the assistant and at least one limit, and passes check-record.sh. A weak answer says “reviewed and verified” under Verification.
7.2 the book’s project, optional steps B5–B7
B5. Repeat with a live assistant. There is no model answer, because the output differs. A good answer keeps every attempt, compiles under --release 17, runs the acceptance examples, and names which failure modes from Section 15.6 appeared. An assistant that gets it right the first time is a valid result: record that and the checks that showed it.
B6. Verify two dependencies. See Section 6.6.
B7. Swap and find. A good answer lists the flaws it found, the flaws it missed and why. Missing a flaw of the invented-method kind (it looked like the real name) is common and instructive.
7.3 your team’s project, steps T1–T3 (and T4)
These are not model answers, because the task is your team’s. A good answer shows:
- T1: a requirement stated precisely, including how any count or window is defined; acceptance examples with inputs and expected results, on the boundaries; the constraints (language level, no new dependencies) and the scope; and a stated decision about what stays out of the prompt.
- T2: every attempt kept, the project’s own build and compiler settings used, and the acceptance examples run against each attempt. If no live assistant was used, the constructed or classmate-written candidate and the routes of Section 15.9.
- T3: a record with every heading, the independent checks named, the limits stated, the record checked by the script and reviewed by a person.
- T4: a short team note that follows the module leader’s rules and says what is shared with tools and what is not, how AI-assisted work is disclosed and recorded, and who reviews it.
A weak answer derives the examples from the candidate code, treats a pass on examples the assistant has seen as proof of behaviour beyond them, accepts a claim of testing, or keeps only the final version. A sound answer that shares reviewed examples is not weak for that reason: it should then add independently derived cases.
8. Intentionally flawed alternatives
These fragments are diagnostic alternatives, not files to compile.
8.1 “Reviewed by another assistant”
A second candidate opinion with the same weaknesses is not verification, and “all tests pass” does not say whose tests. Name the independent checks and the commands.
8.2 Accepting it because it compiled
The project compiles with --release 17. A call that exists only from Java 21 compiled here and fails there.
8.3 Tests derived from the implementation
The name states a requirement that nobody wrote. Examples come from the requirement.
8.4 A record that says nothing
It passes the heading check and tells a reader nothing. The checker looks only for structure.
8.5 A prompt that carries the requirement and the secrets
The requirement belongs in your own documents, stated once; the assistant gets the minimum. Secrets do not go in a prompt.
9. Review answers and transfer solution
9.1 Concept answers
1. “I tested it with Java 17.” It is a sentence from a system that did not run your build, and it names no command or result. Two checks that are evidence: compile and test with the project’s own command (mvn --batch-mode test, which sets release 17) and read the result; and run javac --release 17 on the file.
2. Function and tests that pass. Tests derived from the code agree with it, including where it is wrong: the generated test theSearchWindowIsInclusiveOfTheLastDay passes on attempt 2 and fails on correct code. Examples written from the requirement beforehand would show it, and a fault experiment on the tests would show that they miss the window fault.
3. --release. --source and --target set the language level and the class-file target. --release also restricts the API to the one that release defines, and cannot be combined with --source or --target. On a JDK newer than the baseline, only --release 17 makes getFirst() a compile error under Java 17. On a JDK 17 itself, every compile command rejects it, because the API is not there.
4. A dependency that exists. You have learned that something is published under that coordinate. You have not learned that it is the library you meant, that it is safe or licensed for your use, that the version suits your build, or that you need it.
9.2 Applied diagnosis answers
5. “The method caches the result.” Options: read the class for a field that holds a result (there is none); look for a test that depends on caching, and for a test that depends on its absence (theFileIsReadAfreshOnEveryCall); trace two calls by hand. Write down the claim, the evidence and the verdict: contradicted, with the line or test.
6. A patch of four claims with one failing test. Apply the claims one at a time in scratch copies and run the suite after each; the failing claim is the one whose copy fails. The patch’s summary is a claim of the same kind as the code, and cannot tell you which of its own claims was the cause.
9.3 Transfer solution
| Item | Content |
|---|---|
| Requirement and examples | The validation rule stated precisely (what counts as a missing value, which ranges, what happens to a bad record). Examples on the boundaries, with synthetic records. Written first |
| Context to share | The function signature, the record type, the constraints. Synthetic records only |
| Not to share | Real records, keys, tokens, the registry’s address, any real model or customer identifiers |
| Independent checks | The compiler under the project’s settings; the examples; a fault experiment on the tests; a trace on selected inputs |
| Dependencies | Look up any suggested library; ask whether the standard library already does it; pin the version |
| Record | Task, tool and model, context, each attempt, decision, changes, verification, limits |
| What it does not show about the model | Nothing here evaluates the model: its accuracy, fairness, drift or behaviour on data unlike the synthetic data. Validating the data a model receives is not validating the model |
| AI in the product | The model itself is a component with its own failure modes, contracts and tests. It is a different topic from using an assistant to write the code. In this book it is the borrower directory: Chapter 6 (contract), 7 (doubles), 8 (adapter), 13 (timeout and failure policy) |
9.4 Quiz and revision pointers
None. This chapter adds no assessed content and creates no rubric. Whether and how AI tools may be used in assessments is the module leader’s decision.
10. Instructor notes
10.1 Suggested demonstration and review
Compile attempt 1 with javac --release 17, and, if a JDK of version 21 or later is available, with plain javac, and ask why the results differ. With only a JDK 17 the two commands agree, which is itself worth discussing. Run attempt 2 against its own tests, then against the acceptance examples, and ask the class to find the shared misunderstanding before showing the loop. Ask which of the three wrong claims in the VisitorGate explanation they would have believed without checking, and why.
Do not let the review turn into a debate about whether students should use assistants. The rules are the module leader’s. The skill is the same either way: to check a candidate against evidence it did not write.
Where students have no access to a live service, every activity works from the constructed output. Where they do, ask for the rejected attempts as well as the accepted one: the record of a rejection is the evidence that someone checked.
10.2 Do the tests detect faults?
Ten single faults were applied one at a time to the accepted AvailabilityFinder in a scratch copy and run with mvn test, once against the nine acceptance examples and once against the seven generated tests (of which the one that fails on correct code is excluded from the comparison, since it already fails).
| Fault | Acceptance examples | Generated tests |
|---|---|---|
The window loop runs one step too far (offset <= searchDays), which is attempt 2’s own fault | Caught: EX4, EX5 | Survived |
| The window loop stops one step early | Caught: EX3, EX5, EX6 | Survived |
A stay of days occupies days - 1 days | Caught: EX2, EX9 | Caught: returnsEmptyWhenTheWindowIsFullyBooked, skipsALongStayThatDoesNotFit |
The search skips from itself | Caught: EX3, EX4, EX5, EX6 | Caught: returnsFromWhenNothingIsBooked |
noneMatch becomes allMatch | Caught: EX1, EX2, EX3, EX5, EX6, EX8, EX9 | Caught: three tests |
| Overlap treats an end date as occupied (inclusive end) | Caught: EX1, EX2, EX5, EX6, EX8 | Caught: findsTheFirstFreeDayAfterABooking, skipsALongStayThatDoesNotFit |
days = 0 is accepted | Survived (equivalent) | Survived |
searchDays = 0 is accepted | Caught: EX7 | Survived |
| The caller’s list is kept, not copied | Caught: EX8 | Survived |
A null from is not rejected early | Survived (equivalent) | Survived |
Totals: the acceptance examples caught 8 of 10; the generated tests caught 4 of 10. The two faults that survive the acceptance examples are equivalent to the correct code as far as the examples can tell. With days = 0 the finder’s own check is gone, but LoanPeriod’s constructor throws IllegalArgumentException on a period that does not begin before it ends. With a null from the early check is gone, but from.plusDays throws NullPointerException. Each example asserts only the exception type. The faults change where the exception is raised and not what a caller sees, which is the same kind of surviving equivalent mutant as in Chapters 7 and 11.
Notice the first row. The fault that attempt 2 actually contained, a window one step too long, is the one the generated tests cannot catch, because they were derived from code that contains it. The experiment is ten faults chosen by the author on one small class. It says nothing about assistants or tests in general.
The results establish the selected behaviours on the supplied inputs. They do not establish how a real assistant would behave on this task, or that the acceptance examples are the right examples.
References
The online references of the student chapter (sources 1–8) were opened on 13 October 2026; see the chapter for the source context.
- GitHub Docs, Application card: GitHub Copilot inline suggestions.
- Oracle,
List(Java SE 21). - Oracle,
List(Java SE 17). - Oracle, javac (Java SE 17).
- Apache Maven, maven-compiler-plugin: compile mojo.
- OWASP, Top 10 for LLM Applications.
- Spracklen, J. and others, We Have a Package for You!, arXiv 2406.10279.
- Maven Central,
org/mockito/.