# Neo To Cf Migration Orchestrator

> Invoke this skill to orchestrate complete Neo to Cloud Foundry migration. Analyzes the Neo app, creates a migration plan, and dispatches each migration scenario (Jakarta, SDK, auth, persistence, destinations, etc.) to a separate subagent so the orchestrator's context stays lean across the full 5–14 step pipeline. Use when user says 'migrate Neo app', 'convert to CF', or 'Neo to Cloud Foundry migration'.

- Skill: `sap-samples/neo-to-cf-migration-orchestrator` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add sap-samples/neo-to-cf-migration-orchestrator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/sap-samples/neo-to-cf-migration-orchestrator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: sap-samples (https://skillmd.com/u/sap-samples)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/sap-samples/neo-to-cf-migration-orchestrator

---


# 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:
1. Analyzing the Neo application to detect required transformations
2. Creating a migration plan with skills in the correct order
3. **Dispatching each skill to its own subagent** so the orchestrator never loads the bodies of the 5–14 child skills into its own context
4. Validating the migration at each step using cheap filesystem and `mvn` checks
5. 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 `Agent` tool): 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_DIR` directly with `Edit`/`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 package` fails on a step that previously passed; `mtad.yaml` not 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:

```bash
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/role` requires the live XSUAA `appId` (e.g. `myapp!t1234`) which is only assigned after the first `cf deploy`. Role collections are created by `authentication-xsuaa` via `xs-security.json` + deployment — `subaccount-roles-import` only 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/`, and `mtad.yaml`
- Phase 4 runs **once per app** — deploy each app separately with `cf deploy . -f` from its directory
- Phase 5 reads all apps from `.migration/neo-roles.json` and 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-import` will 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.

```bash
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:

```bash
mkdir -p .migration
```

Save the paths to `.migration/cf-migration-config.json` (create or update):

```json
{
  "sourceAppDir": "<original APP_DIR>",
  "migrationAppDir": "<COPY_DIR>"
}
```

> All subsequent steps and all invoked skills must operate inside `$COPY_DIR`. The original `$APP_DIR` is 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:

```bash
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-logging` is 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:

1. Collect the 13 JSON lines into a single map `{skill → {required, evidence}}`.
2. Apply the Step 1.3 cross-skill rules table against the map and the project layout.
3. Render the Step 1.4 ASCII detection summary, marking `[x]` for `required=true` and `[ ]` 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 `Explore` subagents 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):**
> 1. If a detection command returns **ANY output at all**, the skill is **REQUIRED** — mark it `[x]`
> 2. If **ALL** detection commands for a skill return **empty output**, the skill is **not needed** — mark it `[ ]`
> 3. **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 in `neo/` subdirectories — those are the Neo patterns that need migration.
> 4. **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)
```bash
# 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)
```bash
# 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
```bash
# (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.xml` alone 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's
> `xs-app.json` binds XSUAA (`Cannot find service uaa`), and even an
> auth-stripped approuter introduces a route/context-root mismatch that returns
> **404 on every request** if its `xs-app.json` target doesn't exactly match the
> backend's context root. The `document-management`, `mail`, and `persistence`
> scenarios are exactly this API-only shape — a single `java.tomcat` module
> bound directly to its services, **no `approuter/`, no `xs-app.json`**. See the
> "When this skill does NOT apply" gate in `approuter-setup/SKILL.md`.

#### Check: authentication-xsuaa
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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)
```bash
# 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 incomplete `cf-ai-migrated/` (a built
> WAR but **no `mtad.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_DIR` itself 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`:

1. Read the subagent's blocker description and the verification output it returned.
2. If the failure is recoverable (e.g., specific file needs a tweak), make the fix in `$COPY_DIR` directly with `Edit`/`Write` — don't re-spawn the subagent for a one-line fix.
3. 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.
4. 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-replacement` assumes 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-compile` for #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)
