Neo to Cloud Foundry Migration Orchestrator
Orchestrates the complete migration of SAP BTP Neo Java applications to Cloud Foundry.
Purpose
This skill coordinates the end-to-end migration process by:
- Analyzing the Neo application to detect required transformations
- Creating a migration plan with skills in the correct order
- Dispatching each skill to its own subagent so the orchestrator never loads the bodies of the 5–14 child skills into its own context
- Validating the migration at each step using cheap filesystem and
mvnchecks - Generating the final deployment descriptor
Orchestration Algorithm
This skill follows the Orchestrator-Worker pattern with a verification gate between steps:
- Orchestrator (this skill, running in your main context): plans, dispatches, verifies, and recovers. Holds only the plan, status log, and short worker reports.
- Workers (subagents you spawn via the
Agenttool): each handles one self-contained, idempotent unit of work in a fresh context and returns a ≤30-line structured report. - Gate: after each worker returns SUCCESS, the orchestrator advances to the next step. The worker's verification output is trusted — the orchestrator does NOT re-run the same verification.
Dispatch tool
Every subagent in this skill is spawned via the Agent tool. Use subagent_type: "general-purpose" for migration steps and subagent_type: "Explore" for read-only detection fan-outs.
Agent(
subagent_type: "general-purpose", // or "Explore" for read-only
description: "<3-5 word summary>",
prompt: <the full prompt below>
)
Never emulate a subagent inline — that defeats the entire context-saving purpose.
Concurrency policy
| Phase | Mode | Cap |
|---|---|---|
| Phase 1 detection | Fan-out (parallel) | Up to 13 Explore agents in one batch (one per skill detection). All read-only — no file conflicts. |
| Phase 3 execution | Sequential | 1 at a time. Feature skills mutate overlapping files (pom.xml, web.xml, mtad.yaml precursors); parallel runs would race. Step 3.3 (mta-descriptor) is run inline by the orchestrator — see Step 3.3 for why this is the one exception to the per-skill subagent rule. |
| Phase 4 verification | Sequential | 1 subagent. |
When fanning out in Phase 1, issue all Agent calls in a single message so they run concurrently (barrier-sync pattern).
Failure & retry policy
- Max retries per step: 2. After 2 failed subagent attempts on the same step, stop and surface to the user — do not loop indefinitely.
- Recoverable failures (single-line tweak): fix in
$COPY_DIRdirectly withEdit/Write, then re-run verification inline. Don't re-spawn a subagent for a one-line fix. - Structural failures (skill needs to re-run): re-spawn with the original prompt plus
Previous attempt failed because: <reason>. Address it and retry. - Hard stop conditions: BOM version cannot be resolved from the registry;
mvn clean packagefails on a step that previously passed;mtad.yamlnot produced in Phase 3.3 after 2 attempts.
orchestrator.log format
Every dispatch outcome appends exactly one line to .migration/orchestrator.log in this canonical format:
<ISO-8601-timestamp> phase=<n> skill=<name> status=<SUCCESS|FAILED|PARTIAL> attempt=<k>
Append-only. Never edit prior lines. Both the Resume protocol below and Step 3.4's hygiene rules read and write this single format — do not introduce variants.
Resume protocol
The orchestrator's own context may be summarized mid-migration. Resume is driven by .migration/orchestrator.log and .migration/cf-migration-config.json:
1. cat .migration/orchestrator.log
2. last_success = last line whose status=SUCCESS
resume_from = step immediately after last_success in the plan
3. PARTIAL is treated as FAILED for resume purposes:
if the last line has status=PARTIAL or FAILED, the resume_from is THAT step
(re-dispatch it; child skills are idempotent so re-running is safe).
4. If no log exists → start from Phase 0.
If log exists but plan absent → re-run Phase 1 + 2 (cheap, idempotent)
to rebuild the plan, then jump to resume_from.
5. Honor the max-retry cap: count the existing FAILED+PARTIAL lines for the same
skill in the log; if already at 2, stop and surface to user instead of re-dispatching.
Each child skill is required to be idempotent (re-running on already-migrated files is a no-op or detects the migrated state). This means the worst case of a duplicate dispatch is a wasted subagent call, never a corrupted workspace.
Why subagents (motivation, kept for context)
Running each migration step inline would pull every child skill's SKILL.md (often hundreds of lines plus reference files) into the orchestrator's context. Across a full migration that's tens of thousands of tokens of skill bodies the orchestrator never needs to read — it only needs to know which skill to run next and whether the previous one succeeded.
| Phase | Inline or subagent? | Why |
|---|---|---|
| Phase 0 — copy | Inline | Just cp -r, trivial. |
| Phase 1 — analysis | Fan-out: 13 parallel Explore subagents | Each does one skill's detection in parallel; orchestrator aggregates the small structured reports inline. |
| Phase 2 — planning | Inline | The orchestrator already has the aggregated detection results; it just renders the plan and asks the user. No new file reads. |
| Phase 3 — execution (feature skills, Steps 3.1–3.2) | One general-purpose subagent per skill, sequential | The original motivation — see the dispatch pattern below. |
Phase 3 — execution (mta-descriptor, Step 3.3) |
Inline in the orchestrator's context | One-shot, last step, and mtad.yaml is THE deliverable. The orchestrator already has the cross-skill rules and feature-skill list it needs to feed the descriptor; the subagent dispatch was a translation step that introduced drift. See Step 3.3. |
| Phase 4 — verification | One general-purpose subagent | mvn clean package output, recursive grep, file listings. The subagent returns pass/fail + last 10 lines on failure. |
See the per-phase dispatch prompts in Phase 1.0, Phase 3 (Step 3.1–3.2 for subagent feature skills; Step 3.3 runs inline), and Phase 4.0.
Artifact versions — resolve, don't invent
Never write an artifact version you remember from training data. SAP BOMs (cf-tomcat-bom, sdk-modules-bom, cf-tomee-bom, …) release frequently, and a number that doesn't exist in the registry breaks the BOM import with Non-resolvable import POM, which cascades into 'dependencies.dependency.version' is missing for every dependency the BOM manages.
Resolve each SAP BOM with a one-line lookup against Maven Central — use this exact form, do not hand-pick a number:
latest_version () {
curl -fsS --max-time 10 \
"https://search.maven.org/solrsearch/select?q=g:$1+AND+a:$2&core=gav&rows=20&wt=json" \
| jq -r '.response.docs | map(select(.v | test("^[0-9]+(\\.[0-9]+)*$")))
| sort_by(-.timestamp) | .[0].v'
}
latest_version com.sap.cloud.sjb.cf cf-tomcat-bom
latest_version com.sap.cloud.sdk sdk-modules-bom
latest_version com.sap.cloud.sjb.cf cf-tomee-bom # only if migrating to TomEE
Each invocation prints exactly one line: the latest release version. The subagent must capture that line and substitute it for the RESOLVED_* placeholders the child skills (sdk-replacement, keystore-credstore, tomee-runtime) ship in their pom snippets. If a lookup returns empty or curl exits non-zero — network failure, registry change — the subagent must stop and report. Do not ask another subagent to "just pick a recent one," do not fall back to a number from training data, and do not patch a version into the descriptor yourself.
Other identifiers the skills prescribe — buildpack names (sap_java_buildpack_jakarta, not sap_java_buildpack), service names, plan names — are fixed strings, not version numbers. Use them exactly as the child skill specifies.
Full Subaccount Migration Order
When migrating a complete Neo subaccount (platform configuration + one or more applications), execute the phases in this order. Phases 1 and 3 run once per subaccount; Phase 2 runs once per application.
PHASE 0: Tooling setup — once
Install CF CLI + BTP CLI, login to both
PHASE 1: Subaccount export — once (read-only, Neo side)
subaccount-trust-migrator ← export + import in one pass (in-memory, no disk files)
subaccount-roles-export ← export now; import deferred to Phase 5
(destinations are NOT exported to file — migrated directly in Phase 3 via neo-destinations-keystores-migrator)
PHASE 2: Per-app code migration — repeat for each app directory
For each app:
jakarta-java25-migration
sdk-replacement
authentication-xsuaa ← creates xs-security.json + role-collections
[feature skills: destinations, persistence-hana, etc.]
mta-descriptor
PHASE 3: Platform import — once (CF side, before deploy)
(trust already imported in Phase 1 by subaccount-trust-migrator)
(roles-import is NOT here — deferred)
PHASE 4: Deploy all apps — once per app
mvn clean package -DskipTests
cf deploy . -f
PHASE 5: Post-deploy — once (after ALL apps deployed)
neo-destinations-keystores-migrator ← requires CF apps to exist for app-level binding
subaccount-roles-import ← NOW: live XSUAA appIds exist
assigns role-templates + users to collections
Why roles-import is last:
btp add security/rolerequires the live XSUAAappId(e.g.myapp!t1234) which is only assigned after the firstcf deploy. Role collections are created byauthentication-xsuaaviaxs-security.json+ deployment —subaccount-roles-importonly links role-templates into those collections and assigns users.
Multi-App Notes
For a subaccount with multiple applications:
- Phases 0, 1, 3, and 5 run once for the whole subaccount
- Phase 2 runs once per app directory — each app gets its own
xs-security.json,approuter/, andmtad.yaml - Phase 4 runs once per app — deploy each app separately with
cf deploy . -ffrom its directory - Phase 5 reads all apps from
.migration/neo-roles.jsonand resolves each against live CF XSUAA apps in a single pass — run it only after all apps from Phase 4 are successfully deployed - If one app fails to deploy,
subaccount-roles-importwill flag it and can be re-run after the issue is fixed
Trigger
This skill is triggered when the user requests:
- "Migrate my Neo app to CF"
- "Convert this application to Cloud Foundry"
- "Migration from Neo to Cloud Foundry"
- "Help me migrate to CF"
- Any request involving Neo to CF migration
Migration Workflow
+------------------------------------------------------------------+
| MIGRATION WORKFLOW |
+------------------------------------------------------------------+
| |
| PHASE 1: ANALYSIS |
| ----------------- |
| Scan the application to identify which skills are needed: |
| - pom.xml - Neo dependencies, Java version |
| - web.xml - Resource references, auth config |
| - Java files - Neo API imports |
| |
| PHASE 2: PLANNING |
| ---------------- |
| Create ordered migration plan based on detection results |
| Present plan to user for approval |
| |
| PHASE 3: EXECUTION |
| ----------------- |
| Apply skills in dependency order: |
| 1. Foundation skills (always required) |
| 2. Feature skills (based on detection) |
| 3. Deployment skill (always last) |
| |
| PHASE 4: VERIFICATION |
| --------------------- |
| - Compile the application |
| - Verify no Neo imports remain |
| - Validate mtad.yaml structure |
| |
+------------------------------------------------------------------+
Skill Dependency Order
Skills MUST be applied in this order:
FOUNDATION (Always Required)
|
+-> 1. jakarta-java25-migration
| Migrate to Java 25 and Jakarta EE 10
|
+-> 2. sdk-replacement
Replace Neo Java Web API with SAP Cloud SDK
|
+-> 3. dependency-compatibility (if third-party libs detected)
Resolve library compatibility for Java 25 / Jakarta / HANA Cloud
FEATURES (Based on Detection)
|
+-> 4. approuter-setup (if web-facing app detected)
| Set up SAP Application Router
|
+-> 5. authentication-xsuaa (if auth detected)
| Set up XSUAA security configuration
|
+-> 6. persistence-hana (if DataSource detected)
| Configure HANA Cloud database
|
+-> 7. destinations (if ConnectivityConfiguration detected)
| Configure Destination service
|
+-> 8. connectivity-onpremise (if on-premise proxy detected)
| Enable Cloud Connector connectivity
|
+-> 9. mail-destinations (if mail session detected)
| Configure mail via destinations
|
+-> 10. document-management-sdm (if EcmService detected)
| Migrate to Document Management Service
|
+-> 11. keystore-credstore (if KeyStoreService detected)
| Migrate to Credential Store
|
+-> 12. tomee-runtime (if EJB detected - ALTERNATIVE to Tomcat)
| Configure TomEE container
|
+-> 13. monitoring-logging (optional)
Set up Cloud Logging
DEPLOYMENT (Always Last)
|
+-> 14. mta-descriptor
Generate mtad.yaml deployment descriptor
Phase 0: Create Migration Copy
Before analyzing or modifying anything, create a sibling copy of the application directory. All migration work — by this orchestrator and every skill it invokes — is done on the copy.
APP_DIR=$(pwd)
APP_NAME=$(basename "$APP_DIR")
COPY_DIR="$(dirname "$APP_DIR")/${APP_NAME}-cf-migration"
if [ -d "$COPY_DIR" ]; then
echo "Migration copy already exists at $COPY_DIR — using it."
else
cp -r "$APP_DIR" "$COPY_DIR"
echo "Created migration copy at $COPY_DIR"
fi
cd "$COPY_DIR"
Now that we are inside the copy, create .migration/ and save the config there:
mkdir -p .migration
Save the paths to .migration/cf-migration-config.json (create or update):
{
"sourceAppDir": "<original APP_DIR>",
"migrationAppDir": "<COPY_DIR>"
}
All subsequent steps and all invoked skills must operate inside
$COPY_DIR. The original$APP_DIRis never modified. The.migration/directory is inside the copy, not the original.
Phase 1: Analysis
Step 1.0: Fan Out Detection to Parallel Explore Subagents
Phase 1 runs ~13 detection sweeps over every pom.xml, web.xml, and Java source tree in the project. The sweeps are independent and read-only — perfect for fan-out. Each sweep goes to its own Explore subagent, all dispatched in a single message so they run concurrently (barrier-sync pattern). The orchestrator then aggregates the 13 short reports inline (cheap, decision-shaped).
Step 1.0a: First, discover the project layout once (inline)
The fan-out workers need to know where pom.xml, web.xml, and Java sources live. Do this inline — it's three quick find calls, not worth a subagent:
find . -name "pom.xml" -type f -not -path "*/target/*"
find . -name "web.xml" -path "*/WEB-INF/*" -type f
find . -path "*/src/main/java" -type d
Note the layout: flat vs. multi-module, and which submodules contain Neo code. Pass this layout summary into every fan-out worker's prompt so they don't each re-discover it.
Step 1.0b: Dispatch 13 parallel Explore subagents
In a single message, issue 13 Agent tool calls (one per skill detection). All must use subagent_type: "Explore" and description of the form "detect <skill>". The shared prompt template:
You are detecting whether the <SKILL_NAME> migration skill is needed for a Neo→CF migration.
Working directory (read-only, operate ONLY here): <COPY_DIR>
Project layout (already discovered): <flat | multi-module + submodule list>
Run EXACTLY these detection commands and report what they find:
<COPY THE COMMANDS FROM Step 1.2 FOR <SKILL_NAME> HERE>
Return ONLY this single-line JSON object:
{"skill": "<SKILL_NAME>", "required": true|false, "evidence": "<file:line or 'empty'>", "extras": {<see below>}}
Decision rule: required=true if ANY command returned ANY output. required=false only
if ALL commands returned empty. Matches in `neo/` submodules count as evidence.
The "extras" field carries skill-specific evidence the orchestrator needs to evaluate
the Step 1.3 cross-skill rules. Include only the keys relevant to this skill:
- jakarta-java25-migration: {"hasApachePOI": true|false, "currentJavaVersion": "<n>"}
- authentication-xsuaa: {"hasSAML": true|false, "hasBASIC": true|false,
"neoAuthFilters": ["<filter1>", ...],
"duplicateServletMappings": true|false}
- mta-descriptor: {"buildpack": "sap_java_buildpack_jakarta"|"sap_java_buildpack"|null}
- all other skills: {} (empty object)
Hard limit: 1 line of JSON. No commentary. No file contents. No diffs.
Your final message IS the return value.
Spawn one such Agent call for each of these 13 skills (the names match Step 1.2 headers exactly):
jakarta-java25-migration, sdk-replacement, dependency-compatibility, approuter-setup, authentication-xsuaa, persistence-hana, destinations, connectivity-onpremise, mail-destinations, document-management-sdm, keystore-credstore, tomee-runtime, monitoring-logging.
monitoring-loggingis dispatched only so the orchestrator's fan-out is uniform; its Step 1.2 body is a no-op (the rule is "OPT-IN, do not auto-detect" — see the catalog entry). The subagent returns empty, and Step 1.4 leaves the row at[ ]. Skip this skill in Phase 3 unless the user explicitly asked for Cloud Logging or OTEL.
Concrete Agent tool invocation for one skill:
Agent(
subagent_type: "Explore",
description: "detect jakarta-java25-migration",
prompt: <the template above with <SKILL_NAME> = jakarta-java25-migration
and the matching Step 1.2 commands inlined>
)
Repeat for the other 12 skills in the same message.
Step 1.0c: Aggregate inline + apply cross-skill rules
After the 13 workers return, aggregate inline:
- Collect the 13 JSON lines into a single map
{skill → {required, evidence}}. - Apply the Step 1.3 cross-skill rules table against the map and the project layout.
- Render the Step 1.4 ASCII detection summary, marking
[x]forrequired=trueand[ ]otherwise.
This aggregation runs in the orchestrator's context, but only ~13 lines of JSON enter — not the raw grep output. The summary feeds Phase 2 planning directly.
Failure inside the fan-out
If an Explore agent fails (null return, non-JSON output, or worker error), re-spawn just that one with the same prompt — do not re-run the whole fan-out. Cap re-spawns at 2 per skill (consistent with the global max-retry policy). If a skill is still failing after 2 retries, mark its detection as required=true, evidence="detection failed — assume needed" and record the degradation in a separate .migration/detection-warnings.log file (plain text, one line per failed skill). Do not write detection failures to .migration/orchestrator.log — that log is reserved for Phase-3 execution outcomes and is the source of truth for the Resume protocol. Continue with the remaining skills — over-detection is safe (the corresponding migration step is idempotent on already-migrated code), under-detection is not.
Step 1.1: Locate Project Files
The project layout (pom.xml, web.xml, Java source roots) was discovered inline in Step 1.0a. Reuse those results — do not re-run the find calls here.
Step 1.2: Detection Command Catalog (worker bodies)
Who runs this: the per-skill
Exploresubagents dispatched in Step 1.0b copy the relevant block from this section into their prompt and execute it. The orchestrator does not run any of these commands itself — that's the whole point of the fan-out. This section is the catalog the workers draw from.
Each subsection below specifies the detection commands for one skill plus the rule for marking it required.
CRITICAL — Detection Rules (these are baked into the worker prompt template in Step 1.0b):
- If a detection command returns ANY output at all, the skill is REQUIRED — mark it
[x]- If ALL detection commands for a skill return empty output, the skill is not needed — mark it
[ ]- Multi-module projects: Many Neo apps have submodules (e.g.,
neo/,cf/,common/). A match in ANY submodule counts as detected. Do NOT discount matches found inneo/subdirectories — those are the Neo patterns that need migration.- Pre-existing CF code: Some projects may already have partial CF implementations alongside Neo code. This does NOT suppress detection. If the Neo-side pattern exists, the skill must run to validate and complete the migration.
Layout note: all detection commands below use
find- or project-root-relative (./) scans to handle both flat and multi-module layouts. The layout itself was already discovered in Step 1.0a — workers receive it as part of their prompt and do not re-discover.
Check: jakarta-java25-migration (ALWAYS REQUIRED)
# Check Java version in pom.xml (check all pom.xml files for multi-module)
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l -E "<(source|target|maven.compiler.source|maven.compiler.target)>" {} \;
# Check for javax.* imports (recursive from project root)
grep -r "import javax\." --include="*.java" . | head -10
Detection: If ANY command above returns output -> REQUIRED
Check: sdk-replacement (ALWAYS REQUIRED)
# Check for Neo dependencies (check all pom.xml files for multi-module)
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l -E "neo-java-web-api|scp-neo" {} \;
Detection: If ANY command above returns output -> REQUIRED
Check: approuter-setup
# (1) Served UI — static content the approuter would front. A bare web.xml
# does NOT count: a servlet that only returns JSON/text is API-only.
find . -path "*/webapp/*" -type f \
\( -name "*.html" -o -name "*.css" -o -name "*.js" -o -name "*.jsp" \) | head -5
# (2) Authentication — approuter is needed to front XSUAA when auth is present.
find . -name "web.xml" -path "*/WEB-INF/*" \
-exec grep -l -E "<auth-method>|<security-constraint>|<login-config>" {} \;
Detection: REQUIRED only if command (1) OR (2) returns output.
🛑 The presence of
web.xmlalone does NOT mean an approuter is needed. An API-only, no-auth backend (a servlet that returns JSON/text, no static UI, no<auth-method>/<security-constraint>, no XSUAA) must NOT get an approuter — adding one is actively harmful. The standard approuter'sxs-app.jsonbinds XSUAA (Cannot find service uaa), and even an auth-stripped approuter introduces a route/context-root mismatch that returns 404 on every request if itsxs-app.jsontarget doesn't exactly match the backend's context root. Thedocument-management,persistencescenarios are exactly this API-only shape — a singlejava.tomcatmodule bound directly to its services, noapprouter/, noxs-app.json. See the "When this skill does NOT apply" gate inapprouter-setup/SKILL.md.
Check: authentication-xsuaa
# Check all web.xml files for auth config
find . -name "web.xml" -path "*/WEB-INF/*" -exec grep -l -E "<auth-method>|<security-constraint>|<login-config>" {} \;
# Check for UserProvider imports (recursive from project root)
grep -r "com.sap.security.um.user" --include="*.java" .
Detection: If ANY command above returns output -> REQUIRED
Check: persistence-hana
# Check all web.xml files for DataSource
find . -name "web.xml" -path "*/WEB-INF/*" -exec grep -l -E "javax.sql.DataSource|jakarta.sql.DataSource" {} \;
# Check for @Resource DataSource (recursive from project root)
grep -r "@Resource.*DataSource" --include="*.java" .
Detection: If ANY command above returns output -> REQUIRED
Check: destinations
# Check all web.xml files for ConnectivityConfiguration
find . -name "web.xml" -path "*/WEB-INF/*" -exec grep -l -E "ConnectivityConfiguration|DestinationConfiguration" {} \;
# Check for connectivity API imports (recursive from project root)
grep -r "com.sap.core.connectivity.api" --include="*.java" .
Detection: If ANY command above returns output -> REQUIRED. Matches in neo/ submodules count.
Check: connectivity-onpremise
# Check for on-premise proxy usage (recursive from project root)
grep -r "HC_OP_HTTP_PROXY" --include="*.java" .
grep -r "ProxyType.*OnPremise" --include="*.java" .
Detection: If ANY command above returns output -> REQUIRED
Check: mail-destinations
# Check all web.xml files for mail session
find . -name "web.xml" -path "*/WEB-INF/*" -exec grep -l -E "javax.mail.Session|jakarta.mail.Session" {} \;
# Check for @Resource mail (recursive from project root)
grep -r "@Resource.*mail/Session" --include="*.java" .
Detection: If ANY command above returns output -> REQUIRED
Check: document-management-sdm
# Check all web.xml files for EcmService
find . -name "web.xml" -path "*/WEB-INF/*" -exec grep -l "com.sap.ecm.api.EcmService" {} \;
# Check for ECM API imports (recursive from project root)
grep -r "com.sap.ecm.api" --include="*.java" .
Detection: If ANY command above returns output -> REQUIRED. Matches in neo/ submodules count.
Check: keystore-credstore
# Check all web.xml files for KeyStoreService or PasswordStorage
find . -name "web.xml" -path "*/WEB-INF/*" -exec grep -l -E "KeyStoreService|PasswordStorage" {} \;
# Check for keystore/password imports (recursive from project root)
grep -r "com.sap.cloud.crypto.keystore\|com.sap.cloud.security.password" --include="*.java" .
Detection: If ANY command above returns output -> REQUIRED
Check: tomee-runtime
# Check for EJB annotations (recursive from project root)
grep -r "@Stateless\|@Singleton\|@EJB\|javax.ejb\|jakarta.ejb" --include="*.java" .
# Check for neo-javaee7-wp-api (check all pom.xml files for multi-module)
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l "neo-javaee7-wp-api" {} \;
Detection: If ANY command above returns output -> REQUIRED (use TomEE instead of Tomcat)
Check: dependency-compatibility
# Check for third-party libraries that may have Java 25 / Jakarta / HANA Cloud issues
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l -E "liquibase|flyway|guice|weld|dagger|poi-ooxml|itext|ehcache|hazelcast|quartz|retrofit|log4j" {} \;
# Check for DI framework usage
grep -r "com.google.inject\|GuiceServletContextListener\|javax.enterprise.context\|jakarta.enterprise.context" --include="*.java" . | head -5
# Check for schema migration tools + DI (complex interaction)
find . -name "pom.xml" -not -path "*/target/*" -exec grep -l -E "liquibase|flyway" {} \;
Detection: If ANY command above returns output -> REQUIRED. This skill resolves library-specific compatibility issues that fall outside the core jakarta-java25-migration.
Check: monitoring-logging (OPT-IN — DO NOT AUTO-DETECT)
# This skill is OPT-IN ONLY. The worker MUST return empty output here.
# Do not grep for slf4j / logback / log4j / OTEL / OpenTelemetry — those keywords
# are present in essentially every Java app and a naive match would flag every
# scenario as needing this skill, which provisions a `cloud-logging standard`
# managed service. In SAP BTP that plan is quota-limited per space and every
# additional unrequested instance fails the deploy with:
# "Service broker error: Service broker cloud-logging failed with: Quota is not sufficient for this request"
# The buildpack already routes stdout/stderr to CF's built-in log aggregator,
# so apps without an explicit Cloud Logging requirement deploy fine without
# this skill.
: # no-op — intentionally produces no output
Detection: This command produces NO output by design. Mark this skill [ ] (not selected). The plan rendering step (Step 1.4) leaves it unchecked. Only flip it to [x] if the user explicitly asks for Cloud Logging, OpenTelemetry tracing, or centralized observability — and even then, prefer asking the user to confirm before provisioning, since the service has space-level quota implications.
Step 1.3: Cross-Skill Technology Combination Rules
After detection, check for these common technology combinations that require coordinated handling across skills. Apply the "Then Also Ensure" action during Phase 3 execution:
| If Detected | Then Also Ensure |
|---|---|
| Apache POI + Java 25 | jakarta-java25-migration Step 10 includes JAXB test-scope dependencies (jaxb-api + jaxb-impl) |
sap_java_buildpack_jakarta |
mta-descriptor uses SAPMachineJRE (not SAPMachineJDK) in JBP_CONFIG_COMPONENTS and JBP_CONFIG_SAP_MACHINE_JRE |
| SAML + BASIC auth in Neo web.xml | Default to standard approuter (NOT extended) — XSUAA handles auth natively |
| Neo auth filters in web.xml (SAMLAuthFilter, BASICAuthFilter, CERTAuthFilter) | authentication-xsuaa Step 3 removes all Neo auth filters and their mappings |
Multiple servlet mappings for same servlet (e.g., /s/api/*, /b/api/*, /c/api/*) |
authentication-xsuaa Step 4 consolidates to a single mapping |
Why this matters: Several migration issues fall between skills — e.g., JAXB + Java 25 + POI forms a dependency chain that no single skill fully covers. These rules ensure nothing falls through the cracks.
Step 1.4: Create Detection Summary
After scanning, create a summary. Mark [x] for every skill whose detection commands returned ANY output. Mark [ ] ONLY if ALL detection commands for that skill returned empty output.
+---------------------------------------------------------+
| MIGRATION ANALYSIS RESULTS |
+---------------------------------------------------------+
| Project: [project-name] |
| Project Layout: [flat | multi-module] |
| Current Java Version: [version] |
| Neo Dependencies Found: [yes/no] |
+---------------------------------------------------------+
| REQUIRED SKILLS: (* = always required) |
| [x] jakarta-java25-migration * |
| [x] sdk-replacement * |
| [?] dependency-compatibility (check: third-party libs?) |
| [?] approuter-setup (check: static UI OR auth? NOT web.xml)|
| [?] authentication-xsuaa (check: auth-method found?) |
| [?] persistence-hana (check: DataSource found?) |
| [?] destinations (check: ConnectivityConfig found?) |
| [?] connectivity-onpremise (check: HC_OP_HTTP found?) |
| [?] mail-destinations (check: mail.Session found?) |
| [?] document-management-sdm (check: EcmService found?) |
| [?] keystore-credstore (check: KeyStoreService found?) |
| [?] tomee-runtime (check: EJB annotations found?) |
| [ ] monitoring-logging (OPT-IN — see note below) |
| [x] mta-descriptor * |
+---------------------------------------------------------+
| Replace [?] with [x] if detection returned output, |
| or [ ] if detection returned nothing. |
| |
| monitoring-logging is the ONE exception: it stays [ ] |
| even if the model thinks logging keywords (slf4j, |
| logback, OTEL) appeared. Flip to [x] ONLY when the user |
| has explicitly asked for Cloud Logging / OpenTelemetry. |
+---------------------------------------------------------+
Phase 2: Planning
Step 2.1: Create Migration Plan
Based on detection results, create an ordered plan:
+---------------------------------------------------------+
| MIGRATION PLAN |
+---------------------------------------------------------+
| |
| Step 1: jakarta-java25-migration |
| Migrate to Java 25 and Jakarta EE 10 |
| |
| Step 2: sdk-replacement |
| Replace Neo SDK with SAP Cloud SDK |
| |
| Step 3: authentication-xsuaa |
| Set up XSUAA authentication |
| |
| Step 4: persistence-hana |
| Configure HANA Cloud database |
| |
| Step 5: mta-descriptor |
| Generate deployment descriptor |
| |
+---------------------------------------------------------+
Step 2.2: User Approval
Present the plan to the user.
🛑 Do NOT stop and wait for approval in an automated / non-interactive run. When there is no interactive user to answer — i.e. you were invoked headless (
claude -p, CI, a pipeline, batch mode), or the invoking prompt tells you to run autonomously / without confirmation — render the plan and immediately continue to Phase 3 execution. Pausing to ask "Shall I proceed?" in that mode hangs the run: the question is never answered, so execution stops after planning and the migration produces an incompletecf-ai-migrated/(a built WAR but nomtad.yaml— the deploy then fails with "No MTA descriptor found"). This is a real failure mode observed in CI (tomee, run 43871348).Only wait for explicit confirmation when you are in a genuinely interactive session and the user has not already told you to proceed. When in doubt in an automated context, proceed — the plan is already rendered above for the record.
Phase 3: Execution
CRITICAL: Dispatch Each Feature Skill to a Subagent — EXCEPT mta-descriptor
The migration plan covers 5–14 skills, each with hundreds of lines of instructions and reference material. If the orchestrator invokes them all inline (loading each SKILL.md into its own context), it exhausts the context window before the migration is half done.
To stay context-efficient, dispatch each feature skill (Steps 3.1–3.2) to a subagent via the Agent tool. Each subagent gets a fresh context, reads only the one skill it needs, executes it against $COPY_DIR, and returns a short summary. The orchestrator keeps only the summary — not the skill body — and uses the saved .migration/cf-migration-config.json plus filesystem checks to track progress.
Step 3.3 (mta-descriptor) is the one exception — it runs INLINE in the orchestrator's context. mtad.yaml is the final deliverable, it must be byte-faithful to the cross-skill rules and the feature-skill list the orchestrator already holds, and we've seen subagent dispatch introduce drift here that breaks deploys. See Step 3.3 below.
Subagent Dispatch Pattern
For every skill in the plan, invoke the Agent tool with subagent_type: "general-purpose":
Agent(
subagent_type: "general-purpose",
description: "apply <skill-name>",
prompt: <the prompt below>
)
Prompt template:
You are migrating a SAP BTP Neo Java application to Cloud Foundry.
Working directory (operate ONLY here): <COPY_DIR>
Skill to apply: <skill-name>
Detected context: <relevant detection findings, e.g., "Apache POI present", "SAML+BASIC auth in web.xml">
Cross-skill rules to honor: <any rows from the Step 1.3 table that apply>
Your task:
1. cd <COPY_DIR>
2. Invoke the <skill-name> skill and follow its instructions exactly.
3. After completion, run the verification command(s) specified for this step:
<e.g., mvn test-compile, ls -la mtad.yaml>
4. Return a concise report (≤ 30 lines) with:
- Files created/modified (paths only, no diffs)
- Verification command output (pass/fail + last 10 lines if failed)
- Any blockers or follow-ups the orchestrator should know about
- Status: SUCCESS | FAILED | PARTIAL
Do NOT return file contents, full diffs, or skill body. The orchestrator already knows the migration plan and only needs your status.
Why summaries, not diffs: the orchestrator can re-read any file from
$COPY_DIRitself if it needs to. The subagent's job is to do the work and report status — not to ship the work product back through tokens.
Failure Handling
If a subagent reports FAILED or PARTIAL:
- Read the subagent's blocker description and the verification output it returned.
- If the failure is recoverable (e.g., specific file needs a tweak), make the fix in
$COPY_DIRdirectly withEdit/Write— don't re-spawn the subagent for a one-line fix. - If the failure is structural (skill needs to re-run), spawn a new subagent with the same prompt plus a
Previous attempt failed because: <reason>. Address it and retry.line. - Do NOT proceed to the next skill until the current step is
SUCCESS. Subsequent skills assume the previous one's invariants hold (e.g.,sdk-replacementassumes Jakarta migration is done).
Step 3.1: Dispatch Foundation Skills
Always run in this order — each is a separate subagent invocation:
| # | Skill | Required? | Verification |
|---|---|---|---|
| 1 | jakarta-java25-migration |
Always | mvn test-compile (both main + test must compile) |
| 2 | sdk-replacement |
Always | mvn clean compile |
| 3 | dependency-compatibility |
Only if detected in Step 1.2 | mvn clean compile |
Why
mvn test-compilefor #1: test code with hand-written servlet mocks is a common failure point after the Jakarta migration. The skill's Step 9 handles it, but the orchestrator must verify both main and test compile cleanly before moving on.
The subagent's prompt includes the verification command — its returned report contains the verification output. Trust the worker's report. Do NOT re-run the same mvn build inline after a SUCCESS; that's a token-burning duplicate. The orchestrator only re-verifies if the report status is FAILED/PARTIAL or the verification output it contains is ambiguous.
After SUCCESS, append one line to .migration/orchestrator.log and update .migration/cf-migration-config.json with "foundation.<skill>": "done".
Step 3.2: Dispatch Fe
…(truncated)