Files
agenticCode/prompts/CLAUDE.md
Ingo Schnabel c8ab0274c3 UI tests
2026-07-27 08:55:37 +02:00

22 KiB
Raw Blame History

UPMS → PUR Reengineering

This project has exactly two jobs:

  1. Compare an existing Java implementation in pur against its Natural original in upms and report functional differences.
  2. Reengineer a given Natural program from upms into pur, following the existing Java design template for its program class.

Reengineering is not transpilation. You are not translating Natural statements into Java statements. You extract the behaviour of the Natural program and re-express it in the Java design that pur already uses for that kind of program. The Java structure wins; the Natural program only supplies the semantics.


0. Hard Rules

  • NEVER ask questions in response text. Every question goes through the AskUserQuestion tool — no exceptions. Clarifications, decisions, ambiguities: all via the tool. It offers a freetext option automatically.
  • Never assume intent. If the request is ambiguous, ask and wait.
  • Never invent a design. Before writing any new Java, you MUST locate and read an already-reengineered reference pair of the same class (batch job or web service) and derive the structure from it. If no reference exists, stop and ask.
  • Never invent a behaviour. Every statement in the difference report and every line of reengineered logic must be traceable to the Natural source (file + line) or to the Java template. If you cannot find it, say so — do not fill the gap with a plausible guess.
  • Never start Docker / deploy. If http://localhost:8787 is unreachable, ask the user to start the AgenticCode stack. Reading container state (docker ps, logs) is fine.
  • Never git commit or git push unless the user asks for it in that same turn. Prior approval does not carry over. Do not stage, group, or propose commits on your own initiative.
  • Deviations from the legacy behaviour must be marked in code, with a comment saying what the legacy did and why the Java differs. The existing codebase already does this — follow it:
    // Legacy code sets this to 1, but DB has it with 2
    multiTableEntryEntity.setNumAdditionalAttri(2);
    
    An unmarked deviation is a defect, even when the new behaviour is more correct.
  • Write code that is indistinguishable from the rest of the codebase. Comments are terse — a Natural name, a short clause, nothing more. No explanatory prose, no tutorial tone, no restating what the code plainly does, no authorship or tooling markers of any kind. Match the surrounding files' comment density, Javadoc style (@author itestra GmbH) and formatting exactly. A verbose or self-narrating comment is a defect here, the same as a wrong one.

1. The two codebases

upms pur
Language Natural (Software AG) Java 21 (Quarkus, jBeret, JAX-RS)
Role legacy original reengineering target
Ingested in AgenticCode as project upms project pur

Both are ingested into AgenticCode (http://localhost:8787), the static-analysis graph used to drive this work.

Natural source layout (upms)

  • generated_src/subprogram/*.nat — canonical for structure. Generated by the Software AG batch/service model; contains the **SAG markers and exit points.
  • src/manual/subprogram/*.nat, src/manual/copycode/*.cpy — hand-written subprograms and copycode. Copycode uses .cpy, not .nsc.
  • Files are ISO-8859 encoded. Always grep -a; plain grep silently skips them as binary.

2. Tooling: AgenticCode first

The full usage guide for AgenticCode is /artefacts/agent-api-system-prompt.md (project root) — every endpoint, its parameters, its response shape, and the semantics behind them. It is the authority; read it before using the API in anger, and consult it whenever a response does not look the way you expected. What follows here is only the short orientation for this project's two jobs, not a replacement.

Priority: MCP tools → REST API → ac CLI → grep/Explore. Use mcp__agenticcode__* first. If an MCP call fails with a session/protocol error, fall back to the REST endpoint (GET /api/projects/{project}/modules/{name}/...) — do not let one broken MCP call push you to grep. Fall back to reading source directly only when the question needs exact text, comments, or formatting (which it often does here — see §3).

Endpoints that matter for this work:

Question Call
What does this module call? callees (Java: also INJECTS/REFERENCES/EXTENDS wiring)
Who calls it? callers
Full reachable closure call-tree?depth=N
Which DB tables, read or written? db-accesses?depth=N
Actual SQL text sql-statements?depth=N
Subroutines / methods module_functions, module_context
Work files workfile_accesses
Find a constant/string search_value, search_identifier

Known API pitfalls (verified — do not re-discover these)

  • db-accesses at depth 0 is empty for orchestrators. A Natural batch program usually has no direct SQL; the access happens in an access-layer subprogram (Y****MN0) it CALLNATs. Always query with depth=3 or more and read the via field to see which module actually touches the table.
  • db-accesses?depth=N is a superset of db-accesses. Besides READS/WRITES it returns mode: "DECLARES" rows — the table a Java @Entity/repository maps to — with via naming the declaring module. This is how you get the Java side of a DB comparison: query the caller with depth, and read the entity's table off the via chain.
  • call-tree?followWiring=true is the right tool for a Java job's closure and works at full depth. A callees-only closure misses the steps/collaborators a batch job reaches through DI.
  • The graph can be stale. After code changes, refresh (POST /api/projects/{p}/refresh, or ?deep=true for a full field-level pass) before trusting results. For Java work refresh pur, for Natural work refresh upms — refreshing the wrong project wastes several minutes.
  • A deep refresh mutates the graph and must not be aborted. Interrupting it leaves the graph half-updated, and every later query silently answers from that state. If one must be stopped, say plainly that the graph is now inconsistent.

3. Finding the counterpart program

The correspondence between a Java class and its Natural original is recorded in the code, by convention. There is no link in the graph — you must read it.

Batch jobs carry both a comment and a constant:

// JX0034N0.nat
@ApplicationScoped
public class MultiTableImportJob extends AbstractPurBatchJob {
    public static final String JOB_NAME = "MultiTableImportJob";
    public static final String PROGRAM_IDENTIFIER = "JX0034N0";

Web services carry a Javadoc block per endpoint:

/**
 * ServiceEndpoint: gruppe.detail.genagree.GetDetail
 * UPMSFunction: com.uniqagroup.upms.sonstige.svc.esp.UpmsGenagreeGetDetail
 * Request: GruppeRequest, Response: GruppeResponse
 * UpmsObject: Genagree, UpmsAdapter: GetDetail
 */

To go Java → Natural: read PROGRAM_IDENTIFIER / the // XXXXXXXX.nat comment, then confirm the module exists in upms. To go Natural → Java: search_value the program name in pur. An empty result means the program has not been reengineered yet — that is the normal starting state for a reengineering task, not an error.


4. Job 1 — Functional comparison

Method

  1. Establish the pair (§3) and state it explicitly.
  2. Build the Natural ground truth via the API: call-tree for the full module closure, db-accesses?depth=N for the real tables (note the via chain), sql-statements, module_functions for the subroutine inventory, workfile_accesses for the I/O.
  3. Read the whole Natural source. The API gives you structure; the semantics live in the statements, and — critically — in the comments and commented-out code. Natural programs here carry decisive information in comments: change history (#01…#04 markers correlate to --> #04 / <-- #04 blocks in the body), business caveats, and logic that was disabled by commenting it out. A subroutine that is defined but whose PERFORM is commented out is dead — treating it as live is a common and serious analysis error.
  4. Read the whole Java source: the class, its base classes, its steps/collaborators.
  5. Map subroutine → Java method in a table, and mark each pair equivalent / divergent / missing.
  6. Compare the DB effect on both sides: Natural table (via the access layer) vs. the Java entity's @Table. These names will not match — establish the correspondence explicitly.

What counts as a functional difference

Report, with severity, at least:

  • Missing or dropped branches — e.g. an upsert (GET → UPD else ADD) reduced to insert-only.
  • Unimplemented error handling — a TODO where the Natural sets an error counter and a backout flag.
  • Changed constants or field values — even deliberate corrections.
  • Different transaction / commit granularity — Natural END TRANSACTION every #P-ET-MAX records vs. framework chunk commits.
  • Different data representation — packed strings vs. indexed occurrences.
  • Different control-flow triggers — positional vs. content-based record classification.

For every finding give: the Natural evidence (file + line), the Java evidence (file + line), the behavioural consequence, and a severity. A difference without a stated consequence is not a finding.

Explicitly list what you verified as equivalent too — a report that only lists problems does not tell the reader what was actually checked.

Output

A comparison report as a markdown file — see §6.1 for where it goes and how it is named. Contents: the pair, the structural mapping table, what was verified as equivalent, the differences ranked by severity, the DB-effect comparison, and an overall verdict. Do not propose code changes to pur unless asked.


5. Job 2 — Reengineering a Natural program into pur

Rule zero: derive the template, never invent it

Before writing anything, find an already-reengineered program of the same class and read it end to end. It defines package layout, class names, base classes, annotations, and comment conventions. Your output must be indistinguishable in shape from it.

Batch template (reference: MultiTableImportJob ← JX0034N0)

Package com.uniqagroup.pur.batch.<domain> with a steps sub-package:

Element Role Natural counterpart
<Name>Job extends AbstractPurBatchJob wires steps in jobSteps(), declares JOB_NAME, PROGRAM_IDENTIFIER, defaults main body PERFORM INIT/MAIN/END-PROCESSING
<Name>InitStep extends AbstractPurInitBatchlet debug banner, checkParamsImpl(), batch-monitoring begin INIT-PROCESSING + CHECK-PARMS
<Name>ProcessingStep extends AbstractPurBatchProcessingStep<R, E> rowMapper, doProcessItem, doWriteItems, doBeforeEndTransaction, fillResults, writeResults MAIN-PART, HEADER, LOAD-*, DEL-*, FILL-RESULTS, WRITE-RESULTS, ET-PROCESSING
<Name>EndStep extends AbstractPurBatchlet final return-code reporting END-PROCESSING
<Name>Context extends AbstractIteratorStepContext<R>, @JobScoped all LOCAL state that survives across records; counters as a CounterType enum LOCAL variables, #C-* counters
<Name>Record extends AbstractSerializableDataContainer one input line the SEPARATEd work-file fields

Structural conventions to copy:

  • Steps are wired with new StepBuilder("<name>").batchlet(refName(<Step>.class)); the init step uses step.nextOn(SKIP_TO_FINAL).to(END_PROCESSING_STEP) to model the Natural ESCAPE ROUTINE on check-only / monitoring error.
  • Start parameters map to PROPERTY_PAR1..PAR9 via a BatchParamLayout; getDefaultParameters() supplies the JCL defaults (work file name, log output, ADDPARMB, the PAR* switches).
  • Steps are @Named @Dependent @RequiredArgsConstructor with final collaborator fields (constructor injection). Do not use field injection.
  • Every Java member that corresponds to a Natural artefact carries its Natural name as a comment. This is the reengineering's audit trail and is mandatory. Just the name — no explanation:
    // DEFINE SUBROUTINE LOAD-MELEM
    private @Nullable MultiTableEntryEntity performLoad(...)
    
    // #JP-DEL
    private boolean jpDel;
    
    // #C-READ-HEADER
    READ_HEADER,
    

Web-service template (reference: the pur-rest-api UPMS controllers)

Package com.uniqagroup.pur.rest.upms.<domain>:

  • <Domain>Controller extends AbstractUPMFESvc, @ApplicationScoped, @Path(<Domain>Controller.PATH), @RequiredArgsConstructor with final <Object>Logic fields.
  • One @POST method per service endpoint, @Consumes XML, @Produces text, annotated @RunInTransaction, @DsgvoInfo, @RequiresRole.
  • The method builds a <Object><Adapter>UseCaseUpmsFE from the request body, hands it to the Logic, and renders XML_RESPONSE_TEMPLATE.formatted(useCase.buildResponseFromUseCase().toXML()).
  • The Javadoc block from §3 is mandatory on every endpoint.

Business logic belongs in <Object>Logic in the pur-logic module, never in the controller.

Mapping rules (Natural → Java design)

Natural Java
CALLNAT to a validation subprogram a *Logic collaborator method (e.g. USIX004N → ClientValidationLogic.validate)
CALLNAT to an access layer Y****MN0 *Logic → *Repository → *Entity
INCLUDE YFRAMMC0 'C-MOD-GET/ADD/UPD/DEL' the corresponding *Logic CRUD method
READ WORK 1 the step's reader (getFileBase(), rowMapper, headerLength())
DEFINE SUBROUTINE a private method on the step, named for the behaviour, commented with the Natural name
LOCAL variables surviving records fields on the @JobScoped context
#C-* counters CounterType enum constants
WRITE (#MSG) / WRITE (#OUT) JobOutputPrinter.writeErrorLine / writeResultLine
MSG-INFO.##RETURN-CODE := 'E' purBatchLogic.set*ErrorMessageAndThrow(...) / setting RETURN_CODE_E
END TRANSACTION / BACKOUT TRANSACTION framework chunk commit / rollbackAndContinue()
ON ERROR + CALLNAT 'ZINERR01' the framework's error handling in the base batchlet
commented-out Natural code nothing — do not reengineer dead code

Procedure

  1. Establish that the program is not yet reengineered (§3).
  2. Build the Natural ground truth exactly as in §4 steps 2–3, including the comment archaeology.
  3. Pick the reference pair and read it fully. State which one you chose.
  4. Produce a mapping plan: every Natural subroutine, variable group and counter → its Java destination; every CALLNAT → the *Logic that already exists in pur (search for it — do not create a new one if an equivalent exists). Get this approved before implementing.
  5. Implement one class at a time, in template order: Record → Context → InitStep → ProcessingStep → EndStep → Job (web services: DTO/use-case → Logic → Controller).
  6. Write the tests (§6) and the reengineering report (§6).
  7. Refresh pur in AgenticCode and verify the new class's callees / db-accesses match the plan.

Definition of Done

  • Every Natural subroutine is either implemented or explicitly listed as intentionally dropped, with a reason.
  • Every Java member corresponding to a Natural artefact carries the Natural name as a comment.
  • Every deviation from legacy behaviour is marked with a // Legacy code ... comment.
  • No dead Natural code was reengineered.
  • Existing *Logic classes were reused rather than duplicated.
  • Unit tests and an integration test exist and pass (§6).
  • pur was refreshed and the new wiring verified through the API.
  • The reengineering report exists (§6).

6. Deliverables

Both jobs end in a markdown report — a comparison (§4) produces a comparison report, a reengineering (§5) produces a reengineering report plus the code and its tests. A job without its report is not finished.

6.1 Reports

Ask where they go. Before writing the first report of a session, use AskUserQuestion to ask which directory the reports should be placed in. Do not guess a location and do not scatter reports across the tree — once the user has named a directory, use it for every further report without asking again.

File name: <NATURAL-PROGRAM>-<JavaClassName>.md (e.g. JX0034N0-MultiTableImportJob.md), prefixed comparison- or reengineering- according to the job.

A comparison report documents an existing implementation you did not write; a reengineering report documents one you just produced. Both carry the same sections:

  1. Pair — Natural program and Java class, with source paths.
  2. Mapping table — every Natural subroutine / variable group / counter → its Java destination.
  3. Equivalent — what was checked and found to match. A report that lists only problems does not tell the reader what was actually covered.
  4. Dropped — everything intentionally not reengineered / not present (dead code, obsolete branches), each with a reason.
  5. Deviations — every point where the Java behaves differently from the Natural, with the Natural evidence (file + line), the Java evidence (file + line), the consequence, and a severity.
  6. External dependencies — each CALLNAT / access layer → the pur *Logic it maps to.
  7. DB effect — Natural table(s) vs. Java entity/table, and how they correspond.
  8. Open points — anything unverified or deferred.

The report is a technical record for the maintainers of pur. Write it plainly and factually: no narration of the analysis or reengineering process, no summarising flourishes, no meta-commentary about how the work was performed or which tools were used.

6.2 Tests (reengineering only)

  • Unit tests for the reengineered business logic: parameter validation, record parsing/mapping, key and attribute construction, counter arithmetic, group-change and totals checks, and each error branch. These test the logic directly, without the batch framework or a database.
  • One integration test modelled on the reference pair's own test (e.g. MultiTableImportJobIntegrationTest) covering the job end to end.
  • Cover every behavioural branch the Natural program has, including the switch parameters (e.g. check-only and delete modes) — each combination that changes behaviour needs a case.
  • Test naming, structure and assertion style follow the existing tests in pur. No comments explaining what a test obviously does.

7. Working style

  • Read first, with tools. Never answer structural questions from memory.
  • Make assumptions explicit and visible.
  • Validate state before and after changes.
  • Prefer the simplest structure that matches the template — this codebase values consistency over cleverness.
  • Be willing to backtrack when the mapping turns out wrong; a wrong mapping carried forward silently is the most expensive failure mode in this project.