Zurück zu Skills
microsoft/azure-skillsVor der Ausführung prüfen

SKILL DETAIL

azure-app-onboard-prereq

microsoft/azure-skills/azure-app-onboard-prereq

The Azure App Onboard Prereq skill assesses whether source code is ready to deploy to Azure, serving as the check before infrastructure work. It evaluates build health, app completeness, dependencies and local services, stack compatibility, and deployment feasibility. It answers questions about what your app needs before it can be deployed, such as frameworks, dependencies, and configuration. The skill checks whether dependencies are compatible and identifies deployment blockers and unsupported frameworks. This skill is Phase 1 of the AppOnboard pipeline, called by the azure-app-onboard orchestrator at Step 3, or usable standalone for code readiness checks. It produces per-component verdicts (PASS/WARN/FAIL) consumed by downstream phases. The skill follows read-only evaluation and static-only verification rules, never running install, build, or test commands.

Installationen · 1.077Quelle ansehen

Installation

npx skills add https://github.com/microsoft/azure-skills --skill azure-app-onboard-prereq

Skill-Dateien

SKILL.md

Zuletzt synchronisiert · 29.08.2026

references/build-check.md
# Build Check

> ⛔ **No build/install/test commands during this check.** Use static analysis only.

Detect the project's language/framework stack and assess build health. Static detection (manifest reading) is **default**. Build execution is optional, requires user confirmation.

> **Dynamic detection:** Agent determines build commands from the project manifest (e.g., `package.json` `scripts` for the project-specific build script name). Table below is guidance, not a fixed lookup.

## Step 1: Detect Stack

Scan the workspace for project files to determine the technology stack.

| File | Language/Framework |
|------|-------------------|
| `package.json` | Node.js |
| `package.json` + `tsconfig.json` | TypeScript |
| `requirements.txt` / `pyproject.toml` | Python |
| `*.csproj` / `*.sln` | .NET |
| `pom.xml` | Java (Maven) |
| `build.gradle` / `build.gradle.kts` | Java/Kotlin (Gradle) |
| `go.mod` | Go |
| `Cargo.toml` | Rust |
| `Gemfile` | Ruby |
| `composer.json` | PHP |
| `docker-compose.yml` / `compose.yml` | (dependency source — parsed during deployability check) |
| `build.gradle` + `com.google.cloud.tools.jib` | Java (Jib) |

**Modern package manager lockfiles:** `uv.lock` → uv, `bun.lock` / `bun.lockb` → bun.

> ⚠️ If **no project file** is found, check for a `Dockerfile`. If a Dockerfile exists, note container-dependent build in findings.

### Multi-Project Detection

Search recursively for project manifests, skip `node_modules`/`.git`/`dist`/`build`/`vendor`/`.venv`/`__pycache__`/`.terraform`/`bin`/`obj`. Each directory with a project file = one component.

## Step 2: Static Detection (Default)

Read project manifests to infer build health **without executing any commands**.

**Check for:** missing dependencies (imports not in manifest), version conflicts, obvious misconfigurations (missing `main`/`start` script), lock file presence.

> **Package manager detection:** `package-lock.json` → npm, `yarn.lock` → yarn, `pnpm-lock.yaml` → pnpm. Default to npm if no lockfile.

### Import → Manifest Cross-Check

Scan source files for imports not in the dependency manifest. This catches pre-existing repo bugs that cause build failures on Azure.

**Node.js/TypeScript:** Scan `*.ts`, `*.tsx`, `*.js`, `*.jsx`, `*.mjs` for `import` / `require` statements. Extract package name (first path segment, `@scope/name` for scoped). Check against `dependencies` + `devDependencies`. Skip Node.js built-ins and relative paths.

**Python:** Scan `*.py` for `import {pkg}` / `from {pkg} import`, cross-reference against `requirements.txt` / `pyproject.toml`. Skip stdlib.

| File location | Severity |
|---|---|
| Build-time config (`next.config.*`, `webpack.config.*`, `vite.config.*`, `babel.config.*`, `postcss.config.*`, `tailwind.config.*`) | ❌ FAIL — build crashes at config load |
| Entry point / source — **package absent from manifest** | ❌ FAIL — `MODULE_NOT_FOUND` at runtime |
| Entry point / source — **version mismatch** | 🔧 Recommended Fix |
| Test files only | ⚠️ WARN |

**Scope:** Always scan ALL config files. For `src/`/`app/`/`pages/`/`lib/`, limit to 20 source files per component. Do NOT scan `node_modules/`, `.venv/`, `dist/`, `build/`.

**When ❌ FAIL or 🔧 Fix found:** Include in batch-then-approve: "Add `{package}` to `package.json` dependencies."

| Outcome | Verdict |
|---------|---------|
| No issues found | ✅ PASS |
| Warnings in manifest | ⚠️ WARN |
| Obvious errors | ❌ FAIL |
| No build system detected | ⚠️ WARN |
| Only Dockerfile found | ⚠️ WARN |

**Dependency vintage check:** ALL pinned deps 5+ years old AND ecosystem has known breaking changes (e.g., `werkzeug.contrib.*` removed, `flask.ext.*` removed, `itsdangerous<1.0` API changed) → ❌ FAIL. Grep for imports from removed modules.

**Transitive dependency check (post-migration):** After upgrading dependencies during 🔶 Major Migration, run install **through the build-validation gate (Step 3) — the migration-intent choice does NOT authorize install; the user must answer that specific per-command consent prompt first** to catch transitive deps. Also run entry-point import to catch import-time validation errors (e.g., WTForms `Email()` requires `email-validator`).

**F1 viability signal:** Check `f1Viable` per [dependency-compatibility.md § F1 Viability](dependency-compatibility.md). A vintage ❌ FAIL requiring 🔶 Major Migration (>5 files) → `f1Viable: false`.

## Step 3: Build Execution (Optional — User-Confirmed)

⛔ **Build-validation gate.** Before running ANY install/build/test command, ask via `ask_user`: "I'd like to run `{command}` to verify the build. Run it? (Yes / Skip)". A general prior consent (e.g., "fix my issues", "yes", "go ahead", "fix them") does NOT constitute consent — the user must answer THIS specific question with the exact command named. If they say Skip, continue with static-only verdicts.

Read manifest to determine actual build script. Install deps first. Capture output. Timeout 5 min.

| Outcome | Verdict |
|---------|---------|
| Exit code 0 | ✅ PASS |
| Succeeds with warnings | ⚠️ WARN |
| Exit code ≠ 0 | ❌ FAIL |
| Timeout >5 min | ⚠️ WARN |

## Native Module Detection

See [dependency-compatibility.md § Native Module Detection](dependency-compatibility.md) for the canonical detection procedure, edge cases, and package table. Results go to `buildRequirements.hasNativeModules`.

> ⛔ **Prebuild-install exception:** Packages with `prebuild-install` (without `node-gyp`) in lockfile use prebuilt binaries → `hasNativeModules: false`. Key examples: `better-sqlite3` v12+ = prebuilt, `sharp` v0.33+ = prebuilt, `bcrypt` = always native, `canvas` = always native. Check the lockfile — do not rely on package name alone.
references/cloud-sdk-migration.md
# Non-Azure Cloud Service Dependencies

> Detected during Step 2 manifest scan. The `ask_user` redirect gate is handled in SKILL.md Step 2 — this file defines the classification rules.

Functional cloud SDK deps are 🔶 `CLOUD_SDK_MIGRATION`. Populate `prereq-output.json.cloudSdkFindings[]`.

| Found Dependency | Azure Equivalent |
|-----------------|-----------------|
| AWS DynamoDB (`AWSSDK.DynamoDBv2`, `@aws-sdk/client-dynamodb`), GCP Firestore | Cosmos DB (NoSQL API) |
| AWS Cognito (`AWSSDK.CognitoIdentityProvider`, `amazon-cognito-identity-js`), Firebase auth (`firebase-admin`) | Entra ID / Entra External ID |
| AWS S3, GCP Cloud Storage (`@google-cloud/storage`), MinIO | Azure Blob Storage |
| AWS Lambda, GCP Cloud Functions (handler signatures) | Azure Functions (rewrite handlers) |
| AWS SQS (`@aws-sdk/client-sqs`, `AWSSDK.SQS`), GCP Cloud Tasks (`google-cloud-tasks`) | Queue Storage / Service Bus |
| AWS SNS (`@aws-sdk/client-sns`, `AWSSDK.SimpleNotificationService`), GCP Pub/Sub (`google-cloud-pubsub`) | Service Bus / Event Grid |
| Firebase (`firebase`, `firebase-admin`) full stack | Entra ID + Cosmos DB + Functions |

> **Observability carve-out:** Observability deps are ⚠️ WARN (app runs without them), NOT 🔶. Use this table to distinguish:
>
> | Package | Classification | Why |
> |---------|---------------|-----|
> | `@google-cloud/opentelemetry-*` | ⚠️ WARN | Telemetry — app works without it |
> | `@google-cloud/logging` | ⚠️ WARN | Logging — app works without it |
> | `@google-cloud/monitoring` | ⚠️ WARN | Monitoring — app works without it |
> | `aws-xray-sdk`, `aws-rum-web` | ⚠️ WARN | Tracing/RUM — app works without it |
> | `google-cloud-tasks` | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it |
> | `google-cloud-pubsub` | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it |
> | `google-cloud-storage` | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it |
> | `@aws-sdk/client-dynamodb`, `boto3` (DynamoDB) | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it |
> | `@aws-sdk/client-sqs`, `@aws-sdk/client-sns` | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it |
references/completeness-check.md
# Completeness Check

> ⛔ **No build/install/test commands during this check.** Use static analysis only.

Verify repository has required components for a deployable app.

### 1. Entry Point

Verify main/index file exists for each component.

| Stack | Expected Entry Point |
|-------|---------------------|
| Node.js | `main` or `start` script in `package.json` |
| Python | `app.py`, `main.py`, `manage.py`, or entry in `pyproject.toml` |
| .NET | `Program.cs` or `Startup.cs` with `<OutputType>Exe</OutputType>` |
| Java | Class with `public static void main` or `@SpringBootApplication` |
| Go | `main.go` in `package main` |
| Static | `index.html` at root or in output folder |

| Outcome | Verdict |
|---------|---------|
| Entry point found and file exists on disk | ✅ PASS |
| Ambiguous (multiple candidates) | ⚠️ WARN |
| No entry point | ❌ FAIL |
| Entry point declared but file missing | ❌ FAIL — `MODULE_NOT_FOUND` |

### 2. Dependency Manifest

| Outcome | Verdict |
|---------|---------|
| Manifest found with dependencies | ✅ PASS |
| Manifest exists but empty deps | ⚠️ WARN |
| No manifest found | ❌ FAIL (unless static site) |

> ⛔ **Oryx reads manifests ONLY at repo root.** Subdirectory manifests → ⚠️ WARN (🔧 Fix): create root wrapper (Python: `-r {subdir}/requirements.txt`, Node: workspaces). Add to batch-then-approve.

### 3. Configuration

| Outcome | Verdict |
|---------|---------|
| Config properly externalized | ✅ PASS |
| Hardcoded values but no secrets | ⚠️ WARN |
| Hardcoded secrets in source (no env var fallback) | ❌ FAIL |
| Env var + hardcoded fallback default | ⚠️ WARN |
| `.env` with placeholder values + runtime validation | ✅ PASS |

> **Compose file credentials:** Literal `*PASSWORD=<value>` with NO `${VAR}` → ❌ FAIL. `${VAR:-default}` → ⚠️ WARN. `${VAR}` only → ✅ PASS. ⛔ Do NOT downgrade based on filename ("dev-only") — treat every compose file as potentially production-bound.

### 4. Documentation

README with build/run instructions → ✅ PASS. Sparse/no README → ⚠️ WARN.

### 5. Listening Port

Web apps must bind a port. Detect via `app.listen`, `PORT` env var, framework port config, `WebApplication.CreateBuilder()` (implicit 5000/5001 .NET 6+).

| Outcome | Verdict |
|---------|---------|
| Port binding detected | ✅ PASS |
| Non-web (CLI, worker, function) | ✅ PASS — N/A |
| Web app with no port binding | ❌ FAIL |

### 6. Static Asset Integrity

Parse `href`/`src` from HTML tags. Check relative to HTML file directory. Ignore external URLs.

| Outcome | Verdict |
|---------|---------|
| All referenced assets found | ✅ PASS |
| Broken favicon only | ⚠️ WARN |
| Broken `<link>`, `<script>`, `<img>` reference | 🔧 Recommended Fix — set `fixPhase: "prereq"` |

### 7. Container Readiness

| Outcome | Verdict |
|---------|---------|
| Dockerfile + .dockerignore present | ✅ PASS |
| Dockerfile, no .dockerignore | ⚠️ WARN |
| Multi-process container detected | ⚠️ WARN |
| `CMD.*uv run` or `CMD.*poetry run` | ⚠️ WARN — dep sync at startup fails as non-root. **fix:** "Replace with direct command" **fixPhase:** `prereq` |
| `EXPOSE` port mismatch with app | 🔧 Fix — mismatch causes 502. Extract to `buildRequirements.exposedPort` |

### Stack-Specific Checks

Verify these patterns. Assess severity with tier definitions from [readiness-gate.md](readiness-gate.md) — only ❌ FAIL if causes deploy failure or startup crash.

- **Node.js:** `engines` field, session store type (MemoryStore = ephemeral), health endpoint
- **Express:** trust proxy when secure cookies are used behind reverse proxy
- **Any web app:** health endpoint (`/health`, `/healthz`), README documentation
- **Static sites:** health endpoint is N/A (responds 200 on `/`)

> ⛔ Default these to **⚠️ WARN / `fixPhase: "postdeploy"`** — an app that deploys and runs (missing trust proxy, README, in-memory sessions) is **not** `blocked`. Escalate to ❌ FAIL / `prereq` only when the case actually breaks THIS deploy: `engines` when the app needs a runtime the platform default won't provide, or a health endpoint when a probe is wired to a route the app lacks.

> **Do not short-circuit.** Iterate ALL sub-checks (1–7 + stack-specific) per component.

Severity tiers are defined in [readiness-gate.md](readiness-gate.md). Use the verdict tables in checks 1–7 above for deterministic outcomes. For judgment calls, assess based on deployment impact to this specific app.
references/component-mapping.md
Component-to-Azure mapping and existing infrastructure detection. Part of the [deployability check](deployability-check.md).

## Step 1: Component Mapping Feasibility

Determine if each detected component maps to a known Azure service.

| Component Type | Mappable Azure Services |
|----------------|------------------------|
| SPA / Static Site | Static Web Apps, Blob + CDN |
| SSR Web App | Container Apps, App Service |
| REST / GraphQL API | Container Apps, App Service, Functions |
| Background Worker | Container Apps, Functions |
| Scheduled Task | Functions (Timer Trigger) |
| Event Processor | Functions, Container Apps |
| CLI Tool | Not directly deployable — flag |

| Outcome | Verdict |
|---------|---------|
| All components map to Azure services | ✅ PASS |
| Some components need clarification | ⚠️ WARN |
| Unknown component type, can't map | ⚠️ WARN — ask user for context |
| Component is fundamentally incompatible | ❌ FAIL |

## Step 2: Existing Infrastructure Check

Check if the repo already has Azure infrastructure or deployment config.

| Found | Implication |
|-------|-------------|
| `azure.yaml` | AZD project — may only need update |
| `infra/*.bicep` | Bicep IaC exists |
| `infra/*.tf` or `*.tf` | Terraform IaC exists — classify provider (see below) |
| `Dockerfile` | Containerization ready |
| `.github/workflows/` | CI/CD configured |
| `azure-pipelines.yml` | Azure DevOps CI/CD |
| `docker-compose.yml` | Multi-container setup — parse for service dependencies |
| None of the above | Greenfield — full prep needed |

Record what exists; this feeds into recipe selection during azure-prepare.

### Terraform Provider Classification

When `.tf` files are detected, read `versions.tf`, `provider.tf`, or `main.tf` to identify `required_providers`. Classify and write to `context.json.detectedInfraProvider.terraform`:

| `required_providers` contains | Classification | Scaffold behavior |
|-------------------------------|---------------|-------------------|
| `hashicorp/azurerm` only | `"azure"` | Halt — existing Azure IaC |
| `hashicorp/google` or `hashicorp/google-beta` (no `azurerm`) | `"gcp"` | Generate Azure TF alongside |
| `hashicorp/aws` (no `azurerm`) | `"aws"` | Generate Azure TF alongside |
| Multiple cloud providers including `azurerm` | `"multi"` | Halt — Azure IaC already present |
| Multiple cloud providers without `azurerm` | `"multi"` | Generate Azure TF alongside |
| No provider block found or only non-cloud providers | `"unknown"` | Halt — ask user to clarify |

Also check for `azure.yaml` coexistence: if BOTH `azure.yaml` AND non-Azure `.tf` exist → `azure.yaml` takes priority → route to `azure-deploy`, not AppOnboard scaffold.

### Compose Service Dependency Extraction

When `docker-compose.yml` or `compose.yml` is found, parse `services:` for infrastructure dependencies. Map known images to `detectedServices[]` entries (`DetectedService` in `session-schemas.ts`):

| Image pattern | `type` | Version source |
|--------------|--------|----------------|
| `postgres:*` | `postgresql` | Image tag (e.g., `postgres:16` → `"16"`) |
| `redis:*` / `redis/redis-stack:*` | `redis` | Image tag |
| `*kafka*` (bitnami, confluent, etc.) | `kafka` | Image tag |
| `elasticsearch:*` / `opensearchproject/*` | `elasticsearch` | Image tag |
| `mariadb:*` | `mariadb` | Image tag |
| `mongo:*` | `mongodb` | Image tag |
| `rabbitmq:*` | `rabbitmq` | Image tag |
| `minio/*` | `minio` | Image tag |
| `mysql:*` | `mysql` | Image tag |

Set `source: "compose"` on each. If no tag or `latest`, omit `version`. Skip the app's own service entries (services with `build:` context pointing to the repo).

### Compose Hostname Detection

After extracting compose services, grep app source code and config files for compose service names used as hostnames. Compose DNS names (e.g., `postgres`, `redis`, `api`) resolve inside Docker networks but NOT on Azure PaaS.

**Detection:** For each extracted service name, search app config files (`.env`, `config.*`, `application.*`, `settings.*`) and source code for patterns like `host=<service_name>`, `<service_name>:<port>`, `://<service_name>:`, or `<service_name>.` used as a hostname. Common examples: `host=postgres`, `redis://redis:6379`, `PGHOST=db`.

**Verdict:** ⚠️ WARN — `id: W-COMPOSE-HOSTNAME`. "App references Docker Compose service name `{name}` as a hostname. On Azure, use the managed service endpoint (set via environment variable) instead." Include in `postDeployRecommendations[]`: `{ "title": "Replace compose hostnames with Azure endpoints", "reason": "Compose DNS names don't resolve on Azure PaaS", "effort": "low", "services": ["{mapped Azure service}"] }`.
references/dependency-compatibility.md
Dependency compatibility checks for Azure. Part of the [deployability check](deployability-check.md).

## EOL / Unsupported Runtimes

| Verdict | Condition |
|---------|----------|
| 🔶 Major Migration | .NET Framework 4.x, ASP.NET Core 2.1, Python 2.x |
| ❌ FAIL or 🔶 | Node.js < 18, Java < 11 — config-only upgrade → ❌ FAIL (fixable), API changes needed → 🔶 |

> **Quick test:** ONE config value change, no import changes? → ❌ FAIL. Otherwise → 🔶.

**Ecosystem-era check (Python):** ALL pinned deps pre-2018, no Python 3.10+ wheels → ❌ FAIL. Signals: `flask_script`, `werkzeug<1.0`, `itsdangerous<1.0`, imports from `werkzeug.contrib.*` / `flask.ext.*`.

## EOL / Unmaintained Frameworks

| Verdict | Condition |
|---------|-----------|
| 🔶 Major Migration | Flask < 2.0, Django < 3.2, Express < 4.0, Rails < 6.0, Spring Boot < 2.7 |
| ⚠️ WARN | React < 16, Angular < 14, Next.js < 13 |

## Archived / Abandoned Repositories

| Signal | Verdict |
|--------|--------|
| `archived: true` + EOL stack | 🔶 Major Migration |
| `archived: true` (current runtime) | ⚠️ WARN |
| README "deprecated"/"unmaintained" + EOL | 🔶 Major Migration |
| README "deprecated"/"unmaintained" (current) | ⚠️ WARN |

> **Remediation scope:** Fixing all blockers requires major upgrade OR >5 files → 🔶 Major Migration.

## Intentionally Vulnerable Applications

Detect via **code structure first**, metadata second. These apps are designed to be exploited — vulnerability IS the product.

**Code signals (check first):**
- Directory `vulnerabilities/` with subdirs like `sqli/`, `xss/`, `csrf/`, `fi/` (file inclusion)
- Security-level config toggling vulnerability severity (`default_security_level`, `security.level`, `difficulty`)
- Source files with intentionally unsanitized `$_GET`/`$_POST`/`$_REQUEST` passed to SQL queries, shell commands (`exec`, `shell_exec`, `system `), or `eval`/`include` -- that are systematic across 3+ files (not a single bug)
- `hackable/`/`exploit/` directories, or files with names like `command_injection.php`, `brute_force.php`

**Metadata signals (secondary check):**
- Project/package description or repo About containing: "deliberately vulnerable", "intentionally insecure", "vulnerable by design", "security training", "penetration testing", "do not deploy to production"
- README/License warning against internet-facing deployment

**Verdict:** ≥2 code signals OR 1 code + 1 metadata → 🛑 HALT. Single metadata only → ⚠️ WARN (could be a disclosure).

> ⛔ **🛑 HALT is a verdict, not an exit.** On 🛑 HALT your NEXT action MUST be the Step-4 write — persist all 3 artifacts (`prereq-output.json`, `context.json`, `readiness-report.md`) with `overallHealth: "blocked"` via the `create` tool, then read them back, BEFORE printing any halt summary to the user. A halt message with no `prereq-output.json` on disk is a failure — the deterministic `blocked` verdict must be persisted first, because the halt message can end the turn.

## Non-Azure Cloud SDK Dependencies

Functional cloud SDK deps → 🔶 blockers; classification and observability carve-out in [cloud-sdk-migration.md](cloud-sdk-migration.md). The redirect gate (SKILL.md Step 2) and the deploy-blocking stop (SKILL.md Step 8 Row 2) own all routing — no `routeToSkill` decision happens here.

## Platform-Specific Dependencies

| Dependency Type | Verdict |
|----------------|---------|
| Native OS binaries (`.so`, `.dll`) | ⚠️ WARN |
| Native Node.js addons on free/low-tier SKUs | ⚠️ WARN |
| GPU-required libraries (CUDA) | ⚠️ WARN |
| Local file system writes, file-based DBs | ⚠️ WARN — ephemeral on PaaS |
| BuildKit Dockerfile syntax (`--mount`, `# syntax=`) | ⚠️ WARN `W-BUILDKIT` — set `buildRequirements.hasBuildKitSyntax: true`. **ACR `az acr build` does NOT support BuildKit** — scaffold must generate `Dockerfile.azure`. **fix:** "Generate ACR-compatible Dockerfile.azure" **fixPhase:** `scaffold` |
| Jib container build (no Dockerfile) | ⚠️ WARN — note Jib path for scaffold |
| Redis client without TLS config | ⚠️ WARN `W-REDIS-TLS` — **fix:** "Add TLS config" **fixPhase:** `prereq`. **Config key registration:** if the app uses a config library that requires keys to be pre-registered before env var override (Go/Viper `Unmarshal()`, Spring `@ConfigurationProperties`), the config file must also declare the TLS key (e.g., add `tlsEnabled: false` to YAML) — otherwise the env var is silently ignored. Detection: grep for `viper.Unmarshal`, `mapstructure`, `@ConfigurationProperties`. |
| PostgreSQL client with SSL disabled | ⚠️ WARN `W-PG-SSL` — **fix:** "Set SSL mode env var" **fixPhase:** `scaffold` |
| MySQL client without TLS config | ⚠️ WARN `W-MYSQL-SSL` — **fix:** "Enable client TLS for Azure MySQL" **fixPhase:** `prereq`. Most MySQL drivers/ORMs need an in-code SSL option (no SSL env var like Postgres has), so this is a client-config change → prereq remediation batch (like `W-REDIS-TLS`), not IaC-only scaffold. Detection: MySQL in the plan (`mysql:*` in compose, or a MySQL driver/ORM — e.g. `mysql2`, `sequelize` dialect mysql, `typeorm`, `prisma`, `knex`) with no SSL/TLS option in the client config. |
| Go Viper without env key replacer | ⚠️ WARN `W-VIPER-ENV` — **fix:** "Add SetEnvKeyReplacer call" **fixPhase:** `prereq` |
| Licensed/proprietary SDKs | ⚠️ WARN |

## Hardcoded Localhost URLs

| Pattern | Verdict | ID | fixPhase |
|---|---|---|---|
| `localhost`, `127.0.0.1`, `0.0.0.0` in API base URLs, CORS origins, webhook URLs, service discovery | ⚠️ WARN | `W-LOCALHOST-URL` | `scaffold` |

> **Exclude:** database connection strings, dev-only files, test files.

## Dockerfile Analysis

| Pattern | Verdict | ID | fixPhase |
|---|---|---|---|
| `CMD.*uv run` / `ENTRYPOINT.*uv run` | ⚠️ WARN | `W-UV-RUN` | `prereq` |
| `CMD.*poetry run` / `ENTRYPOINT.*poetry run` | ⚠️ WARN | `W-POETRY-RUN` | `prereq` |

> Also check `docker-compose.yml` `command:` overrides.

## Native Module Detection

Static lockfile analysis only.

| Language | Signal | Where |
|----------|--------|-------|
| Node.js | `"node-gyp"` in lockfile | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` |
| Python | `numpy`, `pandas`, `grpcio`, `Pillow`, `bcrypt`, `psycopg2` (not `-binary`), `cryptography`, `lxml`, `scipy`, `scikit-learn` | `requirements.txt`, `pyproject.toml` |
| .NET | `[DllImport]`, `NativeLibrary.Load` | `.cs` files |
| Go | `import "C"` (cgo) | `.go` files |
| PHP | `ext-*` requirements | `composer.json` |

> ⛔ **Lockfile grep is the ONLY valid evidence.** Do NOT set `hasNativeModules` from package name alone. `prebuild-install` without `node-gyp` = prebuilt → `hasNativeModules: false`. Key: `bcrypt` ✅ native, `bcryptjs` ❌ JS. `psycopg2` ✅, `psycopg2-binary` ❌. `sharp` v0.33+ ❌ prebuilt. `canvas` ✅ always. No lockfile → `"unknown"`, ⚠️ WARN.

When `hasNativeModules: true`: `f1Viable: false`, `f1BlockReason: "native modules ({signal})"`.

## F1 Viability — Beyond Native Modules

| Condition | f1BlockReason |
|-----------|---------------|
| Large dep tree (Python >10, Node lockfile >500KB, .NET >20 NuGet) | `"large dependency tree"` |
| Build-time compilation (`tsconfig.json` + `"build"` script) | `"build-time compilation"` |
| WSGI/ASGI server (`gunicorn`/`uvicorn`/`daphne`) | `"WSGI/ASGI server"` |
| 🔶 Major Migration (>5 files) | `"major migration"` |

When `f1Viable: false`: prepare selects B1 (~$13/mo) minimum.

## First-Run Initialization

Detect init steps needed before first HTTP request — run automatically in dev but cause 500s on Azure.

| Framework | Signal | Init command |
|-----------|--------|-------------|
| Flask-Migrate/Alembic | `flask_migrate`/`alembic` + `migrations/` | `flask db upgrade` |
| Django | `django` + `manage.py` + `*/migrations/` | `python manage.py migrate` |
| Prisma | `@prisma/client` + `prisma/schema.prisma` | `npx prisma migrate deploy` |
| TypeORM | `typeorm` + `migrations/`/`ormconfig` | `npx typeorm migration:run` |
| Sequelize | `sequelize` + `migrations/` | `npx sequelize-cli db:migrate` |
| EF Core | `EntityFrameworkCore.Tools` in csproj | `dotnet ef database update` |

**Seed/bootstrap:** Flask `@app.cli.command('deploy')` → `flask deploy`. Django `fixtures/` → `manage.py loaddata`. Seed commands: `required: false`.

Migration framework + `migrations/` dir → ⚠️ WARN, write to `prereq-output.json.initCommands[]` (schema in [`prereq-schemas.ts`](prereq-schemas.ts)). ORM without `migrations/` dir → ✅ PASS. Prepare prepends required `initCommands` to `deployStrategy.startupCommand`; scaffold encodes in `appCommandLine`. Migrations are idempotent — safe on every cold start.

## Database & Storage

| Found | Assessment |
|-------|-----------|
| File-based DB (SQLite, LevelDB, DuckDB) | ⚠️ WARN — ephemeral on PaaS. Deploy as-is, suggest managed DB in `postDeployRecommendations[]` |
| Local file storage | Needs Azure Blob Storage |
| In-memory cache only | Consider Redis |
| Managed DB connection string | Ready — update connection |
references/deployability-check.md
# Deployability Check

> ⛔ **No build/install/test commands — `npm install`, `npm test`, `dotnet build`, `dotnet restore`, `dotnet test`, `pip install`, `pytest`, `go mod download`, `cargo build`. Use static analysis only during this check.**

Assess whether the repository can feasibly be deployed to Azure and whether a preparation plan can be created.

> ⛔ **You MUST read the following in order. Skip items marked conditional if the condition is not met:**
>
> 1. [component-mapping.md](component-mapping.md) — Steps 1–2: Component→Azure mapping, existing infrastructure detection, Terraform provider classification, compose service extraction. **Conditional: monorepo only (>1 project manifest found).** For single-component repos, skip to Step 3.
> 2. [dependency-compatibility.md](dependency-compatibility.md) — Step 3: EOL runtimes/frameworks, archived repos, vulnerable apps, platform deps, Dockerfile analysis, native module detection
> 3. Steps 4–5 below

## Step 4: Recipe Feasibility

Assess whether at least one deployment recipe is viable.

| Check | Question |
|-------|----------|
| AZD feasible? | Standard web app stack? No exotic build requirements? |
| Container feasible? | Can be Dockerized? Or already has Dockerfile? |
| Functions feasible? | Event-driven or HTTP-triggered stateless handlers? |
| Terraform feasible? | User has TF experience or existing TF files? |

| Outcome | Verdict |
|---------|---------|
| At least one recipe clearly viable | ✅ PASS |
| Viable with modifications (config change, Dockerfile tweak) | 🔧 Recommended Fix — note required changes |
| Viable but requires significant rework (>5 files, architecture change) | 🔶 Major Migration — warn about scope |
| Minor platform concerns (ephemeral storage, missing .dockerignore) | ⚠️ WARN — informational |
| No viable recipe identified | ❌ FAIL |

## Step 5: Specialized Skill Detection

Check if the repo's stack requires a specialized deployment skill. If a match is found, set `context.json.routeToSkill` and `routeReason` so Step 8 routes directly.

> **Non-Azure cloud SDK deps** (AWS/GCP SDKs, Firebase, etc.) — evaluated and carried as 🔶 blockers (no `routeToSkill`); prereq stops at Step 8. See [dependency-compatibility.md § Non-Azure Cloud SDK Dependencies](dependency-compatibility.md).

| Dependency / Pattern | `routeToSkill` | `routeReason` |
|---------------------|----------------|---------------|
| `@github/copilot-sdk`, `github-copilot-sdk`, `GitHub.CopilotSdk` | `azure-hosted-copilot-sdk` | `copilot-sdk-detected` |
| `azure_ai_projects`, `azure-ai-agents`, `foundry-agents` | `microsoft-foundry` | `foundry-agents-detected` |

## f1Viable Aggregation

After the deployability check completes, cross-check `buildRequirements.f1Viable`. The build axis may have set an initial value — the deployability check MUST override it to `false` if ANY blocker was found during [dependency-compatibility.md § Native Module Detection](dependency-compatibility.md) or [§ F1 Viability — Beyond Native Modules](dependency-compatibility.md). Verify `f1BlockReason` is populated when `f1Viable: false`.
references/prereq-artifacts.md
Artifact write procedures for the prereq phase exit. Read at Step 4 of the [readiness gate](readiness-gate.md).

## Write Artifacts

> ⛔ **Write all 3 artifacts before exiting prereq.** Downstream phases read these.
> ⛔ **Use `create` tool for ALL session files — NEVER `powershell`.** Terminal is OK ONLY for read-only ops: UUID generation, git info, file existence checks.
> ⛔ **Path scoping:** ALL `create` calls target `.copilot-azure/sessions/{uuid}/`. After writing, read back to confirm correct path.

1. **`prereq-output.json`** — ⛔ Read [`prereq-schemas.ts`](prereq-schemas.ts) for `PrereqOutput` interface.

   > ⛔ **Per-component verdicts MUST persist.** Every entry in `components[]` MUST include a `verdicts` object: `{ "build", "completeness", "deployability" }` with values `PASS`/`WARN`/`FAIL` (`build` may be `SKIPPED` when build validation was skipped; `completeness`/`deployability` are never `SKIPPED`). Downstream readiness scoring and the prepare phase read these — never omit them.
   >
   > ⛔ **Warnings MUST persist.** Every ⚠️ WARN → `warnings[]`: `{ "id": "W-{ID}", "component", "axis", "summary", "detail", "fix", "fixPhase" }`. Both `fix` and `fixPhase` are required — validate before writing.
   >
   > ⛔ **Health endpoint:** Write detected path to `healthEndpoint` (e.g., `"/api/v1/health/"`). If none → `null` + `W-HEALTH` warning with `fixPhase: "scaffold"`.
   >
   > ⛔ **Entry point:** Write to `entryPoint` (e.g., `"index.js"`). `null` for .NET/Go/Java (Oryx handles startup).
   >
   > ⛔ **`postDeployRecommendations[]`:** For each ⚠️ WARN, write per `PostDeployRecommendation` schema from [`session-schemas.ts`](session-schemas.ts): `{ "title", "reason", "effort": "low|medium|high", "services": [] }`.

2. **`context.json`** — ⛔ Use `edit` (not `create` — Step 1 already created it). Populate `components[]`, `repo`, `detectedInfra[]`, `detectedServices[]`, `app.name` (from primary component's project manifest or workspace root dir name). Append `"prereq"` to `completedPhases` NOW (before presenting), set `currentPhase: null`, update `lastModifiedUtc`.

3. **`readiness-report.md`** — Summary table (Build/Completeness/Deployability verdicts), detected stack, all warnings with actionable detail. Clean markdown, no rigid template.

> ⛔ **Phase exit gate:** All 3 artifacts must exist. If any missing, write NOW.
references/prereq-schemas.ts
/**
 * Prereq-phase TypeScript interfaces for prereq-output.json.
 *
 * Standalone schema for azure-app-onboard-prereq — this skill can run independently.
 *
 * Shared types used across phases (AppOnboardComponent, PostDeployRecommendation,
 * DetectedService, AppOnboardContext) are defined in:
 *   session-schemas.ts (local copy in this skill's references/)
 *
 * Context schema (AppOnboardContext, AppOnboardIntent) is defined in:
 *   session-schemas.ts (local copy in this skill's references/)
 */

// ─── prereq-output.json ──────────────────────────────────────────────────────

export interface BuildRequirements {
  hasNativeModules: boolean;
  hasDockerfile: boolean;
  /** True when the app can deploy on App Service F1 (Free) SKU */
  f1Viable: boolean;
  /** True when Dockerfile uses BuildKit-only syntax (--mount, # syntax=docker/dockerfile:1) */
  hasBuildKitSyntax?: boolean;
  /** Port from EXPOSE directive — used for Container Apps targetPort */
  exposedPort?: number;
  /** Estimated native module compilation time in seconds (typically 30–300s;
   *  larger compiled dependencies like scipy may take 600+s).
   *  Used by deploy phase to set WEBSITES_CONTAINER_START_TIME_LIMIT. */
  estimatedInstallTime?: number;
  /** Why F1 is not viable — set when f1Viable is false.
   *  Examples: "native modules (node-gyp)", "large dependency tree (27 pinned deps)",
   *  "build-time compilation (TypeScript tsc)", "major migration (Flask 0.12→2.3)" */
  f1BlockReason?: string;
}

/** Structured warning from prereq evaluation */
export interface PrereqWarning {
  id: string;
  component: string;
  axis: "build" | "completeness" | "deployability";
  summary: string;
  detail: string;
  /** What to do about this warning — actionable fix instruction.
   *  Examples: "Set PGSSLMODE=require env var in IaC", "Add trust proxy setting to Express app" */
  fix: string;
  /** When the fix should be applied in the pipeline.
   *  "prereq" = prereq can fix this NOW with user approval (code/config changes that prevent deploy failures).
   *  "scaffold" = handled in generated IaC only (env var override, probe config). NEVER modifies user code.
   *  "deploy-gate" = surface at deploy approval gate for user awareness. No code changes.
   *  "post-deploy" = informational — add to postDeployRecommendations[], no action during pipeline. */
  fixPhase: "prereq" | "scaffold" | "deploy-gate" | "post-deploy";
}

export interface PrereqOutput {
  // AppOnboardComponent[] — see session-schemas.ts
  components: any[];
  /** Structured warnings — see prereq-artifacts.md for write rules */
  warnings: PrereqWarning[];
  detectedStack: string;
  /** Prereq-only: auto-approves readiness gate + simplifies prepare alt analysis.
   *  Does NOT skip any phase, gate, reference read, or validation. */
  fastTrackEligible: boolean;
  overallHealth?: "ready" | "readyWithCaveats" | "blocked";
  /** Build-time requirements detected from manifests and lockfiles */
  buildRequirements?: BuildRequirements;
  /** Detected health endpoint path (e.g., "/health", "/api/v1/health/").
   *  Used by scaffold for Azure health probe config and deploy for curl health check.
   *  null if no health endpoint found (triggers W-HEALTH warning). */
  healthEndpoint?: string | null;
  /** Application entry point detected from manifest (e.g., "index.js", "main.py", "cmd/main.go").
   *  Used by prepare/deploy-strategy.md for startupCommand generation. */
  entryPoint?: string;
  /** Structured recommendations derived from WARN findings.
   *  Merged into prepare-plan.json.postDeployRecommendations[] by the prepare phase.
   *  PostDeployRecommendation type — see session-schemas.ts */
  postDeployRecommendations?: any[];
  /** First-run init commands (DB migrations, seed data) detected from
   *  framework signals + migrations/ dir. Prepare prepends required entries
   *  to deployStrategy.startupCommand. See dependency-compatibility.md § First-Run. */
  initCommands?: { type: string; framework: string; command: string; required: boolean }[];
}
references/readiness-gate.md
# Readiness Gate — Step 4

## Write Artifacts

⛔ **You MUST read [`prereq-artifacts.md`](prereq-artifacts.md)** for complete artifact write procedures and phase exit checklist.

---

## Severity Tiers

| Verdict | Icon | Meaning |
|---------|------|---------|
| Hard Halt | 🛑 | App is intentionally vulnerable — pipeline stops, no fix possible |
| Major Migration | 🔶 | Large-scope change (EOL runtime, cloud SDK migration, >5 files) — redirect or warn |
| Critical | ❌ FAIL | Deployment will fail — agent can fix (≤5 files, config-level) |
| Recommended Fix | 🔧 | App deploys but has quality/security issues — agent offers fix |
| Warning | ⚠️ WARN | Informational, non-blocking — can proceed with caveats |
| Pass | ✅ PASS | No issues |

---

## Overall Health Gate

**Compute `overallHealth`:** ALL ✅ PASS → `"ready"` | Any ⚠️ WARN no ❌ FAIL → `"readyWithCaveats"` | ANY ❌ FAIL → `"blocked"`

**Component `readiness.status` alignment:**
- `"ready"` → `readiness.status: "ready"`
- `"readyWithCaveats"` → `readiness.status: "ready"` (WARNs aren't "needs fixes")
- `"blocked"` → `readiness.status: "needsFixes"`
- After remediation → `readiness.status: "fixesApplied"`

⛔ `readiness.status: "needsFixes"` requires at least one ❌ FAIL. If all ⚠️/✅, use `"ready"`.

---

## Critical Readiness Gate

⛔ **Verdict propagation cross-check** before computing `overallHealth`:
1. Any finding with `verdict: "FAIL"` → axis verdict MUST be `"FAIL"`.
2. Any finding with `verdict: "WARN"` + `fixPhase: "prereq"` → escalate to `"FAIL"` (prevents wasting a deploy cycle). ⛔ Escalate only WARNs that would actually break THIS deploy (build/startup failure, or a health probe wired to a route the app lacks). Issues that deploy and run fine — missing trust proxy, README, in-memory sessions — stay `fixPhase: "postdeploy"`/`"scaffold"`; `engines`/health-endpoint escalate only on a real version/probe mismatch (see [completeness-check.md](completeness-check.md) § Stack-Specific Checks).

| Tiers | Reference file |
|-------|---------------|
| 🛑 🔶 🔧 ⚠️ | [`dependency-compatibility.md`](dependency-compatibility.md) |
| ❌ 🔧 | [`completeness-check.md`](completeness-check.md) |
| ❌ | [`build-check.md`](build-check.md) |

**Post-evaluation HALT cross-check:** Intentionally vulnerable apps (≥2 code signals from dependency-compatibility.md) → `overallHealth: "blocked"` MUST be written to `prereq-output.json` on disk (Step 4) BEFORE any halt message. If the artifact is not on disk when you reach the halt, write it NOW via the `create` tool and read it back — do NOT present the halt until it exists.

---

## Batch-Then-Approve Flow

⛔ **Artifacts before message.** Write AND read back all 3 artifacts (`prereq-output.json`, `context.json`, `readiness-report.md`) to confirm they exist on disk BEFORE presenting any findings, cloud-SDK stop prompt, or 🛑 hard-halt message. Those messages can end the turn, so every artifact MUST already be persisted — NEVER batch artifact writes after the message.

1. **Detect ALL issues first** — full 3-axis scan, all components.
2. **Present ALL findings at once** — summary: "🔍 Readiness: 2 critical, 1 recommended fix, 3 warnings". Group: 🛑 → 🔶 → ❌ → 🔧 → ⚠️.
3. **Fix plan** — for ❌, 🔧, 🔶, ⚠️ with `fixPhase: "prereq"`: describe WHAT and WHY. ⛔ Exclude 🔶 with `routeToSkill` set. Never include 🛑.
4. **User choice** (based on highest severity):
   - **🛑:** Pipeline stops. No formal gate.
   - **🔶 + others:** "Fix {N} issues including {M} migration(s) — scope warning" / "Fix blockers only" / "Continue with risks" / "Cancel"
   - **🔶 only:** "Attempt migration" / "Continue as-is" / "Cancel"
   - **❌/🔧/⚠️ with fixPhase prereq:** "Fix {N} deployment issues" / "Continue with risks" / "Cancel"
5. **After approval** → apply fixes per [remediation-protocol.md](remediation-protocol.md).

> ⛔ **Two-gate rule:** Intent approval ≠ fix execution approval. Present the fix prompt here even if user agreed earlier.

---

## Fast-Track

Single-component + no DB + no auth + **no Dockerfile** → `fastTrackEligible: true`.

---

## Present Findings (Step 5)

⛔ Do NOT skip — user must see scan results before pipeline continues.

**Part 1 — Summary:** "🔍 Readiness: {N} critical, {M} fixes, {K} warnings" (or "✅ Ready").
**Part 2 — Per-axis reasoning:** Verdict icon + 1–2 sentence summary per axis.
**Part 3 — Findings table:** Grouped by severity. Include warning ID and actionable detail.

**Data-loss warnings** (SQLite, in-memory sessions, local file storage) require explicit acknowledgment. Other ⚠️ are informational.

End with: "📄 Full evaluation saved to `readiness-report.md`."

### Remediation Decision Gate

⛔ **STOP after presenting findings.** Options:
1. **"Fix deployment issues"** — fix all actionable items (❌, 🔧, ⚠️ with `fixPhase: "prereq"`). Re-evaluate after.
2. **"I have context — let me guide the fixes"**
3. **"Continue without fixing — I accept the risks"**

Wait for explicit choice. Generic "Yes"/"Go ahead" ≠ remediation consent — clarify if ambiguous.

> ⛔ **Remediation budget:** Max 3 cycles. See [remediation-protocol.md](remediation-protocol.md) step 7.

> ⛔ **ARTIFACT CHECKPOINT.** After presenting findings, verify all 3 artifacts exist: `context.json`, `prereq-output.json`, `readiness-report.md`. Write any missing ones NOW.
references/remediation-protocol.md
# Remediation Protocol — Step 6

## Pre-check

> **🛑 Hard Halt?** Skip remediation. Set `overallHealth: "blocked"`, write artifacts, go to Step 7.
> **🔶 Major Migration?** If user chose "Attempt migration," enter loop. Warn changes may need review.

## Remediation Scope

⛔ **Fix ONLY Azure-deployment blockers:** missing dependencies causing build/startup failure, missing entry point, startup crashes, Azure-required configuration (port binding, env var externalization, managed-DB client TLS/SSL — e.g. `W-MYSQL-SSL`). Everything else → `postDeployRecommendations[]`.

## Remediation Loop

If any ❌ FAIL, 🔧 Fix, or ⚠️ WARN with `fixPhase: "prereq"` exist:
1. Present ALL together in one batch. Lead with: "Found {N} blockers, {M} fixes, {P} prereq-phase warnings. Fix all?"
   > ⛔ `fixPhase: "prereq"` is a remediation trigger regardless of severity. If left unfixed → deploy-time failures. Warnings with `fixPhase: "scaffold"/"deploy-gate"/"post-deploy"` are NOT included.
2. After fixes, ⛔ **re-run full Step 3 evaluation (all 3 axes)** on affected components. Re-read each reference file and re-evaluate inline.
3. ⛔ **Verify fixes via static analysis only.** File exists → exports match imports (grep) → no syntax errors → config values present. Do NOT run install/build/test commands.
4. ⛔ **Re-read SKILL.md before applying fixes** if it hasn't been read this turn.
5. ⛔ **Print after re-evaluation:** `🔄 Re-evaluation complete — ✅ N issues resolved, ❌ M remaining.` If M > 0, loop back.
6. **Build-validation gate (agent-modified code only).** After re-evaluation passes AND agent modified >2 source files, ask via `ask_user`: "I've fixed {N} files. Want me to install, build, and test? (Yes / Skip)". ⛔ **General prior consent** (e.g., "fix my issues", "yes", "go ahead", "fix them") **does NOT constitute consent to run install/build/test.** The user must answer THIS specific question. If they say Skip, proceed to Step 7 without running commands. Max 3 build-fix attempts.
7. ⛔ **Max 3 cycles total** (re-evaluations + build-fix retries). Then STOP and ask: "Keep trying" (grants 3 more) / "Guide fixes" / "Stop — accept remaining issues".

## Post-Remediation Artifact Updates

⛔ Re-evaluation = full 3-axis re-scan, NOT just reading fixed files. After ANY successful remediation, update all 3 artifacts per [prereq-artifacts.md](prereq-artifacts.md):
1. `context.json`: `readiness.status: "fixesApplied"`, updated verdicts, `fixes[]`, updated `statusSummary`
2. `prereq-output.json`: Rewrite completely via `create` tool. Recompute `overallHealth` from updated verdicts.
3. `readiness-report.md`: Append "Post-Fix Re-evaluation" section.

⛔ Re-derive verdicts from findings — do NOT carry forward pre-fix values. Per axis: worst finding verdict wins. Then: any axis FAIL → `"blocked"`, else any WARN → `"readyWithCaveats"`, else `"ready"`.

## Write Final State (Step 7)

⛔ Write components to BOTH `prereq-output.json.components[]` AND `context.json.components[]`. `context.json.components[]` is authoritative for downstream.

⛔ **`fixesApplied` requires re-evaluation evidence** — verify `🔄 Re-evaluation complete` was printed.

Set `readiness.status` per component: All PASS → `ready` | Fixes applied + re-eval passed → `fixesApplied` | Unresolved FAILs → `needsFixes`.

Update `context.json`: append `"prereq"` to `completedPhases`, set `currentPhase: null`, update `lastModifiedUtc`.

> ⛔ `overallHealth` MUST be one of: `"ready"`, `"readyWithCaveats"`, `"blocked"`.
> ⛔ `repo.lastScanCommit` is required — run `git rev-parse HEAD`.
references/session-protocol.md
# Session Protocol — Step 1

## All Prompts Are Actionable

> ⛔ **ALL prompts that activate this skill are actionable — go directly to Step 1.** Do NOT answer the user's question, give an overview of capabilities, or describe what AppOnboard can do before starting the pipeline. "Can Azure figure out my app?" and "Deploy my app" are the same action: Step 1 → Step 2 → scan. The user's phrasing (question vs command) does NOT change the workflow.

## Session Check

Resolve active session via pointer file.

> ⛔ **YOU MUST CREATE A SESSION BEFORE DOING ANY WORK — INCLUDING SCANNING**
>
> 1. **STOP** — Do not answer the user's question, scan code, or plan architecture yet
> 2. **CHECK** — Read `.copilot-azure/sessions/active-session.json`.
>    - **Pointer exists** →
>      1. ⛔ **Read [`session-schemas.ts`](session-schemas.ts)** to get the exact field names and types for `AppOnboardContext`, `PrereqOutput`, and `PreparePlan`. Do not guess field names. Then read the pointed-to session's `context.json`. Display: "Found session from [lastModifiedUtc] — {statusSummary}."
>      2. ⛔ **MANDATORY `ask_user` GATE — execute this step NOW, before ANY branching.** Call `ask_user`: "Resume this session or start fresh?" **Do NOT auto-resume, do NOT skip ahead to the staleness check, do NOT present cached findings.** Nothing else happens until the user answers. WHY: stale sessions from prior test runs cause the agent to silently reuse outdated results and skip sub-SKILL.md reads. The staleness check alone cannot catch this — only the user knows whether the prior session is still relevant.
>      3. **Branch on user's answer** (only after `ask_user` returns):
>         - Resume → **Staleness check (prereq skill only):** If `completedPhases` includes `"prereq"`, run `git rev-parse HEAD` and compare to `context.json.repo.lastScanCommit`. If the commit is **different** OR `lastScanCommit` is **missing** → tell the user: "Repo has changed since last scan — re-running prereq." Proceed to SKILL.md Step 2 (skip session creation). If identical → present cached findings from `prereq-output.json` and go to SKILL.md Step 8 (Route). Then ⛔ **Read the sub-SKILL.md for the NEXT phase** (derive from `completedPhases`). E.g., prereq done → read `prepare/SKILL.md`. Then continue from that phase.
>         - Start fresh → generate a new UUID via `[guid]::NewGuid().ToString()`, create a new session folder, update `active-session.json` to point to the new session. Old session folder is never touched again.
>    - **Pointer missing but session folders exist** → list folders under `.copilot-azure/sessions/`. If 1 folder: adopt it (read its `context.json`, write `active-session.json` pointing to it, show summary). If 2+: show a numbered list with `statusSummary` + `lastModifiedUtc` from each, ask user to pick one or start fresh. Write pointer for the chosen session.
>    - **No sessions at all** → generate a UUID by running `[guid]::NewGuid().ToString()` in the terminal. ⛔ **You MUST generate the UUID via a terminal command — do NOT hardcode a placeholder like `a1b2c3d4-e5f6-7890-abcd-ef1234567890`.** Create the session directory: `New-Item -ItemType Directory -Path ".copilot-azure/sessions/{uuid}" -Force`. Then write a **minimal** `context.json` using the `create` tool — only these 3 fields are known immediately: `{ "sessionId": "{uuid}", "createdUtc": "{ISO 8601 now}", "intent": { "userPrompt": "{user's first message verbatim}" } }`. Write `active-session.json` with `activeSessionId: {uuid}` using the `create` tool.
> 3. **PRUNE** — After resolving the active session, check remaining session folders. Delete any where `context.json.lastModifiedUtc` is >7 days ago. **Never delete the active session** (the one `active-session.json` points to).
> 4. **VERIFY** — Confirm `context.json` exists and is valid JSON. If missing or malformed, halt and retry creation — do NOT continue to Step 2 without a verified session.
> 5. **CONFIRM** — Begin your first response with: "Started session at `.copilot-azure/sessions/{uuid}/`" (new) or "Resuming session from [date] — {statusSummary}" (existing)
> 6. **THEN** proceed to Step 2
>
> ⛔ **Ordering: session FIRST, scanning SECOND.** If you scan the workspace or read project files before writing `context.json`, you have violated the session-first rule. The session must exist before ANY code analysis.
>
> ⛔ **Shell fallback:** If PowerShell/terminal hangs on first attempt (no output after 10s), use the `create` tool directly for session directory and file writes. Do NOT retry shell commands more than once.
>
> ⛔ **Path scoping: ALL `create` tool calls for session artifacts MUST target `.copilot-azure/sessions/{active-session-id}/`.** Writing to any other session folder is forbidden.

## CLI Availability

Call `mcp_azure_mcp_extension_cli_install` with `cli-type: "az"` to verify Azure CLI is available. If missing, surface installation instructions before proceeding. Downstream phases (prepare, deploy) require it. Fallback: skip if MCP tool unavailable.

## Azure Login Gate

**Azure login gate (mandatory):** Run `az account show --query "{id:id, name:name, tenantId:tenantId}" -o json` with a **5-second timeout** (PowerShell: `Start-Process` with `-Wait` or inline timeout; if command hangs beyond 5s, treat as failure). If it succeeds, merge `subscriptionId`, `subscriptionName`, `tenantId` into `context.json.azure` (use `replace_string_in_file` or rewrite the file — the minimal context.json from Step 1 sub-step 2 may not have the `azure` key yet).

> ⛔ **If `az account show` fails or hangs:** ⛔ **You MUST read [`subscription-resolution.md`](subscription-resolution.md)** and follow its fallback procedure. Do NOT proceed to Step 2 without a resolved subscription. Do NOT leave `context.json.azure` empty and continue. Every downstream phase (prepare, scaffold validation, deploy) requires Azure auth — proceeding without it produces incomplete results.

## User Identity Detection

**User identity detection (for `deployed-by` tag):** If `az account show` succeeded, also run `az ad signed-in-user show --query displayName -o tsv` (5-second timeout). Write the result to `context.json.azure.userDisplayName`. Fallback if `az ad` fails: use `az account show --query user.name -o tsv` (returns UPN/email). If both fail, leave empty — prepare phase will resolve. This value becomes the `deployed-by` tag on ALL resources — resolving it once here prevents inconsistent tag values across resources.

## Subscription Detection Method

> ⛔ **`az account show` is the ONLY subscription detection method in Step 1.** Do NOT call `mcp_azure_mcp_subscription_list` here — that tool returns ALL subscriptions across ALL tenants and causes a lengthy picker detour. `az account show` returns the CLI's active subscription in <1 second. MCP subscription list is reserved for prepare Step 1 when the user explicitly wants a different subscription.

## Artifact Locations

| Location | Artifacts |
|----------|-----------|
| `.copilot-azure/sessions/{uuid}/` | `context.json`, `prereq-output.json`, `prepare-plan.json`, `scaffold-manifest.json`, `deploy-result.json` |

## Phase-gated Reference Loading

> ⛔ **Phase-gated reference loading.** Do NOT pre-read reference files for downstream phases. Read each sub-skill's references only when entering that step. Scaffold references are irrelevant during deploy; prepare references are irrelevant during scaffold. Each sub-skill SKILL.md specifies its own required reads.
references/session-schemas.ts
/**
 * Context + shared TypeScript interfaces for AppOnboard session artifacts:
 * context.json, active-session.json, and shared types used across all phases.
 *
 * Per-phase schemas (load only when entering that phase):
 * - prereq-schemas.ts (in azure-app-onboard-prereq) — prereq-output.json (PrereqOutput, BuildRequirements, CloudSdkFinding)
 * - session-schemas-prepare.ts — prepare-plan.json (PreparePlan, PlannedService, etc.)
 * - session-schemas-deploy.ts — scaffold-manifest.json, deploy-result.json
 *
 * Source of truth for JSON artifacts in `.copilot-azure/sessions/{session-id}/`.
 */

// ─── shared types (used across all phases) ───────────────────────────────────

export interface AppOnboardComponentStack {
  language: string;
  framework: string;
  version: string;
}

export type ReadinessStatus = "ready" | "fixesApplied" | "needsFixes" | "unknown";

export interface AppOnboardComponentReadiness {
  status: ReadinessStatus;
  fixes: string[];
}

export type VerdictLevel = "PASS" | "WARN" | "FAIL" | "SKIPPED";

export interface AppOnboardComponentVerdicts {
  build: VerdictLevel;
  completeness: Exclude<VerdictLevel, "SKIPPED">;
  deployability: Exclude<VerdictLevel, "SKIPPED">;
}

export interface AppOnboardComponentFinding {
  category: "build" | "completeness" | "deployability";
  verdict: VerdictLevel;
  summary: string;
  fix: string | null;
}

export interface AppOnboardComponent {
  name: string;
  path: string;
  stack: AppOnboardComponentStack;
  readiness: AppOnboardComponentReadiness;
  verdicts?: AppOnboardComponentVerdicts;
  findings?: readonly AppOnboardComponentFinding[];
}

export interface AppOnboardAzureTarget {
  subscriptionId: string;
  /** Display name of the subscription (from `az account show --query name`).
   *  Shown at both approval gates so the user can verify the target. */
  subscriptionName: string;
  resourceGroup: string;
  region: string;
}

export interface AppOnboardRepoInfo {
  remote: string | null;
}

export interface AppOnboardOverride {
  key: string;
  value: string;
  reason: string;
}

export interface AppOnboardAppInfo {
  name: string;
}

export interface PostDeployRecommendation {
  title: string;
  reason: string;
  effort: "low" | "medium" | "high";
  services?: string[];
}

export interface DetectedService {
  type: string;
  version?: string;
  source: "compose" | "config" | "code";
}

// ─── context.json ─────────────────────────────────────────────────────────────

export interface AppOnboardIntent {
  userPrompt: string;
  description: string;
  users?: string;
  auth?: string;
  scale?: string;
  budget?: string;
  /** Set to true after prereq scan refines intent */
  refinedFromScan?: boolean;
  /** Facts discovered by the prereq scan */
  scanDiscoveredFacts?: string[];
}

export type AppOnboardPhase = "info" | "prereq" | "prepare" | "scaffold" | "deploy" | "cicd" | "observe";

export interface AppOnboardContext {
  sessionId: string;
  createdUtc: string;
  lastModifiedUtc: string;
  currentPhase: AppOnboardPhase | null;
  completedPhases: readonly AppOnboardPhase[];
  /** Human-readable 1-line summary of where the session stands, updated at each phase exit.
   *  Displayed in the session picker when the user resumes or switches sessions. */
  statusSummary?: string;
  intent: AppOnboardIntent;
  components: AppOnboardComponent[];
  azure: AppOnboardAzureTarget;
  repo: AppOnboardRepoInfo;
  app?: AppOnboardAppInfo;
  /** Infrastructure file types detected in repo: dockerfile, terraform, bicep, azure-yaml, github-actions */
  detectedInfra: readonly string[];
  /** Cloud provider targeted by detected IaC. Only populated when `.tf` or `.bicep` files found.
   *  Used by scaffold to distinguish "existing Azure IaC" (halt) from "non-Azure IaC" (generate Azure TF alongside). */
  detectedInfraProvider?: {
    terraform?: "azure" | "gcp" | "aws" | "multi" | "unknown";
  };
  /** Service dependencies parsed from docker-compose, config files, or code imports */
  detectedServices: readonly DetectedService[];
  overrides: AppOnboardOverride[];
  /** The skill to invoke next. Set by Step 2 (cloud SDK gate), Step 5 (specialized skill detection),
   *  or Step 8 (normal routing based on health + infra). Examples: "azure-cloud-migrate",
   *  "azure-hosted-copilot-sdk", "microsoft-foundry", "azure-app-onboard", "azure-prepare". */
  routeToSkill?: string;
  /** Why this route was chosen. Examples: "cloud-sdk-migration", "copilot-sdk-detected",
   *  "foundry-agents-detected", "ready-no-infra", "ready-existing-infra". */
  routeReason?: string;
}

// ─── active-session.json ─────────────────────────────────────────────────────

/** Pointer file at `.copilot-azure/sessions/active-session.json`.
 *  Avoids scanning all session folders on startup — read this one file
 *  to find the active session, then read that session's context.json. */
export interface ActiveSessionPointer {
  activeSessionId: string;
}

// PrereqOutput, BuildRequirements → see azure-app-onboard-prereq/references/prereq-schemas.ts
references/subagent-starter-scaffold.md
# Subagent Template — Starter App Scaffold (Zero-Code Path Step 4)

Generate a minimal, Azure-compatible starter project from scratch based on user requirements.

## Critical Rules

- ⛔ **Do NOT invoke ANY skills** — no `{"skill": "..."}` calls. You are a code generation sub-agent only.
- ⛔ **Do NOT generate Azure infrastructure** (Bicep, Terraform, `azure.yaml`). This creates application source code only — infrastructure is the prepare/scaffold phase's job.
- ⛔ **Do NOT install dependencies** — no `npm install`, `pip install`, or any package manager commands. The main agent handles the build-validation gate after you return.

## Input (provided by caller)

| Field | Source | Required |
|-------|--------|----------|
| App description | User's answer to "What kind of app?" or `context.json.intent.userPrompt` | YES |
| Chosen stack | Stack the user accepted or overrode (e.g., "Node.js/Express", "Python/FastAPI") | YES |
| Workspace root | Absolute path to write files | YES |
| Data needs | `true` if user described database/storage needs ("with a database", "stores tasks") | YES |
| Multi-page | `true` if user described multiple views/pages ("three tabs", "dashboard + settings") | YES |

## Workflow

### Step 1 — Apply starter patterns

Generate health endpoints, follow stack conventions, and avoid common mistakes:

**Health endpoints (MANDATORY):**
- `/healthz` — liveness: `200 { status: "ok" }`. Must return 2xx directly (no redirects), allow anonymous access.
- `/readyz` — readiness: check DB/cache/deps. `200` when ready, `503` when not.
- Container Apps: `httpGet.port` must match `targetPort` in ingress config.

**Stack conventions:**
- **Node.js/Express:** Listen on `process.env.PORT || 3000`. `"start"` script required. `"engines": { "node": ">=24" }`. Production deps in `dependencies`.
- **Python (Flask/FastAPI):** Production: `gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app`. Include `gunicorn` in `requirements.txt`. Bind `0.0.0.0`, not `127.0.0.1`.
- **Next.js/React:** Static export: `output: 'export'`. Hybrid SSR works on SWA.

**Project structure:** `src/` for app code (routes, entry, server). `.env.sample`, `.gitignore`, `package.json`/`requirements.txt`, `README.md`.

**Common mistakes to avoid:** hardcoded port, dev server in production, no health endpoint, missing `start` script, secrets in source, no CORS middleware, missing `engines.node`, Python missing `gunicorn`, Python using `passlib` (unmaintained, breaks on Python 3.12+ with bcrypt 5.x — use `bcrypt>=4.0` directly).

### Step 2 — Generate files

Scaffold a minimal starter project. Include:
- Entry point file with a working HTTP server, `/healthz` (liveness), and `/readyz` (readiness) endpoints
- Package manifest (`package.json`, `requirements.txt`, `*.csproj`, `go.mod`) with minimal production dependencies
- `.env.sample` listing required environment variables (at minimum: `PORT`)
- `.gitignore` appropriate for the stack
- README.md with project name, one-line description, and local run instructions
- If data needs is `true`: add a placeholder data model/schema file and an in-memory or file-based data layer (NOT a cloud database client — that's scaffold phase's job)
- If multi-page is `true`: scaffold route stubs or page components

### Step 3 — Return file list

Return the list of files written to the workspace so the main agent can verify and pass to build validation.

## Output

| Artifact | Location |
|----------|----------|
| Application source files | Workspace root (conventional layout per Step 1 patterns) |
| File list | Return to caller — array of relative paths written |

## Rules

- Scaffold dynamically based on app description — no hardcoded templates. Read the stack ecosystem's conventions (e.g., `npm init` patterns for Node, `dotnet new webapi` patterns for .NET).
- Code must be functional enough to start locally (e.g., `node src/server.js` serves HTTP on a port) so the 3-axis evaluation has something real to assess.
- Follow the project structure and common mistakes guidance from Step 1 exactly.
references/subscription-resolution.md
# Subscription Resolution — Defensive Fallback

The `azure-app-onboard` orchestrator resolves the subscription at Step 1 (login hard gate) and writes `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure` before any sub-skill runs. In normal operation, `context.json.azure.subscriptionId` is always set by the time prepare runs.

At prepare phase entry, verify `context.json.azure.subscriptionId` is set. If it is (expected path), use it — done.

If `context.json.azure` is somehow empty, resolve now rather than halting the flow:

1. **Check env vars** — if `AZURE_SUBSCRIPTION_ID` is set, use it directly (with `AZURE_TENANT_ID` if set). Write `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure`, done.
2. **Run `az account show`** — `az account show --query "{id:id, name:name, tenantId:tenantId}" -o json`. If it succeeds, **auto-select** — write `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure`. Do NOT run `az account list` or present a picker.
3. **Fallback: `mcp_azure_mcp_subscription_list` + picker** — only if `az account show` fails. Call `mcp_azure_mcp_subscription_list` to retrieve all subscriptions (returns `subscriptionId`, `displayName`, `isDefault`).
   - **1 subscription** → auto-select, no question. Write `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure`.
   - **2+ subscriptions** → present a picker via `ask_user`: list each subscription as a choice `"{displayName} ({subscriptionId})"` with the default marked. The user selects one. Write `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure`.
4. **MCP tool fails** → run `az login` (interactive browser login). If that fails (no browser, remote session), fall back to `az login --use-device-code`. After login succeeds, retry from step 2. Do NOT proceed without a resolved subscription.
references/zero-code-path.md
# Zero-Code Path

When the workspace is empty (no project files, no Dockerfile), prereq scaffolds a starter project before evaluating.

## Step 0: Check Existing Context

⛔ **Before asking anything, check `context.json.intent.userPrompt`.** The orchestrator (or the user's original message on direct entry) already captured what the user wants. If `userPrompt` contains an app description (e.g., "build me a todo app", "REST API with auth", "a notes app with three tabs"), treat it as the answer to "what are you building?" and skip directly to Step 2 (recommend stack). Only ask Step 1's question if `userPrompt` is vague ("help me get started", "deploy to Azure") or empty.

## Flow

1. **Ask what they want to build** (skip if Step 0 resolved it): *"What kind of app are you building?"* (e.g., "a todo app", "REST API"). Users know what they want, not what stack to use.
2. **Recommend a stack** based on the app description (from `userPrompt` or Step 1): *"For a REST API with a database, I'd suggest Node.js with a framework like Express or Fastify. Sound good?"* User can accept or override.
3. ⛔ **Confirm before scaffolding** via `ask_user`.
4. **Scaffold minimal starter project** — ⛔ **You MUST read [subagent-starter-scaffold.md](subagent-starter-scaffold.md).** Your NEXT action MUST be a `task` call with the FULL template text (verbatim) + app description + chosen stack + workspace path + data needs flag + multi-page flag. Do NOT generate code inline — the sub-agent applies starter patterns internally and writes files to the workspace. After the sub-agent returns, verify the file list is non-empty and proceed to Step 5.
5. **Validate generated code** — ⛔ **When the agent has WRITTEN code from scratch, offer to run build validation before evaluation.** The generated code has never been tested. Present: **"I've scaffolded your app. Want me to install dependencies, build, and run tests? (Yes / Skip)"** ⛔ **General prior consent** (e.g., "yes", "go ahead", "fix it") **does NOT constitute consent** — the user must answer THIS specific question. If they say Skip, run nothing.
   - If **Yes**: run `npm install` → `npm run build` → `npm test` (or stack equivalent). If any step fails, fix the issue and retry (max 2 attempts). This is allowed because the agent WROTE the code — it's not an existing repo.
   - If **Skip**: proceed to Step 6. The deploy phase will handle builds via Oryx/ACR.
   - ⛔ **This gate applies ONLY to code the agent generated from scratch.** It does NOT apply to existing repos — those follow the deploy-as-is principle with the ABSOLUTE PROHIBITION on `npm install` during prereq.
6. Run the full 3-axis evaluation (from prereq Step 3) on the scaffolded code.
7. Continue to prereq Step 4 (write artifacts + readiness gate).

## Rules

- Max 3 interactions before scaffolding begins (counting from Step 1 — Step 0's `userPrompt` check doesn't count as an interaction).
- Scaffold dynamically based on app description — no hardcoded templates. Read the stack ecosystem's conventions (e.g., `npm init` patterns for Node, `dotnet new webapi` patterns for .NET).
- If user gives no app description after 2 attempts AND `userPrompt` was also vague: *"I can't evaluate an empty workspace without knowing what you want to build. Try: 'I want to build a REST API' or 'Help me scaffold a Node.js API.'"*
- ⛔ **Do NOT generate Azure infrastructure (Bicep/Terraform/azure.yaml) here.** This path creates application source code only. Infrastructure is the scaffold phase's job.
- The generated code should be functional enough to start locally (e.g., `node index.js` serves HTTP on a port) so prereq's evaluation has something real to assess.
SKILL.md
---
name: azure-app-onboard-prereq
description: "Assess whether source code is ready to deploy to Azure — the check BEFORE infrastructure work. Evaluates build health, app completeness, dependencies and local services, stack compatibility, and deployment feasibility. Answers questions about what your app needs before it can be deployed — frameworks, dependencies, and configuration. Checks whether dependencies are compatible and identifies deployment blockers and unsupported frameworks. WHEN: \"evaluate my repo\", \"is my app ready to deploy\", \"what does my app need to deploy\", \"what do I need before deploying\", \"does my app need\", \"can I ship this to Azure\", \"scan my repo for issues\", \"is this app deployable\", \"check if my app is ready for Azure\", \"do I need a Dockerfile\", \"what's blocking my deployment\", \"are there any blockers\", \"are my dependencies compatible\", \"does Azure support my framework\", \"what needs to change before deploying\", \"check my app configuration\"."
license: MIT
metadata:
  author: Microsoft
  version: "1.2.2"
---

# Azure App Onboard Prereq — Repository Evaluation

Evaluate a user's repository for build health, app completeness, and Azure deployment feasibility — before infrastructure planning. Produces per-component verdicts (PASS/WARN/FAIL) consumed by downstream phases.

> **Orchestrator relationship:** Called by `azure-app-onboard` at Step 3, or standalone for code readiness checks. When called by orchestrator, return control to `azure-app-onboard` after writing artifacts — do NOT invoke downstream phases directly.

Phase 1 of 4 in AppOnboard pipeline. Session: `.copilot-azure/sessions/{session-id}/`. Reads `context.json`. Writes `components[]`, `repo{}`, `detectedInfra[]`. Produces `prereq-output.json`. Schema: [`prereq-schemas.ts`](references/prereq-schemas.ts) — `PrereqOutput`, `BuildRequirements`. Direct entry supported.

## When NOT to Use

| Signal | Redirect |
|--------|----------|
| Validate infrastructure (Bicep/TF/azure.yaml) | **azure-validate** |
| Generate IaC | **azure-prepare** |
| End-to-end idea-to-production | **azure-app-onboard** |
| Run `azd up` or deploy | **azure-deploy** |

## Rules

> ⛔ **ABSOLUTE PROHIBITION — `npm install`, `npm test`, `npx jest`, `pytest`, and ALL install/build/test commands are NEVER allowed.**
> Under NO circumstances may you run `npm install`, `npm test`, `npx jest`, `pip install`, `pytest`, `dotnet build`, `dotnet restore`, `dotnet test`, `go mod download`, `cargo build`, or ANY package-manager install, build, or test command during the prereq phase. Do NOT run test suites to verify code — check for test config files statically instead. The prereq phase is read-only evaluation + static-only verification.
> **ONLY exception — two sanctioned contexts, both consent-gated:** (a) code the agent **modified** during migration/remediation (see [remediation-protocol.md](references/remediation-protocol.md) step 6), or (b) code the agent **wrote** from scratch on the zero-code path (see [zero-code-path.md](references/zero-code-path.md)). In either case, install/build/test runs ONLY via the user-confirmed build-validation gate ([build-check.md](references/build-check.md) Step 3), after the user answers that specific per-command consent prompt. General prior consent never counts.

1. ⛔ **Full pipeline (Steps 1–8), no exceptions.** All prompts → Step 1 directly. Answer specific questions AS PART OF findings (Step 5), not before.
2. ⛔ **No sub-agents for evaluation.** 3-axis evaluation is inline. **Exception**: zero-code-path scaffolding (Step 2).
3. Code/destructive modifications require `ask_user`. Max 3 questions before results. Direct entry: don't repeat orchestrator's intent questions.

## MCP Tools

| Tool | Purpose |
|------|---------|
| `mcp_azure_mcp_get_azure_bestpractices` | Validate detected stack patterns against Azure best practices |
| `mcp_azure_mcp_extension_cli_install` | Check/install required CLI tools (az, azd, func) |

## Workflow

### Step 1: Session Check

**Orchestrator entry:** Session exists — read `context.json`, proceed to Step 2.

**Direct entry:** Check `.copilot-azure/sessions/active-session.json`:
- **Exists** → ⛔ read [session-protocol.md](references/session-protocol.md) for resume/fresh gate. Do NOT proceed until user answers.
- **Missing** → create session: generate UUID, `New-Item -ItemType Directory -Path ".copilot-azure/sessions/{uuid}" -Force`, write `context.json` + `active-session.json` via `create` tool.

Then: `az account show` → merge `{id, name, tenantId}` into `context.json.azure`. ⛔ Session MUST exist on disk before any scanning.

### Step 2: Scan Workspace

Scan for project files. Detect components, `repo{}`, `detectedInfra[]`, `detectedServices[]`. Classify Terraform providers. Check CLI availability. Stack detection conflicts: user explicit statement wins (write to `context.json`, mark scan as override); scan-only → confirm with user; multiple stacks → show all and ask (see [component-mapping.md](references/component-mapping.md)); no code → [zero-code-path.md](references/zero-code-path.md).

> If no project files, no Dockerfile, AND no index.html → ⛔ read [zero-code-path.md](references/zero-code-path.md).

> ⛔ **Cloud SDK early gate.** Grep for `aws-sdk|@aws-sdk|boto3|google-cloud|@google-cloud|firebase`. If functional deps found → read [cloud-sdk-migration.md](references/cloud-sdk-migration.md), then `ask_user`: **"Redirect to Azure Cloud Migrate"** (set `routeToSkill: "azure-cloud-migrate"`) · **"Continue evaluation anyway"** (finish readiness eval + SDK→Azure mapping, then STOP at Step 8 — no plan until the deps are swapped) · **"Cancel"**.

### Step 3: Per-Component Evaluation

| Sub-step | Action | Reference |
|----------|--------|-----------|
| 3.1 | **Build check** | ⛔ **You MUST read [build-check.md](references/build-check.md)** |
| 3.2 | **Completeness check** | ⛔ **You MUST read [completeness-check.md](references/completeness-check.md)** |
| 3.3 | **Deployability check** | ⛔ **You MUST read [deployability-check.md](references/deployability-check.md)** |
| 3.3a | **Component mapping** (conditional) | Read [component-mapping.md](references/component-mapping.md) ONLY IF >1 project manifest found (monorepo) |

Populate `buildRequirements` per component after evaluation. Verdict propagation, tier rules, and f1Viable aggregation are in [readiness-gate.md](references/readiness-gate.md) and the individual check references.

### Step 4: Write Artifacts + Readiness Gate

⛔ Verify `context.json` exists on disk. Read [readiness-gate.md](references/readiness-gate.md) (verdicts, tiers, batch-then-approve, fast-track) then [prereq-artifacts.md](references/prereq-artifacts.md) (write procedures, schemas).

### Step 5: Present Findings

Per [readiness-gate.md § Present Findings](references/readiness-gate.md) — show verdicts grouped by severity before proceeding.

### Step 6: Remediation (conditional)

⛔ **You MUST read [remediation-protocol.md](references/remediation-protocol.md)** IF any ❌ FAIL verdict, 🔧 Recommended Fix, or ⚠️ WARN with `fixPhase: "prereq"` exists. Contains remediation loop, static verification, re-eval mandate, post-remediation artifact updates, and the build-validation consent gate. If all verdicts are ✅ PASS or ⚠️ WARN without `fixPhase: "prereq"`, skip to Step 7.

### Step 7: Write Final State

`completedPhases` already has `"prereq"` + `currentPhase: null` (from Step 4). Then:

> ⛔ **Write `lastScanCommit`.** Run `git rev-parse HEAD` and store the full 40-character SHA as `context.json.repo.lastScanCommit`. Required — staleness guard in Step 1 compares to HEAD on resume to detect changes.

### Step 8: Route

⛔ **Mandatory — do NOT skip this step.**

> **Routing fields:** All routing writes `routeToSkill` and `routeReason` to `context.json`.

> **Post-remediation context:** If Step 6 ran, lead the routing prompt with: "Remediation complete — {N} issues fixed, your app is now {overallHealth}."

> ⛔ **Evaluate rows top to bottom — first match wins.**

| # | Condition | Action |
|---|-----------|--------|
| 1 | `routeToSkill` set (any entry) | `ask_user`: "Redirect to {routeToSkill}" / "Not now". ⛔ Pipeline stops — do NOT proceed to architecture planning. |
| 2 | `cloudSdkFindings[]` non-empty (user chose "Continue evaluation anyway") | Present the cloud-SDK → Azure swap mapping as 🔶 blockers, then `ask_user` with this exact prompt: **"🔶 Cloud SDK migration required — these dependencies must be swapped before this app can deploy to Azure. (Redirect to azure-cloud-migrate / Stop — swap manually and re-run)"** — Redirect sets `routeToSkill: "azure-cloud-migrate"`, Stop halts. ⛔ Pipeline stops — do NOT proceed to architecture planning, and do NOT offer a "continue to prepare" option; the app can't deploy until the deps are swapped. |
| 3 | Orchestrator + no `routeToSkill` | Tell the user: "✅ Your app has been evaluated and is ready — let's plan your Azure deployment." Then invoke `azure-app-onboard`. ⛔ Do NOT stop, do NOT wait for user input, do NOT narrate internal handoffs. The user already consented to the full pipeline at scope triage. |
| 4 | Direct + ready/readyWithCaveats + no Azure infra | `ask_user`: "Deploy to Azure (full pipeline)" → invoke `azure-app-onboard` / "Not now" |
| 5 | Direct + ready/readyWithCaveats + existing Azure infra | `ask_user`: "Start fresh" → invoke `azure-app-onboard` / "Use existing infra" → invoke `azure-prepare` / "Not now" |
| 6 | Direct + blocked | Report blocker summary + "Fix and re-run." |

Severity tiers (🛑🔶❌🔧⚠️✅) are defined in [readiness-gate.md](references/readiness-gate.md).

## Outputs

| Artifact | Location | Consumer |
|----------|----------|----------|
| Session context | `context.json` → `components[]`, `repo{}`, `detectedInfra[]`, `detectedServices[]` | All downstream phases |
| Prereq output | `prereq-output.json` | prepare phase (via `azure-app-onboard`) |
| Readiness report | `.copilot-azure/sessions/{uuid}/readiness-report.md` | User (offline reference) |